The compiler no longer emits warnings that should be emitted by a control flow analyzer. Major cleanup and refactoring

This commit is contained in:
Mattia Giambirtone 2023-10-01 13:11:30 +02:00
parent db41234ee0
commit f5d091bb9b
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
2 changed files with 29 additions and 58 deletions

View File

@ -165,8 +165,6 @@ type
belongsTo*: Name
# Where is this node declared in its file?
line*: int
# Has this name been referenced at least once?
resolved*: bool
# The AST node associated with this node. This
# is needed because we compile function and type
# declarations only if, and when, they're actually
@ -288,8 +286,8 @@ type
proc compile(self: PeonCompiler, node: ASTNode): TypedNode
proc toIntrinsic(name: string): Type
proc done(self: PeonCompiler): bool {.inline.}
proc wrap(self: Type): Type {.inline.}
proc unwrap(self: Type): Type {.inline.}
proc wrapType(self: Type): Type {.inline.}
proc unwrapType(self: Type): Type {.inline.}
proc toRef(self: Type): Type {.inline.}
proc toConst(self: Type): Type {.inline.}
proc toPtr(self: Type): Type {.inline.}
@ -466,14 +464,14 @@ proc handleMagicPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
self.error("'magic' pragma: wrong argument value", pragma.args[0])
if TypeDecl(name.node).isRef:
name.valueType = name.valueType.toRef()
name.valueType = name.valueType.wrap()
name.valueType = name.valueType.wrapType()
name.valueType.isBuiltin = true
else:
self.error("'magic' pragma is not valid in this context")
proc handleNullablePragma(self: PeonCompiler, pragma: Pragma, name: Name) =
## Handles the "magic" pragma. Assumes the given name is already
## Handles the "nullable" pragma. Assumes the given name is already
## declared
if pragma.args.len() > 0:
self.error(&"'nullable' pragma: wrong number of arguments (expected 0, got {len(pragma.args)})")
@ -549,7 +547,7 @@ proc warning(self: PeonCompiler, kind: WarningKind, message: string, name: Name
stderr.styledwriteLine(resetStyle, fgRed, "Failed to retrieve line information")
proc wrap(self: Type): Type {.inline.} =
proc wrapType(self: Type): Type {.inline.} =
## Wraps a type in a typevar if it's not already
## wrapped
if self.kind != Typevar:
@ -557,7 +555,7 @@ proc wrap(self: Type): Type {.inline.} =
return self
proc unwrap(self: Type): Type {.inline.} =
proc unwrapType(self: Type): Type {.inline.} =
## Unwraps a typevar if it's not already
## unwrapped
if self.kind == Typevar:
@ -941,7 +939,6 @@ proc find(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsic()): N
# If we got here, we can access the name
if self.compare(obj.valueType, kind):
result = obj
result.resolved = true
break
if not result.isNil():
break
@ -957,8 +954,7 @@ proc findOrError(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsi
proc findAll(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsic()): seq[Name] =
## Like find(), but doesn't stop at the first match. Returns
## a list of matches. Note that names found through this function
## are not marked as resolved
## a list of matches
var depth = self.scopeDepth
while depth >= 0:
if self.names[depth].hasKey(name):
@ -1115,28 +1111,6 @@ proc beginScope(self: PeonCompiler) =
proc endScope(self: PeonCompiler) =
## Closes the current lexical
## scope and reverts to the one
## above it
for names in self.names[self.scopeDepth].values():
for name in names:
if not name.resolved:
# We emit warnings for names that are declared but never used
case name.kind:
of NameKind.Var:
# We ignore names that start with an underscore (simply by convention)
# and of course we ignore public names because we can't know if they have
# been exported into or imported from other modules and not used yet
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
of NameKind.Argument:
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
if not name.belongsTo.isNil() and not name.belongsTo.valueType.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved:
# Builtin functions never use their arguments. We also don't emit this
# warning if the function was generated internally by the compiler (for
# example as a result of generic/auto type instantiation) because such
# objects do not exist in the user's code and are likely duplicated anyway
self.warning(UnusedName, &"argument '{name.ident.token.lexeme}' of '{name.belongsTo.ident.token.lexeme}' is unused (add '_' prefix to silence warning)", name)
else:
discard
discard self.names.pop()
dec(self.scopeDepth)
assert self.scopeDepth == self.names.high()
@ -1323,7 +1297,6 @@ proc match(self: PeonCompiler, name: string, sig: TypeSignature, node: ASTNode =
of 1:
# Match found, all good!
result = impl[0]
result.resolved = true
if result.kind == NameKind.Var:
# Variables bound to other names must always
# have this field set
@ -1426,7 +1399,7 @@ proc unpackTypes(self: PeonCompiler, condition: Expression, list: var seq[tuple[
var typ = self.inferOrError(condition).kind
if not isGeneric and not self.compare(typ, "typevar".toIntrinsic()):
self.error(&"expecting a type name, got value of type {self.stringify(typ)}", condition)
typ = typ.unwrap()
typ = typ.unwrapType()
if self.compare(typ, "auto".toIntrinsic()):
self.error("automatic types cannot be used within type constraints", condition)
list.add((accept, typ, condition))
@ -1591,7 +1564,7 @@ proc declare(self: PeonCompiler, node: ASTNode): Name {.discardable.} =
interfaces: @[])
if node.isRef:
kind = kind.toRef()
kind = kind.wrap()
kind = kind.wrapType()
self.addName(Name(kind: NameKind.CustomType,
depth: self.scopeDepth,
owner: self.currentModule,
@ -1613,7 +1586,7 @@ proc declare(self: PeonCompiler, node: ASTNode): Name {.discardable.} =
proc identifier(self: PeonCompiler, node: IdentExpr): TypedExpr =
## Compiles name resolution
return newTypedIdentExpr(node, self.findOrError(node.name.lexeme))
return newTypedIdentExpr(node, self.findOrError(node.name.lexeme))
proc unary(self: PeonCompiler, node: UnaryExpr): TypedUnaryExpr =
@ -1731,7 +1704,7 @@ proc refExpr(self: PeonCompiler, node: Ref): TypedExpr =
## Compiles ref expressions
result = self.check(node.value, "typevar".toIntrinsic())
result.kind = result.kind.toRef()
result.kind = result.kind.wrap()
result.kind = result.kind.wrapType()
proc constExpr(self: PeonCompiler, node: ast.Const): TypedExpr =
@ -1742,7 +1715,7 @@ proc constExpr(self: PeonCompiler, node: ast.Const): TypedExpr =
result.kind = result.kind.toConst()
if result.kind.value.wrapped.kind in [Reference, Lent]:
result.kind.nullable = result.kind.value.wrapped.nullable
result.kind = result.kind.wrap()
result.kind = result.kind.wrapType()
proc lentExpr(self: PeonCompiler, node: ast.Lent): TypedExpr =
@ -1751,8 +1724,9 @@ proc lentExpr(self: PeonCompiler, node: ast.Lent): TypedExpr =
# Only match references
kind.wrapped = kind.wrapped.toRef()
result = self.check(node.value, kind)
# Wrap the result back
result.kind = result.kind.toLent()
result.kind = result.kind.wrap()
result.kind = result.kind.wrapType()
#[
method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
@ -1813,7 +1787,7 @@ proc expression(self: PeonCompiler, node: Expression): TypedExpr =
of ptrExpr:
result = self.check(Ref(node).value, "typevar".toIntrinsic())
result.kind = result.kind.toPtr()
result.kind = result.kind.wrap()
result.kind = result.kind.wrapType()
of NodeKind.lentExpr:
result = self.lentExpr(ast.Lent(node))
of NodeKind.constExpr:
@ -1853,6 +1827,7 @@ proc whileStmt(self: PeonCompiler, node: WhileStmt): TypedNode =
# Compile and check the condition
let condition = self.check(node.condition, "bool".toIntrinsic())
# Compile the body
return newTypedWhileStmt(node, TypedBlockStmt(self.compile(node.body)), condition)
@ -1873,13 +1848,14 @@ proc varDecl(self: PeonCompiler, node: VarDecl): TypedVarDecl =
init = TypedExpr(self.compile(node.value))
typ = init.kind
if not node.valueType.isNil():
# Type declaration always takes over
# Explicit type declaration always takes over
typ = self.inferOrError(node.valueType).kind
if typ.isNil():
self.error("expecting either a type declaration or an initializer, but neither was found", node)
self.error("expecting either a type declaration or an initializer value, but neither was found", node)
# Check that the inferred type is an actual type
# and not a value
typ = self.check(typ, "typevar".toIntrinsic(), node).unwrap()
# and not a value. This is to guard against things
# like "var x: 1 = 1;"
typ = self.check(typ, "typevar".toIntrinsic(), node.valueType).unwrapType()
# Now check that the type of the initializer, if it exists,
# matches the type of the variable
if not init.isNil():
@ -1930,7 +1906,7 @@ proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
for argument in node.arguments:
typ = self.inferOrError(argument.valueType).kind
if typ.kind != Generic:
typ = self.check(argument.valueType, "typevar".toIntrinsic()).kind.unwrap()
typ = self.check(argument.valueType, "typevar".toIntrinsic()).kind.unwrapType()
if self.compare(typ, "auto".toIntrinsic()):
name.valueType.isAuto = true
arg = Name(depth: self.scopeDepth,
@ -1958,7 +1934,7 @@ proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
# The function needs a return type too!
name.valueType.returnType = self.inferOrError(node.returnType).kind
if name.valueType.returnType.kind != Generic:
name.valueType.returnType = self.check(node.returnType, "typevar".toIntrinsic()).kind.unwrap()
name.valueType.returnType = self.check(node.returnType, "typevar".toIntrinsic()).kind.unwrapType()
if not name.valueType.isAuto and self.compare(name.valueType.returnType, "auto".toIntrinsic()):
name.valueType.isAuto = true
if node.body.isNil():
@ -1976,12 +1952,8 @@ proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
self.currentFunction = name
if BlockStmt(node.body).code.len() == 0:
self.error("cannot declare function with empty body")
var last: Declaration
for decl in BlockStmt(node.body).code:
if not last.isNil() and last.kind == returnStmt:
self.warning(UnreachableCode, "code after 'return' statement is unreachable", nil, decl)
discard self.compile(decl)
last = decl
self.endScope()
# Restores the enclosing function (if any).
# Makes nested calls work (including recursion)
@ -2043,7 +2015,7 @@ proc typeDecl(self: PeonCompiler, node: TypeDecl, name: Name = nil): TypedTypeDe
# global namespace. I don't love unqualified enums, but I use
# them because I'm lazy and it often leads to problems, so since
# peon is also meant (among other things) to address my pain points
# with the languages I'm currentlyusing , I'm going to explicitly
# with the languages I'm currently using , I'm going to explicitly
# forbid myself from using unqualified enum members ever again :)
var res = newTypedEnumDecl(node, name, @[], Type(kind: Enum, members: @[]))
for member in node.members:
@ -2131,7 +2103,6 @@ proc compile*(self: PeonCompiler, tree: seq[Declaration], file, source: string,
file: self.file,
path: self.file,
ident: newIdentExpr(Token(lexeme: self.file, kind: Identifier)),
resolved: true,
line: 1)
self.addName(mainModule)
self.currentModule = mainModule
@ -2152,7 +2123,6 @@ proc compile*(self: PeonCompiler, tree: seq[Declaration], file, source: string,
),
ident: newIdentExpr(Token(lexeme: "", kind: Identifier)),
kind: NameKind.Function,
resolved: true,
line: 1)
self.addName(main)
while not self.done():

View File

@ -56,7 +56,8 @@ type
Parser* = ref object
## A recursive-descent top-down
## parser implementation
## parser for the Peon programming
## language
# Index into self.tokens
current: int
@ -241,7 +242,7 @@ proc check[T: TokenType or string](self: Parser, kind: T, distance: int = 0): bo
proc check[T: TokenType or string](self: Parser, kind: openarray[T]): bool {.inline.} =
## Calls self.check() in a loop with each entry of
## Calls self.check() in a loop with each element of
## the given openarray of token kinds and returns
## at the first match. Note that this assumes
## that only one token may match at a given
@ -263,7 +264,7 @@ proc match[T: TokenType or string](self: Parser, kind: T): bool {.inline.} =
proc match[T: TokenType or string](self: Parser, kind: openarray[T]): bool {.inline.} =
## Calls self.match() in a loop with each entry of
## Calls self.match() in a loop with each element of
## the given openarray of token kinds and returns
## at the first match. Note that this assumes
## that only one token may exist at a given
@ -923,7 +924,7 @@ proc varDecl(self: Parser, isLet: bool = false,
self.error("constant initializer is not a constant")
if value.isNil() and tok.kind == TokenType.Let:
self.error("let declaration requires an initializer")
self.expect(Semicolon, "expecting semicolon after declaration")
self.expect(Semicolon, &"expecting semicolon after '{tok.lexeme}' declaration")
if self.match(TokenType.Pragma):
for pragma in self.parsePragmas():
pragmas.add(pragma)