Initial work on generic narrowing, clean up and get rid of old unnecessary things, yeet inheritance

This commit is contained in:
2025-02-20 11:51:29 +01:00
parent 4ac9d1e6d3
commit 9c1abc43fb
6 changed files with 176 additions and 344 deletions

View File

@@ -64,7 +64,7 @@ type
## Forward declarations
proc isStaticallyConvertible(value: TypedExpr, b: Type): bool
proc compareWithContext(self: TypeChecker, a: TypedExpr, b: Type): Type
proc check(self: TypeChecker, node: ASTNode): TypedNode
proc typecheck(self: TypeChecker, node: ASTNode): TypedNode
proc toIntrinsic(name: string): Type
proc done(self: TypeChecker): bool {.inline.}
proc toRef(self: Type): Type {.inline.}
@@ -76,7 +76,6 @@ proc unwrapType(self: Type): Type {.inline.}
proc handleErrorPragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handlePurePragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handleMagicPragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handleUnsafePragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handleWarnPragma(self: TypeChecker, pragma: Pragma, name: Name)
proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil)
proc stringify*(self: TypeChecker, typ: Type, verbose = false): string
@@ -86,7 +85,6 @@ proc compare(self: TypeChecker, a, b: Type): bool
proc expression(self: TypeChecker, node: Expression): TypedExpr
proc declareGenerics(self: TypeChecker, name: Name)
proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl
proc getTypeDistance(self: TypeChecker, a, b: Type): int
proc addName(self: TypeChecker, name: Name)
proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic(), skip: int = 0): Name
proc ensureBoolean(self: TypeChecker, node: Expression): TypedExpr {.inline.}
@@ -117,7 +115,6 @@ proc newTypeChecker*: TypeChecker =
result.pragmas["pure"] = PragmaFunc(kind: Immediate, handler: handlePurePragma)
result.pragmas["error"] = PragmaFunc(kind: Delayed, handler: handleErrorPragma)
result.pragmas["warn"] = PragmaFunc(kind: Delayed, handler: handleWarnPragma)
result.pragmas["unsafe"] = PragmaFunc(kind: Immediate, handler: handleUnsafePragma)
result.showMismatches = false
@@ -161,10 +158,7 @@ proc handleMagicPragma(self: TypeChecker, pragma: Pragma, name: Name) =
if pragma.args[0].token.lexeme[1..^2] == "owo":
# :3
self.error("Thou hast discovered ze forbidden type. Huzzah! :3", pragma)
let typ = pragma.args[0].token.lexeme[1..^2].toIntrinsic()
if typ.isNil():
self.error("'magic' pragma: wrong argument value", pragma.args[0])
name.valueType = typ
name.valueType = pragma.args[0].token.lexeme[1..^2].toIntrinsic()
elif name.node.kind == NodeKind.funDecl:
name.valueType.isBuiltin = true
name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
@@ -199,13 +193,6 @@ proc handlePurePragma(self: TypeChecker, pragma: Pragma, name: Name) =
discard # TODO
else:
self.error("'pure' pragma is not valid in this context")
proc handleUnsafePragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "unsafe" pragma
if name.node.kind notin [NodeKind.funDecl, NodeKind.lambdaExpr]:
self.error("'unsafe' pragma is not valid in this context")
name.valueType.unsafe = true
proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
@@ -254,9 +241,9 @@ proc wrapType(self: Type): Type {.inline.} =
## Wraps a type in a typevar
case self.kind:
of Union:
result = Type(kind: Union, types: @[], displayName: self.displayName)
for typ in self.types:
result.types.add((match: typ.match, kind: typ.kind.wrapType(), value: typ.value))
result = Type(kind: Union, constraints: @[], displayName: self.displayName)
for typ in self.constraints:
result.constraints.add((match: typ.match, kind: typ.kind.wrapType(), value: typ.value))
else:
result = Type(kind: Typevar, wrapped: self)
@@ -268,9 +255,9 @@ proc unwrapType(self: Type): Type {.inline.} =
of Typevar:
result = self.wrapped
of Union:
result = Type(kind: Union, types: @[], displayName: self.displayName)
for typ in self.types:
result.types.add((match: typ.match, kind: typ.kind.unwrapType(), value: typ.value))
result = Type(kind: Union, constraints: @[], displayName: self.displayName)
for typ in self.constraints:
result.constraints.add((match: typ.match, kind: typ.kind.unwrapType(), value: typ.value))
else:
result = self
@@ -302,53 +289,51 @@ proc toIntrinsic(name: string): Type =
## type
case name:
of "typevar":
return Type(kind: Typevar, wrapped: "any".toIntrinsic(), intrinsic: true)
return Type(kind: Typevar, wrapped: "any".toIntrinsic())
of "any":
return Type(kind: Any, intrinsic: true)
of "auto":
return Type(kind: Auto, intrinsic: true)
return Type(kind: Any)
of "int64", "i64":
return Type(kind: Integer, size: LongLong, signed: true, intrinsic: true)
return Type(kind: Integer, size: LongLong, signed: true)
of "uint64", "u64":
return Type(kind: Integer, size: LongLong, signed: false, intrinsic: true)
return Type(kind: Integer, size: LongLong, signed: false)
of "int32", "i32":
return Type(kind: Integer, size: Long, signed: true, intrinsic: true)
return Type(kind: Integer, size: Long, signed: true)
of "uint32", "u32":
return Type(kind: Integer, size: Long, signed: false, intrinsic: true)
return Type(kind: Integer, size: Long, signed: false)
of "int16", "i16":
return Type(kind: Integer, size: Short, signed: true, intrinsic: true)
return Type(kind: Integer, size: Short, signed: true)
of "uint16", "u16":
return Type(kind: Integer, size: Short, signed: false, intrinsic: true)
return Type(kind: Integer, size: Short, signed: false)
of "int8", "i8":
return Type(kind: Integer, size: Tiny, signed: true, intrinsic: true)
return Type(kind: Integer, size: Tiny, signed: true)
of "uint8", "u8":
return Type(kind: Integer, size: Tiny, signed: false, intrinsic: true)
return Type(kind: Integer, size: Tiny, signed: false)
of "float", "float64", "f64":
return Type(kind: Float, width: Full, intrinsic: true)
return Type(kind: Float, width: Full)
of "float32", "f32":
return Type(kind: Float, width: Half, intrinsic: true)
return Type(kind: Float, width: Half)
of "byte":
return Type(kind: Byte, intrinsic: true)
return Type(kind: Byte)
of "char":
return Type(kind: Char, intrinsic: true)
return Type(kind: Char)
of "NaN":
return Type(kind: TypeKind.Nan, intrinsic: true)
return Type(kind: TypeKind.Nan)
of "Inf":
return Type(kind: Infinity, positive: true, intrinsic: true)
return Type(kind: Infinity, positive: true)
of "NegInf":
return Type(kind: Infinity, intrinsic: true)
return Type(kind: Infinity)
of "bool":
return Type(kind: Boolean, intrinsic: true)
return Type(kind: Boolean)
of "string":
return Type(kind: String, intrinsic: true)
return Type(kind: String)
of "pointer":
return Type(kind: Pointer, value: "any".toIntrinsic(), intrinsic: true)
return Type(kind: Pointer, value: "any".toIntrinsic())
of "lent":
return Type(kind: TypeKind.Lent, value: "any".toIntrinsic(), intrinsic: true)
return Type(kind: TypeKind.Lent, value: "any".toIntrinsic())
of "const":
return Type(kind: TypeKind.Const, value: "any".toIntrinsic(), intrinsic: true)
return Type(kind: TypeKind.Const, value: "any".toIntrinsic())
of "ref":
return Type(kind: Reference, value: "any".toIntrinsic(), intrinsic: true)
return Type(kind: Reference, value: "any".toIntrinsic())
else:
raise newException(ValueError, &"invalid intrinsic '{name}'")
@@ -442,7 +427,7 @@ proc isAny(typ: Type): bool =
of Any:
return true
of Union:
for condition in typ.types:
for condition in typ.constraints:
if condition.kind.isAny():
return true
of Typevar:
@@ -531,8 +516,7 @@ proc compare(self: TypeChecker, a, b: Type): bool =
return false
return true
of Boolean, Infinity, Any,
Auto, Char, Byte, String:
of Boolean, Infinity, Any, Char, Byte, String:
return true
of Integer:
return a.size == b.size and a.signed == b.signed
@@ -540,10 +524,10 @@ proc compare(self: TypeChecker, a, b: Type): bool =
return a.width == b.width
of TypeKind.Lent, Reference, Pointer, TypeKind.Const:
return self.compare(a.value, b.value)
of Union:
if a.types.len() > b.types.len():
return self.isSubsetOf(b.types, a.types)
return self.isSubsetOf(a.types, b.types)
of Union, Generic:
if a.constraints.len() > b.constraints.len():
return self.isSubsetOf(b.constraints, a.constraints)
return self.isSubsetOf(a.constraints, b.constraints)
of Function:
# TODO
return false
@@ -551,9 +535,9 @@ proc compare(self: TypeChecker, a, b: Type): bool =
# TODO
return false
if a.kind == Union:
return self.matchUnion(b, a.types)
return self.matchUnion(b, a.constraints)
if b.kind == Union:
return self.matchUnion(a, b.types)
return self.matchUnion(a, b.constraints)
return false
@@ -734,8 +718,7 @@ proc stringify*(self: TypeChecker, typ: Type, verbose: bool = false): string =
if typ.isNil():
return "void"
case typ.kind:
of Char, Byte, String, TypeKind.Nan,
Auto, Any:
of Char, Byte, String, TypeKind.Nan, Any:
result &= ($typ.kind).toLowerAscii()
of Structure:
result &= typ.name
@@ -743,7 +726,7 @@ proc stringify*(self: TypeChecker, typ: Type, verbose: bool = false): string =
result &= "["
var i = 0
for gen in typ.generics.keys():
result &= &"{gen}: {self.stringify(typ.generics[gen])}"
result &= &"{gen}: {self.stringify(typ.generics[gen], true)}"
if i < typ.generics.len() - 1:
result &= ", "
inc(i)
@@ -814,15 +797,12 @@ proc stringify*(self: TypeChecker, typ: Type, verbose: bool = false): string =
of Generic, Union:
if typ.displayName.len() > 0 and not verbose:
return typ.displayName
for i, condition in typ.types:
for i, condition in typ.constraints:
if i > 0:
result &= " | "
if not condition.match:
result &= "~"
if typ.kind == Generic:
result &= self.stringify(condition.kind.unwrapType())
else:
result &= self.stringify(condition.kind)
result &= self.stringify(condition.kind)
of Typevar:
result &= &"typevar[{self.stringify(typ.wrapped)}]"
else:
@@ -884,11 +864,29 @@ proc check(self: TypeChecker, term, expected: Type, node: ASTNode = nil): Type {
## Raises an error if appropriate and returns the typed expression
## otherwise. The node is passed in to error() in case of a failure
## for more precise error reporting
if not self.compare(term, expected):
self.error(&"expecting an expression of type {self.stringify(expected, true)}, got {self.stringify(term, true)} instead", node)
result = term
proc check(self: TypeChecker, term, expected: TypedExpr, node: ASTNode = nil): TypedExpr {.inline, discardable.} =
## Checks the type of the given expression against a known one.
## Raises an error if appropriate and returns the typed expression
## otherwise. The node is passed in to error() in case of a failure
## for more precise error reporting
if not self.compare(term.kind, expected.kind):
self.error(&"expecting an expression of type {self.stringify(expected, true)}, got {self.stringify(term, true)} instead", node)
result = term
proc check(self: TypeChecker, term: TypedExpr, expected: Type, node: ASTNode = nil): TypedExpr {.inline, discardable.} =
## Checks the type of the given expression against a known one.
## Raises an error if appropriate and returns the typed expression
## otherwise. The node is passed in to error() in case of a failure
## for more precise error reporting
if not self.compare(term.kind, expected):
self.error(&"expecting an expression of type {self.stringify(expected, true)}, got {self.stringify(term, true)} instead", node)
result = term
if not self.compare(result, expected):
self.error(&"expecting an expression of type {self.stringify(expected, true)}, got {self.stringify(result, true)} instead", node)
if result.isAny() and not expected.isAny():
self.error("any is not a valid type in this context", node)
proc checkWithContext(self: TypeChecker, term: TypedExpr, expected: Type, node: ASTNode = nil): Type {.inline, discardable.} =
@@ -897,113 +895,19 @@ proc checkWithContext(self: TypeChecker, term: TypedExpr, expected: Type, node:
result = self.compareWithContext(term, expected)
if result.isNil():
self.error(&"expecting an expression of type {self.stringify(expected, true)}, got {self.stringify(term, true)} instead", node)
if result.isAny() and not expected.isAny():
self.error("any is not a valid type in this context", node)
proc check(self: TypeChecker, term: Expression, expected: Type): TypedExpr {.inline, discardable.} =
proc check(self: TypeChecker, term: Expression, expected: Type, node: ASTNode = nil): TypedExpr {.inline, discardable.} =
## Checks the type of the given expression against a known one.
## Raises an error if appropriate and returns the typed expression
## otherwise
result = self.inferOrError(term)
self.check(result.kind, expected, term)
result = self.check(self.inferOrError(term), expected, node)
proc getTypeDistance(self: TypeChecker, a, b: Type): int =
## Gets the type distance of two Peon types. Assumes
## a and b are already compatible (i.e. compare(a, b)
## returns true). For more info, check out self.match()
if a.kind != Structure or b.kind != Structure:
# TODO: Test
return 0
var parent = b.parent
result = 0
# We already know that the inheritance
# chain is correct (i.e. that a is at the
# root of it) because we assume a and b are
# already compatible, so we really just need
# to walk the tree backwards and keep track of
# how many times we go up one node
while not parent.isNil():
# Juuust to be sure
when defined(debug):
if parent.parent.isNil():
assert parent.parent == a
inc(result)
parent = parent.parent
proc calcTypeDistance(self: TypeChecker, typ: Type, sig: TypeSignature): int =
## Computes the cumulative type distance between
## the given type and the given type signature. It's
## basically just adding up the type distances of each
## element in the type/signature to end up with a single
## value to represent how precisely a given type matches
## the given signature. Note that this function assumes
## that the types are already compatible
case typ.kind:
of TypeKind.Function:
for (argA, argB) in zip(typ.signature, sig):
result += self.getTypeDistance(argA.kind, argB.kind)
of TypeKind.Structure:
self.error("not implemented")
else:
self.error(&"cannot compute type distance for object of type {self.stringify(typ)}")
proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool =
## Helper to check type signatures against a known
## one. Returns true if the given type matches the
## given type signature
case typ.kind:
of TypeKind.Function:
if typ.signature.len() < sig.len():
# If the function has less arguments than
# we have in our signature, then we surely
# can't match to it. This is different from
# the code in self.compare() (which checks that
# both signatures have the same length) because
# of how we handle default arguments; This addresses
# a problem that arises that when we have a function
# that looks like (a, b, c = someDefault) -> z and we're
# looking for a signature (a, b) -> y: if we enforced that
# the length of the signatures must be the same, we'd consider
# that function to not be a good match, even though it is
# because the third argument has a default value
return false
# We construct a new signature, without the default arguments
var args: TypeSignature = @[]
for argument in typ.signature:
if argument.default.isNil():
args.add(argument)
else:
break
if args.len() != sig.len():
return false
for (argA, argB) in zip(args, sig):
if not self.compare(argA.kind, argB.kind):
return false
if argA.name == "" or argB.name == "":
continue
elif argA.name != argB.name:
return false
return true
of TypeKind.Structure:
if sig.len() != typ.fields.len():
# For now, we require that all fields of an object
# be explicitly initialized
return false
var fields: seq[Type] = @[]
var names: HashSet[string]
for fieldName in typ.fields.keys():
fields.add(typ.fields[fieldName])
else:
self.error(&"cannot check type signature for object of type {self.stringify(typ)}")
func toCapability(typ: Type, value: TypedExpr = nil): TypeCapabilities =
case typ.kind:
of Union, Generic:
for unionType in typ.types:
for unionType in typ.constraints:
for element in unionType.kind.toCapability(unionType.value):
result.add(element)
else:
@@ -1037,6 +941,7 @@ proc isCapable(self: TypeChecker, name: Name, sig: TypeSignature): bool =
if self.capabilities[functionName].len() == 0:
return false
# Find the right capability
# TODO: Probably a good idea to cache this
let candidateCapability = sig.toCapability()
var superCapability: TypeCapabilities
for capability in self.capabilities[functionName]:
@@ -1044,13 +949,9 @@ proc isCapable(self: TypeChecker, name: Name, sig: TypeSignature): bool =
result = self.isSubsetOf(candidateCapability, superCapability)
proc matchCapability(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode = nil): Name =
var
candidates: seq[Name] = @[]
matches: seq[Name] = @[]
distances: seq[int] = @[]
dst: int = 0
minDst: int = dst
proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode = nil): Name =
## Attempts to find a suitable named implementation with the given type signature
var candidates: seq[Name] = @[]
# Find all matching implementations and record their
# type distance relative to our input type signature,
# as well as the smallest one found so far
@@ -1058,58 +959,8 @@ proc matchCapability(self: TypeChecker, name: string, sig: TypeSignature, node:
# TODO: Handle default arguments
if self.isCapable(candidate, sig):
candidates.add(candidate)
dst = self.calcTypeDistance(candidate.valueType, sig)
if dst < minDst:
minDst = dst
distances.add(dst)
# When we look for an object to instantiate with a given type signature, we
# incrementally build a list of potential matches: if the resulting list has
# length zero, that means no match has been found and we should report the error
# to the user; A similarly trivial case is that of a list of length one, except of
# course it's not an error (it just means we have found the only match). A more
# interesting case is that of multiple candidate matches: the simple answer would
# be to just throw an exception warning the user of the ambiguity, except that sometimes
# that backfires hard. A practical example is going to be useful: say you have a function
# sum that takes in two integers and returns their sum. In peon, you would write something
# like this:
#
# fn sum(a, b: int): int {
# return a + b;
# }
#
# Let's now say that I overload this for some arbitrary subtype of int, MyInt
#
# fn sum(a, b: MyInt): MyInt {
# # code here
# }
#
# If I now were to call sum with two integers, that would work because the compiler
# can only find one function that matches the type signature (1, 2) for the name sum;
# If you attempted to call sum with arguments of type MyInt, however, the compiler would
# find that both functions taking int and MyInt are valid matches: this is because in peon
# (and in many other languages) you can always treat a given subtype like an instance of its
# supertype (potentially losing information, of course). The problem lies in our comparison
# function, which has no way of judging how "good" a given match is: in our example above,
# we can easily see that the overloaded implementation is the one that most closely matches
# the type signature we're looking for (since (MyInt, MyInt) -> MyInt is more precise than
# (int, int) -> int). So what we do is basically gauge how good each potential match is by
# computing a metric I call "type distance". Before explaining what type distance is, I'd like
# to point out that it is a strictly relative metric: it measures how closely a given type
# signature matches the one we're looking for, but only in relation to all of the other potentially
# compatible matches for a given lookup. This means that comparing type distances between types that
# are not compatible just doesn't make sense (you could say they're "infinitely distant"). Of course,
# should we encounter more than one match with the same type distance from our signature that would
# still be an ambiguity error, but at least the compiler isn't completely clueless now. The way type
# distance is measured is quite simple: it's simply the relative distance between the two types in their
# inheritance tree, hence a value of 0 indicates a perfect match (i.e. they're the EXACT same type, structurally
# speaking at least), while larger values indicate "less precise" ones (you could say they're further apart)
for i, candidate in candidates:
# Grab all the matches with the smallest type distance and
# matching type capability
if distances[i] == minDst:
matches.add(candidate)
case matches.len():
case candidates.len():
of 0:
# No matches
let names = self.findAll(name)
@@ -1138,34 +989,27 @@ proc matchCapability(self: TypeChecker, name: string, sig: TypeSignature, node:
else:
msg &= &": name is not defined"
self.error(msg, node)
of 1:
result = candidates[0]
else:
# We found only one matching function: easy!
result = matches[0].deepCopy()
result.valueType.signature = sig
# Extra checks
case result.valueType.kind:
of Function:
for (a, b) in zip(result.valueType.signature, sig):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
of Structure:
for (a, b) in zip(result.valueType.fields.values().toSeq(), sig):
if not a.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
else:
# TODO: Enums
discard
# Ambiguity detected
var msg = &"multiple matches found for '{name}'"
if self.showMismatches:
msg &= ":"
for fn in reversed(candidates):
msg &= &"\n- in {relativePath(fn.file, getCurrentDir())}, line {fn.line} of type {self.stringify(fn.valueType)}"
else:
msg &= " (compile with --showMismatches for more details)"
self.error(msg, node)
proc expandTypeConstraints(self: TypeChecker, condition: Expression, list: var TypeCapabilities, accept: bool = true) =
## Recursively unpacks a type constraint
case condition.kind:
of identExpr, genericExpr:
var inferred = self.ensureType(condition)
var typ = inferred.kind
let inferred = self.ensureType(condition)
let typ = inferred.kind
if not typ.isAny() and self.compare(typ, "auto".toIntrinsic()):
self.error("automatic types cannot be used within type constraints", condition)
list.add((accept, typ.unwrapType(), inferred))
of binaryExpr:
let condition = BinaryExpr(condition)
@@ -1242,13 +1086,15 @@ proc addName(self: TypeChecker, name: Name) =
proc recordCapabilities(self: TypeChecker, function: Name) =
## Records type capabilities to be able to match against them
## later
if not self.capabilities.hasKey(function.ident.token.lexeme):
self.capabilities[function.ident.token.lexeme] = @[]
var newCapability: TypeCapabilities
for parameter in function.valueType.signature:
case parameter.kind.kind:
of Generic, Union:
for constraint in parameter.kind.types:
for constraint in parameter.kind.constraints:
newCapability.add((constraint.match, constraint.kind.unwrapType(), constraint.value))
else:
newCapability.add((true, parameter.kind.unwrapType(), parameter.default))
@@ -1337,31 +1183,56 @@ proc unary(self: TypeChecker, node: UnaryExpr): TypedUnaryExpr =
## Typechecks unary expressions
var
default: TypedExpr
typeOfA = self.infer(node.a)
typeOfA = self.inferOrError(node.a)
let fn = Type(kind: Function, returnType: Type(kind: Any), signature: @[("", typeOfA.kind, default)])
let name = self.matchCapability(node.token.lexeme, fn.signature, node)
result = newTypedUnaryExpr(node, name.valueType, typeOfA)
let name = self.match(node.token.lexeme, fn.signature, node)
result = newTypedUnaryExpr(node, name.valueType.returnType, typeOfA)
proc binary(self: TypeChecker, node: BinaryExpr): TypedBinaryExpr =
## Typechecks binary expressions
var
default: TypedExpr
typeOfA = self.infer(node.a)
typeOfB = self.infer(node.b)
typeOfA = self.inferOrError(node.a)
typeOfB = self.inferOrError(node.b)
let fn = Type(kind: Function,
returnType: Type(kind: Any),
signature: @[("", typeOfA.kind, default), ("", typeOfB.kind, default)])
let name = self.matchCapability(node.token.lexeme, fn.signature, node)
let name = self.match(node.token.lexeme, fn.signature, node)
result = newTypedBinaryExpr(node, name.valueType, typeOfA, typeOfB)
proc isTypeSet(self: TypeChecker, typ: Type): bool =
## Returns whether the given type represents
## a set of types
case typ.kind:
of Typevar:
return true
of Union, Generic:
for t in typ.constraints:
if not self.isTypeSet(t.kind):
return false
else:
return false
return true
proc genericExpr(self: TypeChecker, node: GenericExpr): TypedExpr =
## Typechecks generic instantiation
var args: seq[TypedExpr] = @[]
for arg in node.args:
args.add(self.inferOrError(arg))
result = newTypedExpr(node, self.inferOrError(node.ident).kind)
let typExpr = self.ensureType(node.ident)
let typ = typExpr.kind.unwrapType()
result = newTypedGenericExpr(node, typ, args)
if result.kind.kind == Typevar:
# Typevars are a special case
if len(args) > 1:
self.error(&"too many arguments in generic instantiation (expected 1, got {len(args)} instead)", node)
if not self.isTypeSet(args[0].kind):
self.error(&"typevar instantiation only expects types, not values (got expression of type {self.stringify(args[0].kind)})", args[0].node)
result.kind = result.kind.deepCopy()
result.kind.wrapped = args[0].kind
proc call(self: TypeChecker, node: CallExpr): TypedExpr =
@@ -1370,19 +1241,18 @@ proc call(self: TypeChecker, node: CallExpr): TypedExpr =
var args: TypeSignature = @[]
var argExpr: seq[TypedExpr] = @[]
var default: TypedExpr
var kind: Type
for i, argument in node.arguments.positionals:
kind = self.inferOrError(argument).kind
args.add(("", kind, default))
argExpr.add(self.expression(argument))
let typedExpr = self.expression(argument)
args.add(("", typedExpr.kind, default))
argExpr.add(typedExpr)
for i, argument in node.arguments.keyword:
kind = self.infer(argument.value).kind
args.add((argument.name.token.lexeme, kind, default))
argExpr.add(self.expression(argument.value))
let typedExpr = self.expression(argument.value)
args.add((argument.name.token.lexeme, typedExpr.kind, default))
argExpr.add(typedExpr)
case node.callee.kind:
of NodeKind.identExpr:
# Calls like hi()
var impl = self.matchCapability(IdentExpr(node.callee).name.lexeme, args, node)
var impl = self.match(IdentExpr(node.callee).name.lexeme, args, node)
self.dispatchDelayedPragmas(impl)
case impl.valueType.kind:
of Structure:
@@ -1396,8 +1266,7 @@ proc call(self: TypeChecker, node: CallExpr): TypedExpr =
self.error("not implemented")
of NodeKind.callExpr:
# Calling a call expression, like hello()()
# TODO
#[
# TODO: is this right?
var node: Expression = node
var all: seq[CallExpr] = @[]
# Since there can be as many consecutive calls as
@@ -1410,23 +1279,17 @@ proc call(self: TypeChecker, node: CallExpr): TypedExpr =
# one and work our way to the innermost call
for exp in all:
result = self.call(exp)
]#
self.error("not implemented")
of NodeKind.getterExpr:
# Calling a.b()
# TODO
let node = GetterExpr(node.callee)
self.error("not implemented")
of NodeKind.lambdaExpr:
# Calling a lambda on the fly
var node = LambdaExpr(node.callee)
self.error("not implemented")
# TODO
of NodeKind.genericExpr:
# Calling a generic expression
self.error("not implemented")
var node = GenericExpr(node.callee)
# TODO
else:
let typ = self.infer(node.callee)
if typ.isNil():
@@ -1510,22 +1373,18 @@ proc blockStmt(self: TypeChecker, node: BlockStmt): TypedBlockStmt =
self.beginScope()
var body: seq[TypedNode] = @[]
for decl in node.body:
body.add(self.check(decl))
body.add(self.typecheck(decl))
self.endScope()
result = newTypedBlockStmt(node, body)
proc ifStmt(self: TypeChecker, node: IfStmt): TypedIfStmt =
## Typechecks if/else statements
# Check the condition
let condition = self.ensureBoolean(node.condition)
# Check the body
let then = TypedBlockStmt(self.check(node.thenBranch))
let then = TypedBlockStmt(self.typecheck(node.thenBranch))
var otherwise: TypedBlockStmt = nil
if not node.elseBranch.isNil():
# Check the else clause, if it exists
otherwise = TypedBlockStmt(self.check(node.elseBranch))
otherwise = TypedBlockStmt(self.typecheck(node.elseBranch))
# Note: Peon enforces the body of loops and conditionals to
# always be a block statement (for a variety of very good reasons,
# as it avoids mistakes like the infamous GOTO fail), so the
@@ -1538,15 +1397,16 @@ proc returnStmt(self: TypeChecker, node: ReturnStmt): TypedReturnStmt =
if self.currentFunction.isNil():
self.error("return outside a function is not allowed", node)
return newTypedReturnStmt(node, self.infer(node.value))
if self.currentFunction.valueType.returnType.isNil():
self.error("cannot return a value from a void function", node.value)
var returnType = self.check(node.value, self.currentFunction.valueType.returnType, node.value)
return newTypedReturnStmt(node, returnType)
proc whileStmt(self: TypeChecker, node: WhileStmt): TypedNode =
## Typechecks C-style while loops
let
condition = self.ensureBoolean(node.condition) # Check the condition
body = TypedBlockStmt(self.check(node.body)) # Check the body
return newTypedWhileStmt(node, body, condition)
return newTypedWhileStmt(node, TypedBlockStmt(self.typecheck(node.body)),
self.ensureBoolean(node.condition))
proc varDecl(self: TypeChecker, node: VarDecl): TypedVarDecl =
@@ -1563,7 +1423,7 @@ proc varDecl(self: TypeChecker, node: VarDecl): TypedVarDecl =
else:
if node.constant and not node.value.isConst():
self.error("const initializer must be a value of constant type", node.value)
init = TypedExpr(self.check(node.value))
init = TypedExpr(self.inferOrError(node.value))
typ = init.kind
if not node.valueType.isNil():
# Explicit type declaration always takes over
@@ -1606,12 +1466,12 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
var paramName = Name(kind: Default, ident: parameter.ident, module: self.currentModule, file: self.file, depth: self.scopeDepth,
isPrivate: true, valueType: nil, owner: self.currentFunction, line: parameter.ident.token.line,
node: node)
var default = if not parameter.default.isNil(): self.inferOrError(parameter.default) else: nil
var default = if not parameter.default.isNil(): self.expression(parameter.default) else: nil
case parameter.valueType.kind:
of NodeKind.binaryExpr, NodeKind.unaryExpr:
# Untagged type union
paramName.valueType = Type(kind: Union, types: @[])
self.expandTypeConstraints(parameter.valueType, paramName.valueType.types)
paramName.valueType = Type(kind: Union, constraints: @[])
self.expandTypeConstraints(parameter.valueType, paramName.valueType.constraints)
else:
paramName.valueType = self.ensureType(parameter.valueType).kind.unwrapType()
# Ensure the expression represents a type and not a value
@@ -1621,8 +1481,6 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
self.addName(paramName)
if not node.returnType.isNil():
name.valueType.returnType = self.ensureType(node.returnType).kind.unwrapType()
if not name.valueType.isAuto and self.compare(name.valueType.returnType, "auto".toIntrinsic()):
name.valueType.isAuto = true
self.recordCapabilities(name)
if name.valueType.isBuiltin:
self.endScope()
@@ -1639,7 +1497,7 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
let function = self.currentFunction
self.currentFunction = name
for decl in BlockStmt(node.body).body:
result.body.body.add(self.check(decl))
result.body.body.add(self.typecheck(decl))
self.endScope()
# Restores the enclosing function (if any).
# Makes nested calls work (including recursion)
@@ -1649,14 +1507,12 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
proc declareGenerics(self: TypeChecker, name: Name) =
## Helper to declare the generic arguments of the
## given name, if it has any
if name.valueType.kind notin [TypeKind.Structure, TypeKind.Function] and not name.valueType.intrinsic:
self.error("not implemented")
var
constraints: seq[tuple[match: bool, kind: Type, value: TypedExpr]] = @[]
value: Expression
for gen in name.node.generics.values():
if gen.constr.isNil():
constraints = @[(match: true, kind: "any".toIntrinsic(), value: self.infer(value))]
constraints = @[(match: true, kind: "any".toIntrinsic(), value: self.expression(value))]
else:
self.expandTypeConstraints(gen.constr, constraints)
let generic = Name(kind: Default,
@@ -1666,11 +1522,11 @@ proc declareGenerics(self: TypeChecker, name: Name) =
file: self.currentModule.file,
depth: self.scopeDepth,
isPrivate: true,
valueType: Type(kind: Generic, types: constraints, displayName: gen.ident.token.lexeme),
valueType: Type(kind: Generic, constraints: constraints, displayName: gen.ident.token.lexeme),
line: gen.ident.token.line,
)
self.addName(generic)
name.valueType.generics[gen.ident.token.lexeme] = generic.valueType.unwrapType()
name.valueType.generics[gen.ident.token.lexeme] = generic.valueType
constraints.setLen(0)
@@ -1679,7 +1535,7 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
var name = name
if name.isNil():
name = self.declare(node)
result = newTypedTypeDecl(node, name, newTable[string, TypedExpr](), nil)
result = newTypedTypeDecl(node, name, newTable[string, TypedExpr]())
self.beginScope()
# Declare the type's generics
self.declareGenerics(name)
@@ -1690,7 +1546,7 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
var fieldType: TypedExpr
var n: Name
for field in node.fields.values():
fieldType = self.infer(field.valueType)
fieldType = self.expression(field.valueType)
if fieldType.kind.kind != Reference:
# Check for self-recursion of non-ref types (which would require
# infinite memory)
@@ -1716,30 +1572,14 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
case node.value.kind:
of NodeKind.identExpr:
# Type alias
name.valueType = self.inferOrError(node.value).kind
name.valueType = self.expression(node.value).kind
of NodeKind.binaryExpr, NodeKind.unaryExpr:
# Untagged type union
name.valueType = Type(kind: Union, types: @[], displayName: name.ident.token.lexeme)
self.expandTypeConstraints(node.value, name.valueType.types)
name.valueType = Type(kind: Union, constraints: @[], displayName: name.ident.token.lexeme)
self.expandTypeConstraints(node.value, name.valueType.constraints)
else:
# Unreachable due to how we parse things. Still, better be safe
self.error(&"got node of unexpected type {node.value.kind} at typeDecl()")
if not node.parent.isNil():
# Ensure parent is actually a type
let subtype = self.ensureType(node.parent)
# Grab its name object
let parentName = subtype.getName()
# This should *never* be nil
if parentName.isNil():
self.error(&"could not obtain name information for the given object: is it a type?", node.parent)
result.parent = parentName
for field in TypeDecl(result.parent.node).fields.values():
if result.fields.hasKey(field.ident.token.lexeme):
for f in TypeDecl(result.node).fields.values():
if f.ident.token.lexeme == field.ident.token.lexeme:
# This always eventually runs
self.error(&"cannot to re-declare type member '{field}'", f.ident)
result.fields[field.ident.token.lexeme] = newTypedExpr(field.ident, result.parent.valueType.fields[field.ident.token.lexeme])
# Turn the declared type into a typevar so that future references
# to it will be distinct from its instances
name.valueType = name.valueType.wrapType()
@@ -1752,9 +1592,9 @@ proc pragmaExpr(self: TypeChecker, pragma: Pragma) =
if pragma.name.token.lexeme notin self.pragmas:
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
self.pragmas[pragma.name.token.lexeme].handler(self, pragma, nil)
proc check(self: TypeChecker, node: ASTNode): TypedNode =
proc typecheck(self: TypeChecker, node: ASTNode): TypedNode =
## Dispatches typeless AST nodes to typecheck them and turn
## them into typed ones
case node.kind:
@@ -1766,7 +1606,7 @@ proc check(self: TypeChecker, node: ASTNode): TypedNode =
result = self.expression(Expression(node))
of NodeKind.exprStmt:
let statement = ExprStmt(node)
result = TypedExprStmt(node: statement, expression: TypedExpr(self.check(statement.expression)))
result = TypedExprStmt(node: statement, expression: TypedExpr(self.typecheck(statement.expression)))
of NodeKind.whileStmt:
result = self.whileStmt(WhileStmt(node))
of NodeKind.blockStmt:
@@ -1789,7 +1629,7 @@ proc check(self: TypeChecker, node: ASTNode): TypedNode =
self.error(&"failed to dispatch node of type {node.kind}", node)
proc check*(self: TypeChecker, tree: ParseTree, file, source: string, showMismatches: bool = false,
proc typecheck*(self: TypeChecker, tree: ParseTree, file, source: string, showMismatches: bool = false,
disabledWarnings: seq[WarningKind] = @[]): seq[TypedNode] =
## Transforms a sequence of typeless AST nodes
## into a sequence of typed AST nodes
@@ -1831,7 +1671,7 @@ proc check*(self: TypeChecker, tree: ParseTree, file, source: string, showMismat
self.addName(main)
var node: TypedNode
while not self.done():
node = self.check(self.step())
node = self.typecheck(self.step())
if node.isNil():
continue
result.add(node)

View File

@@ -31,7 +31,6 @@ type
Boolean,
Any,
Typevar,
Auto,
Byte,
Char,
Structure,
@@ -68,12 +67,8 @@ type
positive*: bool
of Function:
isLambda*: bool
isGenerator*: bool
isCoroutine*: bool
isAuto*: bool
signature*: TypeSignature
returnType*: Type
unsafe*: bool
isBuiltin*: bool
builtinOp*: string
of Typevar:
@@ -81,13 +76,12 @@ type
of Structure:
name*: string
fields*: TableRef[string, Type]
parent*: Type
interfaces*: seq[Type]
isEnum*: bool
of Reference, Pointer, Lent, Const:
value*: Type
of Generic, Union:
types*: TypeCapabilities
constraints*: TypeCapabilities
displayName*: string
else:
discard
@@ -148,6 +142,9 @@ type
## A generic typed expression
kind*: Type
TypedGenericExpr* = ref object of TypedExpr
args*: seq[TypedExpr]
TypedUnaryExpr* = ref object of TypedExpr
## A generic typed unary expression
a*: TypedExpr
@@ -176,7 +173,6 @@ type
TypedTypeDecl* = ref object of TypedDecl
## A typed type declaration node
fields*: TableRef[string, TypedExpr]
parent*: Name
interfaces*: seq[TypedTypeDecl]
TypedEnumDecl* = ref object of TypedTypeDecl
@@ -223,13 +219,17 @@ proc newTypedExpr*(node: Expression, kind: Type): TypedExpr =
## Initializes a new typed expression
result = TypedExpr(node: node, kind: kind)
proc newTypedGenericExpr*(node: GenericExpr, kind: Type, args: seq[TypedExpr]): TypedGenericExpr =
## Initializes a new typed expression
result = TypedGenericExpr(node: node, kind: kind, args: args)
proc newTypedDecl*(node: Declaration, name: Name): TypedDecl =
## Initializes a new typed declaration
result = TypedDecl(node: node, name: name)
proc newTypedTypeDecl*(node: TypeDecl, name: Name, fields: TableRef[string, TypedExpr], parent: Name): TypedTypeDecl =
proc newTypedTypeDecl*(node: TypeDecl, name: Name, fields: TableRef[string, TypedExpr]): TypedTypeDecl =
## Initializes a new typed function declaration
result = TypedTypeDecl(node: node, name: name, fields: fields, parent: parent)
result = TypedTypeDecl(node: node, name: name, fields: fields)
proc newTypedEnumDecl*(node: TypeDecl, name: Name, variants: seq[TypedTypeDecl], enumeration: Type): TypedEnumDecl =
## Initializes a new typed function declaration

View File

@@ -190,8 +190,7 @@ type
parameters*: Parameters
returnType*: Expression
pragmas*: seq[Pragma]
genericTypes*: TypeGenerics
genericValues*: TypeGenerics
generics*: TypeGenerics
SliceExpr* = ref object of Expression
## A slice expression such as x[b]
@@ -290,10 +289,9 @@ type
members*: seq[TypeDecl]
isEnum*: bool
isRef*: bool # Is this type a managed reference?
parent*: Expression
# Only filled if it's a type alias
value*: Expression
Pragma* = ref object of Expression
name*: IdentExpr
args*: seq[LiteralExpr]
@@ -465,8 +463,7 @@ proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
proc newLambdaExpr*(parameters: Parameters = newOrderedTable[string, Parameter](),
body: Statement = nil, token: Token = nil, pragmas: seq[Pragma] = @[],
returnType: Expression = nil, genericTypes: TypeGenerics = newOrderedTable[string, TypeGeneric](),
genericValues: TypeGenerics = newOrderedTable[string, TypeGeneric]()): LambdaExpr =
returnType: Expression = nil, generics: TypeGenerics = newOrderedTable[string, TypeGeneric]()): LambdaExpr =
new(result)
result.kind = NodeKind.lambdaExpr
result.body = body
@@ -474,8 +471,7 @@ proc newLambdaExpr*(parameters: Parameters = newOrderedTable[string, Parameter](
result.token = token
result.returnType = returnType
result.pragmas = pragmas
result.genericTypes = genericTypes
result.genericValues = genericValues
result.generics = generics
proc newGetterExpr*(obj: Expression, name: IdentExpr, token: Token): GetterExpr =
@@ -666,7 +662,7 @@ proc newFunDecl*(name: IdentExpr, parameters: Parameters, body: Statement, isPri
result.generics = generics
proc newTypeDecl*(name: IdentExpr, fields: TypeFields, isPrivate: bool, token: Token, pragmas: seq[Pragma], parent: IdentExpr, isEnum: bool, isRef: bool,
proc newTypeDecl*(name: IdentExpr, fields: TypeFields, isPrivate: bool, token: Token, pragmas: seq[Pragma], isEnum: bool, isRef: bool,
generics: TypeGenerics = newOrderedTable[string, TypeGeneric]()): TypeDecl =
new(result)
result.kind = NodeKind.typeDecl
@@ -676,7 +672,6 @@ proc newTypeDecl*(name: IdentExpr, fields: TypeFields, isPrivate: bool, token: T
result.token = token
result.pragmas = pragmas
result.generics = generics
result.parent = parent
result.isEnum = isEnum
result.isRef = isRef
result.members = @[]
@@ -765,7 +760,7 @@ func `$`*(self: ASTNode): string =
result &= &"""FunDecl(name={self.name}, body={self.body}, returnType={self.returnType}, parameters={self.parameters}, generics={self.generics}, private={self.isPrivate}, pragmas={self.pragmas})"""
of typeDecl:
var self = TypeDecl(self)
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, members={self.members}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, parent={self.parent}, ref={self.isRef}, enum={self.isEnum}, value={self.value})"""
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, members={self.members}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, ref={self.isRef}, enum={self.isEnum}, value={self.value})"""
of lambdaExpr:
var self = LambdaExpr(self)
result &= &"""Lambda(body={self.body}, returnType={self.returnType}, parameters={self.parameters}, pragmas={self.pragmas})"""

View File

@@ -1197,7 +1197,7 @@ proc typeDecl(self: Parser): TypeDecl =
let token = self.peek(-1)
self.expect(Identifier, "expecting type name after 'type'")
var name = newIdentExpr(self.peek(-1))
result = newTypeDecl(name, newOrderedTable[string, TypeField](), true, token, @[], nil, false, false)
result = newTypeDecl(name, newOrderedTable[string, TypeField](), true, token, @[], false, false)
result.file = self.file
if self.check(["<", "["]):
self.parseGenerics(result)
@@ -1231,9 +1231,6 @@ proc typeDecl(self: Parser): TypeDecl =
result.file = self.file
else:
self.error("invalid syntax")
if not result.isEnum and self.match("of"):
# Type has a parent (and is not an enumeration)
result.parent = self.expression()
if not self.match(";"):
self.expect(LeftBrace, "expecting '{' after type declaration")
if self.match(TokenType.Pragma):
@@ -1244,7 +1241,7 @@ proc typeDecl(self: Parser): TypeDecl =
else:
var variant: TypeDecl
while not self.done():
variant = newTypeDecl(nil, nil, true, nil, @[], nil, false, false)
variant = newTypeDecl(nil, nil, true, nil, @[], false, false)
self.expect(Identifier, "expecting variant name")
variant.name = newIdentExpr(self.peek(-1))
variant.token = variant.name.token

View File

@@ -116,7 +116,7 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, gene
echo ""
if not typeCheck:
return
typedNodes = typeChecker.check(tree, filename, tokenizer.getSource(), mismatches, disabledWarnings)
typedNodes = typeChecker.typecheck(tree, filename, tokenizer.getSource(), mismatches, disabledWarnings)
if debugTypeChecker:
styledEcho fgCyan, "Typechecker output:"
for typedNode in typedNodes:

View File

@@ -15,4 +15,4 @@ type Test[T: typevar[int64], V: int64] = object {
val: V;
}
Test[int64, 1];