The compiler no longer emits warnings that should be emitted by a control flow analyzer. Major cleanup and refactoring
This commit is contained in:
parent
db41234ee0
commit
f5d091bb9b
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue