More fixes to generics

This commit is contained in:
Mattia Giambirtone 2023-12-06 15:25:28 +01:00
parent 34d5f77f65
commit 60d9b3c37e
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
4 changed files with 73 additions and 101 deletions

View File

@ -151,8 +151,7 @@ proc handleMagicPragma(self: TypeChecker, pragma: Pragma, name: Name) =
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.wrapType()
name.valueType.intrinsic = true
name.valueType = typ
else:
self.error("'magic' pragma is not valid in this context")
@ -176,7 +175,6 @@ proc handleWarnPragma(self: TypeChecker, pragma: Pragma, name: Name) =
self.error("'warn' pragma: wrong type of argument (constant string expected)")
self.warning(UserWarning, pragma.args[0].token.lexeme[1..^2])
proc handlePurePragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "pure" pragma
@ -289,51 +287,51 @@ proc toIntrinsic(name: string): Type =
## otherwise
case name:
of "any":
return Type(kind: Any)
return Type(kind: Any, intrinsic: true)
of "auto":
return Type(kind: Auto)
return Type(kind: Auto, intrinsic: true)
of "int64":
return Type(kind: Integer, size: LongLong, signed: true)
return Type(kind: Integer, size: LongLong, signed: true, intrinsic: true)
of "uint64":
return Type(kind: Integer, size: LongLong, signed: false)
return Type(kind: Integer, size: LongLong, signed: false, intrinsic: true)
of "int32":
return Type(kind: Integer, size: Long, signed: true)
return Type(kind: Integer, size: Long, signed: true, intrinsic: true)
of "uint32":
return Type(kind: Integer, size: Long, signed: false)
return Type(kind: Integer, size: Long, signed: false, intrinsic: true)
of "int16":
return Type(kind: Integer, size: Short, signed: true)
return Type(kind: Integer, size: Short, signed: true, intrinsic: true)
of "uint16":
return Type(kind: Integer, size: Short, signed: false)
return Type(kind: Integer, size: Short, signed: false, intrinsic: true)
of "int8":
return Type(kind: Integer, size: Tiny, signed: true)
return Type(kind: Integer, size: Tiny, signed: true, intrinsic: true)
of "uint8":
return Type(kind: Integer, size: Tiny, signed: false)
return Type(kind: Integer, size: Tiny, signed: false, intrinsic: true)
of "float64":
return Type(kind: Float, width: Full)
return Type(kind: Float, width: Full, intrinsic: true)
of "float32":
return Type(kind: Float, width: Half)
return Type(kind: Float, width: Half, intrinsic: true)
of "byte":
return Type(kind: Byte)
return Type(kind: Byte, intrinsic: true)
of "char":
return Type(kind: Char)
return Type(kind: Char, intrinsic: true)
of "NaN":
return Type(kind: TypeKind.Nan)
return Type(kind: TypeKind.Nan, intrinsic: true)
of "Inf":
return Type(kind: Infinity, positive: true)
return Type(kind: Infinity, positive: true, intrinsic: true)
of "NegInf":
return Type(kind: Infinity)
return Type(kind: Infinity, intrinsic: true)
of "bool":
return Type(kind: Boolean)
return Type(kind: Boolean, intrinsic: true)
of "string":
return Type(kind: String)
return Type(kind: String, intrinsic: true)
of "pointer":
return Type(kind: Pointer, value: "any".toIntrinsic())
return Type(kind: Pointer, value: "any".toIntrinsic(), intrinsic: true)
of "lent":
return Type(kind: TypeKind.Lent, value: "any".toIntrinsic())
return Type(kind: TypeKind.Lent, value: "any".toIntrinsic(), intrinsic: true)
of "const":
return Type(kind: TypeKind.Const, value: "any".toIntrinsic())
return Type(kind: TypeKind.Const, value: "any".toIntrinsic(), intrinsic: true)
of "ref":
return Type(kind: Reference, value: "any".toIntrinsic())
return Type(kind: Reference, value: "any".toIntrinsic(), intrinsic: true)
@ -436,6 +434,7 @@ proc compare(self: TypeChecker, a, b: Type): bool =
# have names)
if a.name.len() > 0 and b.name.len() > 0:
if a.name != b.name:
echo "fail: name"
return false
# Compare fields
var hashSet = initHashSet[string]()
@ -447,12 +446,15 @@ proc compare(self: TypeChecker, a, b: Type): bool =
# names
for field in hashSet:
if field notin a.fields:
echo &"fail: {field} notin a.fields"
return false
if field notin b.fields:
echo &"fail: {field} notin b.fields"
return false
# Ensure fields have matching types
for field in hashSet:
if not self.compare(a.fields[field], b.fields[field]):
echo &"fail: compare(a.{field}, b.{field})"
return false
hashSet.clear()
# Compare generic arguments
@ -485,9 +487,9 @@ proc compare(self: TypeChecker, a, b: Type): bool =
# Ensure both types have the same generic
# argument names
for generic in hashSet:
if generic notin a.genericTypes:
if generic notin a.genericValues:
return false
if generic notin b.genericTypes:
if generic notin b.genericValues:
return false
for generic in hashSet:
@ -526,7 +528,7 @@ proc compare(self: TypeChecker, a, b: Name): bool =
## exact object)
if not self.compare(a.valueType, b.valueType):
return false
return a.owner == b.owner
return a.module == b.module
proc literal(self: TypeChecker, node: LiteralExpr): TypedExpr =
@ -653,7 +655,7 @@ proc findAll(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()):
while depth >= 0:
if self.names[depth].hasKey(name):
for obj in self.names[depth][name]:
if obj.owner.absPath != self.currentModule.absPath:
if obj.module.absPath != self.currentModule.absPath:
# We don't own this name, but we
# may still have access to it
if obj.isPrivate or self.currentModule notin obj.exportedTo:
@ -786,6 +788,7 @@ proc stringify*(self: TypeChecker, typ: Type): string =
proc stringify*(self: TypeChecker, typ: TypedNode): string =
if typ.node.isConst():
return self.stringify(TypedExpr(typ).kind)
case typ.node.kind:
@ -796,7 +799,7 @@ proc stringify*(self: TypeChecker, typ: TypedNode): string =
result = self.stringify(TypedExpr(typ).kind)
else:
# TODO
return "void"
return &"?: {typ[]}"
proc beginScope(self: TypeChecker) =
@ -1085,24 +1088,23 @@ proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNo
var replaced = newTable[string, Type]()
var i = 0
for key in typ.genericTypes.keys():
var term = args[i].kind
# Type may not be wrapped yet
if args[i].kind.isType():
term = term.wrapType()
result.genericTypes[key] = self.check(term, typ.genericTypes[key], args[i].node)
result.genericTypes[key] = self.check(args[i].kind, typ.genericTypes[key], args[i].node)
replaced[key] = result.genericTypes[key]
inc(i)
# Note how we do not reset i!
for key in typ.genericValues.keys():
var term = args[i].kind
result.genericValues[key] = self.check(term, typ.genericValues[key], args[i].node)
# echo args[i].kind, "\n"
# echo typ.genericValues[key], "\n\n"
result.genericValues[key] = self.check(args[i].kind, typ.genericValues[key], args[i].node)
replaced[key] = result.genericValues[key]
inc(i)
for field in TypeDecl(name.node).fields:
if field.valueType.kind == identExpr:
let name = field.valueType.token.lexeme
if name in replaced:
result.fields[name] = replaced[name]
let
fieldName = field.name.token.lexeme
fieldType = field.valueType.token.lexeme
if fieldType in replaced:
result.fields[fieldName] = replaced[fieldType]
else:
self.error(&"cannot create concrete instance of objects of type {self.stringify(typ)}")
@ -1182,8 +1184,8 @@ proc addName(self: TypeChecker, name: Name) =
if name.valueType.kind == TypeKind.Function:
# We don't check for name clashes for functions because self.match() does that
continue
if (obj.kind in [NameKind.Var, NameKind.Module] or obj.valueType.kind in [TypeKind.Structure, TypeKind.EnumEntry]) and name.owner == obj.owner:
self.error(&"re-declaration of '{obj.ident.token.lexeme}' is not allowed (previously declared in {obj.owner.ident.token.lexeme}:{obj.ident.token.line}:{obj.ident.token.relPos.start})", name.node)
if (obj.kind in [NameKind.Var, NameKind.Module] or obj.valueType.kind in [TypeKind.Structure, TypeKind.EnumEntry]) and name.module == obj.module:
self.error(&"re-declaration of '{obj.ident.token.lexeme}' is not allowed (previously declared in {obj.module.ident.token.lexeme}:{obj.ident.token.line}:{obj.ident.token.relPos.start})", name.node)
else:
scope[name.ident.token.lexeme] = @[]
scope[name.ident.token.lexeme].add(name)
@ -1378,42 +1380,6 @@ proc lentExpr(self: TypeChecker, node: ast.Lent): TypedExpr =
# Wrap the result back
result.kind = result.kind.toLent()
#[
method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
## Typechecks assignment expressions
case node.kind:
of assignExpr:
let node = AssignExpr(node)
let name = IdentExpr(node.name)
var r = self.resolveOrError(name)
if r.constant:
self.error(&"cannot assign to '{name.token.lexeme}' (value is a constant)", name)
elif r.mutable:
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
self.check(node.value, r.valueType)
self.expression(node.value, compile)
var position = r.position
if r.depth < self.depth and r.belongsTo != self.currentFunction:
self.warning(WarningKind.MutateOuterScope, &"mutation of '{r.ident.token.lexeme}' declared in outer scope ({r.owner.file}.pn:{r.ident.token.line}:{r.ident.token.relPos.start})", nil, node)
result = r.valueType
if not compile:
return
self.emitByte(StoreVar, node.token.line)
self.emitBytes(position.toTriple(), node.token.line)
of setItemExpr:
let node = SetItemExpr(node)
let name = IdentExpr(node.name)
var r = self.resolveOrError(name)
if r.constant:
self.error(&"cannot assign to '{name.token.lexeme}' (value is a constant)", name)
elif r.mutable:
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
if r.valueType.kind != Structure:
self.error("only types have fields", node)
else:
self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)")
]#
proc expression(self: TypeChecker, node: Expression): TypedExpr =
## Typechecks expressions
@ -1579,7 +1545,7 @@ proc declareGenerics(self: TypeChecker, name: Name) =
file: self.currentModule.file,
depth: self.scopeDepth,
isPrivate: true,
valueType: Type(kind: Union, types: constraints),
valueType: if constraints.len() > 1: Type(kind: Union, types: constraints) else: constraints[0].kind,
line: gen.name.token.line,
)
self.addName(generic)
@ -1598,7 +1564,7 @@ proc declareGenerics(self: TypeChecker, name: Name) =
file: self.currentModule.file,
depth: self.scopeDepth,
isPrivate: true,
valueType: Type(kind: Union, types: constraints).unwrapType(),
valueType: (if constraints.len() > 1: Type(kind: Union, types: constraints) else: constraints[0].kind).unwrapType(),
line: gen.name.token.line,
)
self.addName(generic)
@ -1678,8 +1644,7 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
result.fields[field.name.token.lexeme] = newTypedExpr(field.name, result.parent.valueType.fields[field.name.token.lexeme])
# Turn the declared type into a typevar so that future references
# to it will be distinct from its instances
if not name.valueType.intrinsic:
name.valueType = name.valueType.wrapType()
name.valueType = name.valueType.wrapType()
# TODO: Check interfaces
self.endScope()

View File

@ -1025,18 +1025,19 @@ proc parseGenericConstraint(self: Parser, endToken: TokenType or string): Expres
proc parseGenerics(self: Parser, decl: Declaration) =
## Parses generics in declarations
var gen: tuple[name: IdentExpr, cond: Expression]
while not self.check(">") and not self.done():
self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
gen.name.file = self.file
if self.match(":"):
gen.cond = self.parseGenericConstraint(">")
else:
gen.cond = nil
decl.genericTypes.add(gen)
if not self.match(Comma):
break
self.expect(">", "unterminated generic declaration")
if self.match("<"):
while not self.check(">") and not self.done():
self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
gen.name.file = self.file
if self.match(":"):
gen.cond = self.parseGenericConstraint(">")
else:
gen.cond = nil
decl.genericTypes.add(gen)
if not self.match(Comma):
break
self.expect(">", "unterminated generic declaration")
if self.match(LeftBracket):
while not self.check(RightBracket) and not self.done():
self.expect(Identifier, "expecting generic type name")
@ -1078,7 +1079,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
if self.match("*"):
FunDecl(self.currentFunction).isPrivate = false
self.checkDecl(FunDecl(self.currentFunction).isPrivate)
if self.match("<"):
if self.check(["<", "["]):
self.parseGenerics(self.currentFunction)
elif not isLambda and (self.check([LeftBrace, LeftParen]) or self.check(":")):
# We do a bit of hacking to pretend we never
@ -1276,7 +1277,7 @@ proc typeDecl(self: Parser): TypeDecl =
var name = newIdentExpr(self.peek(-1), self.scopeDepth)
result = newTypeDecl(name, @[], @[], true, token, @[], @[], @[], nil, false, false)
result.file = self.file
if self.match("<"):
if self.check(["<", "["]):
self.parseGenerics(result)
result.isPrivate = not self.match("*")
self.checkDecl(result.isPrivate)
@ -1321,7 +1322,7 @@ proc typeDecl(self: Parser): TypeDecl =
self.expect(Identifier, "expecting variant name")
variant.name = newIdentExpr(self.peek(-1))
variant.token = variant.name.token
if self.match(LeftBracket):
if self.check(["[", "<"]):
self.parseGenerics(variant)
if self.match("{"):
variant.fields = self.parseTypeFields()

View File

@ -184,6 +184,7 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
print(exc)
except TypeCheckError as exc:
print(exc)
raise
except CodeGenError as exc:
var file = exc.file
if file notin ["<string>", ""]:

View File

@ -8,8 +8,13 @@ type Test<T: int64>[V: int64] = object {
value: V;
}
type Test2<T: Test>[V: Test] = object {
value: V;
}
Test[int64, 1]; # Works
# Test[int64, int64]; # Error: expecting an expression of type int64, got typevar[int64] instead
# Test[1, int64]; # Error: expecting an expression of type typevar[int64], got int64 instead
Test[int64, 1]; # Works
# Test[int64, int64]; # Error: expecting an expression of type int64, got typevar[int64] instead
# Test[1, int64]; # Error: expecting an expression of type typevar[int64], got int64 instead
Test2[Test, Test[int64, 1]]; # Also works