Completely rework generics

This commit is contained in:
Mattia Giambirtone 2023-12-05 12:48:41 +01:00
parent 838fc3d5a1
commit a6a944a4fa
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
6 changed files with 206 additions and 87 deletions

View File

@ -1,2 +1,2 @@
--hints:off --deepCopy:on --experimental:strictFuncs
--hints:off --deepCopy:on --experimental:strictFuncs --exceptions:setjmp
path="src"

View File

@ -445,7 +445,7 @@ proc generate*(self: BytecodeGenerator, compiled: seq[TypedNode], typeChecker: T
self.currentNode = typedNode
let currentFile = self.currentFile
if self.currentNode.node.isDecl():
self.currentFile = TypedDecl(typedNode).name.owner.ident.token.lexeme
self.currentFile = TypedDecl(typedNode).name.module.ident.token.lexeme
case typedNode.node.kind:
of exprStmt:
self.generateExpression(TypedExprStmt(typedNode).expression)

View File

@ -63,12 +63,12 @@ type
proc validate(self: TypeChecker, node: ASTNode): TypedNode
proc toIntrinsic(name: string): Type
proc done(self: TypeChecker): bool {.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.}
proc toLent(self: Type): Type {.inline.}
proc wrapType(self: Type): Type {.inline.}
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)
@ -80,7 +80,7 @@ proc stringify*(self: TypeChecker, typ: TypedNode): string
proc inferOrError*(self: TypeChecker, node: Expression): TypedExpr
proc compare(self: TypeChecker, a, b: Type): bool
proc expression(self: TypeChecker, node: Expression): TypedExpr
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr]): Type
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNode = nil): Type
proc declareGenerics(self: TypeChecker, name: Name)
proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl
proc getTypeDistance(self: TypeChecker, a, b: Type): int
@ -151,6 +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
else:
self.error("'magic' pragma is not valid in this context")
@ -190,7 +191,7 @@ 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.safe = false
name.valueType.unsafe = true
proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
@ -207,8 +208,8 @@ proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name =
node = name.node
if node.isNil():
node = self.getCurrentNode()
if not name.belongsTo.isNil():
fn = name.belongsTo.node
if not name.owner.isNil():
fn = name.owner.node
else:
fn = self.getCurrentFunction()
var file = self.file
@ -237,7 +238,13 @@ proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name =
proc wrapType(self: Type): Type {.inline.} =
## Wraps a type in a typevar
result = Type(kind: Typevar, wrapped: self)
case self.kind:
of Union:
result = Type(kind: Union, types: @[])
for typ in self.types:
result.types.add((match: typ.match, kind: typ.kind.wrapType(), value: typ.value))
else:
result = Type(kind: Typevar, wrapped: self)
proc unwrapType(self: Type): Type {.inline.} =
@ -246,6 +253,10 @@ proc unwrapType(self: Type): Type {.inline.} =
case self.kind:
of Typevar:
result = self.wrapped
of Union:
result = Type(kind: Union, types: @[])
for typ in self.types:
result.types.add((match: typ.match, kind: typ.kind.unwrapType(), value: typ.value))
else:
result = self
@ -313,8 +324,6 @@ proc toIntrinsic(name: string): Type =
return Type(kind: Infinity)
of "bool":
return Type(kind: Boolean)
of "typevar":
return Type(kind: Typevar, wrapped: "any".toIntrinsic())
of "string":
return Type(kind: String)
of "pointer":
@ -392,17 +401,12 @@ proc matchUnion(self: TypeChecker, a: Type, b: seq[tuple[match: bool, kind: Type
proc matchGeneric(self: TypeChecker, a: Type, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
## Returns whether a concrete type matches the
## given generic type b
assert a.kind != Generic
for constraint in b:
if not self.compare(constraint.kind, a) or not constraint.match:
return false
return true
proc isType(typ: Type): bool =
return typ.kind in [Structure, Typevar]
proc isAny(typ: Type): bool =
## Returns true if the given type is
## of (or contains) the any type. Not
@ -419,16 +423,77 @@ proc isAny(typ: Type): bool =
proc compare(self: TypeChecker, a, b: Type): bool =
if a.isNil() or b.isNil():
raise newException(NilAccessDefect, "what the fuck are you doing")
if a.isAny() or b.isAny():
return true
if a.kind == b.kind:
case a.kind:
of Typevar:
return self.compare(a.wrapped, b.wrapped)
# TODO: Take interfaces into account
of Structure:
# TODO
return false
# Compare type names, if they both have it
# (some internally generated types may not
# have names)
if a.name.len() > 0 and b.name.len() > 0:
if a.name != b.name:
return false
# Compare fields
var hashSet = initHashSet[string]()
for field in a.fields.keys():
hashSet.incl(field)
for field in b.fields.keys():
hashSet.incl(field)
# Ensure both types have the same field
# names
for field in hashSet:
if field notin a.fields:
return false
if 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]):
return false
hashSet.clear()
# Compare generic arguments
# Check generic types
for generic in a.genericTypes.keys():
hashSet.incl(generic)
for generic in b.genericTypes.keys():
hashSet.incl(generic)
# Ensure both types have the same generic
# argument names
for generic in hashSet:
if generic notin a.genericTypes:
return false
if generic notin b.genericTypes:
return false
for generic in hashSet:
if not self.compare(a.genericTypes[generic], b.genericTypes[generic]):
return false
hashSet.clear()
# Check generic values
for generic in a.genericValues.keys():
hashSet.incl(generic)
for generic in b.genericValues.keys():
hashSet.incl(generic)
# Ensure both types have the same generic
# argument names
for generic in hashSet:
if generic notin a.genericTypes:
return false
if generic notin b.genericTypes:
return false
for generic in hashSet:
if not self.compare(a.genericValues[generic], b.genericValues[generic]):
return false
return true
of Boolean, Infinity, Any,
Auto, Char, Byte, String:
return true
@ -440,8 +505,6 @@ proc compare(self: TypeChecker, a, b: Type): bool =
return self.compare(a.value, b.value)
of Union:
return self.compareUnions(a.types, b.types)
of Typevar:
return self.compare(a.wrapped, b.wrapped)
of Function:
# TODO
return false
@ -455,7 +518,7 @@ proc compare(self: TypeChecker, a, b: Type): bool =
return false
proc compare*(self: TypeChecker, a, b: Name): bool =
proc compare(self: TypeChecker, a, b: Name): bool =
## Compares two names. In addition to checking
## that their types match, this function makes
## sure the two given objects actually come from
@ -551,7 +614,7 @@ proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Na
while depth >= 0:
if self.names[depth].hasKey(name):
for obj in reversed(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:
@ -570,7 +633,6 @@ proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Na
if self.compare(obj.valueType, kind):
result = obj
break
echo "DIOSTRONZO"
if not result.isNil():
break
@ -599,7 +661,7 @@ proc findAll(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()):
# module, so we definitely can't use it, or
# said module has not explicitly exported it
# to us. If the name is public but not exported
# in its owner module, then we act as if it's
# from its owner module, then we act as if it's
# private. This is to avoid namespace pollution
# from imports (i.e. if module A imports modules
# C and D and module B imports module A, then B
@ -665,8 +727,6 @@ proc stringify*(self: TypeChecker, typ: Type): string =
result &= "32"
of Full:
result &= "64"
of Typevar:
result = &"typevar[{self.stringify(typ.wrapped)}]"
of Pointer:
result &= &"ptr {self.stringify(typ.value)}"
of Reference:
@ -686,11 +746,7 @@ proc stringify*(self: TypeChecker, typ: Type): string =
result &= "]"
result &= "("
for i, (argName, argType, argDefault) in typ.parameters:
result &= &"{argName}: "
if argType.kind == Generic:
result &= argType.name
else:
result &= self.stringify(argType)
result &= &"{argName}: {self.stringify(argType)}"
if not argDefault.isNil():
result &= &" = {argDefault.kind}"
if i < typ.parameters.len() - 1:
@ -723,6 +779,8 @@ proc stringify*(self: TypeChecker, typ: Type): string =
if not condition.match:
result &= "~"
result &= self.stringify(condition.kind)
of Typevar:
result &= &"typevar[{self.stringify(typ.wrapped)}]"
else:
discard # TODO(?)
@ -755,12 +813,17 @@ proc endScope(self: TypeChecker) =
assert self.scopeDepth == self.names.high()
func isType(self: Type): bool = self.kind in [Structure, ]
proc isTypevar(self: Type): bool =
return self.unwrapType() != self
proc check(self: TypeChecker, term, expected: Type, node: ASTNode = nil): Type {.inline, discardable.} =
## Like the other check(), but works with two type objects.
## The node is passed in to error() in case of a failure
result = term
if not self.compare(result, expected):
self.error(&"expecting expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", node=node)
self.error(&"expecting an expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", node=node)
if result.isAny() and not expected.isAny():
self.error("any is not a valid type in this context", node)
@ -770,10 +833,7 @@ proc check(self: TypeChecker, term: Expression, expected: Type): TypedExpr {.inl
## Raises an error if appropriate and returns the typed expression
## otherwise
result = self.inferOrError(term)
if not self.compare(result.kind, expected):
self.error(&"expecting expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", term)
if result.kind.isAny() and not expected.isAny():
self.error("any is not a valid type in this context", term)
self.check(result.kind, expected)
proc getTypeDistance(self: TypeChecker, a, b: Type): int =
@ -874,8 +934,9 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
## Tries to find a matching type for a given (typeName, typeSignature) pair
## and returns it. In this context, "type signature" means an ordered list of
## tuples (paramName, paramType, paramDefault) that represents the arguments we
## want to instantiate a given (named) object with, be it a function or a type.
## The optional node parameter is passed to error() for reporting purposes
## want to instantiate a given named object with, be it a function, a type or an
## enumeration. The optional node parameter is passed to error() for reporting purposes
## in case of failure
var
impl: seq[Name] = @[]
matches: seq[Name] = @[]
@ -931,14 +992,15 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
# 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) and
# larger values indicate "less precise" ones
# 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" (or "further") ones
for i, n in impl:
# Grab all the matches with the smallest type distance
if distances[i] == minDst:
matches.add(n)
case matches.len():
of 1:
# Match found, all good!
# There's just one match. We're done
result = impl[0]
if result.kind == NameKind.Var:
# Variables bound to other names must always
@ -959,7 +1021,7 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
if not a.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
else:
# TODO
# TODO: Enums
discard
else:
# We either found no matches or too many, woopsie daisy! That's definitely an error
@ -1004,25 +1066,52 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
self.error(msg, node)
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr]): Type =
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNode = nil): Type =
## Instantiates a generic type
let typ = name.valueType
let
typ = name.valueType.unwrapType()
expectedCount = typ.genericTypes.len() + typ.genericValues.len()
if expectedCount == 0:
self.error(&"cannot create concrete instance of objects of type {self.stringify(typ)} (type is not a generic)")
if len(args) < expectedCount:
self.error(&"partial generic instantiation is not supported (expecting exactly {expectedCount} arguments, got {len(args)} instead)", node=node)
elif len(args) != expectedCount:
self.error(&"invalid number of arguments supplied for generic instantiation (expecting exactly {expectedCount}, got {len(args)} instead)", node=node)
case typ.kind:
of TypeKind.Structure:
discard
result = typ.deepCopy()
result.genericTypes.clear()
result.genericValues.clear()
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)
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)
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]
else:
self.error(&"cannot instantiate concrete type for object of type {self.stringify(name.valueType)}")
self.error(&"cannot create concrete instance of objects of type {self.stringify(typ)}")
proc unpackTypes(self: TypeChecker, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true, expectTypes: bool = false) =
proc unpackTypes(self: TypeChecker, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true) =
## Recursively unpacks a type constraint
case condition.kind:
of identExpr, genericExpr:
var typ = self.inferOrError(condition).kind
if expectTypes:
if not typ.isType():
self.error(&"expecting a type, got a value of type {self.stringify(typ)} instead", condition)
typ = typ.unwrapType()
if self.compare(typ, "auto".toIntrinsic()):
self.error("automatic types cannot be used within type constraints", condition)
@ -1113,11 +1202,11 @@ proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
name = Name(depth: self.scopeDepth,
ident: node.name,
isPrivate: node.isPrivate,
owner: self.currentModule,
module: self.currentModule,
file: self.file,
valueType: nil, # Done later in varDecl (for better semantics)
line: node.token.line,
belongsTo: self.currentFunction,
owner: self.currentFunction,
kind: NameKind.Var,
node: node,
)
@ -1138,16 +1227,15 @@ proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
isEnum: node.isEnum)
if node.isRef:
kind = kind.toRef()
self.addName(Name(depth: self.scopeDepth,
owner: self.currentModule,
node: node,
ident: node.name,
line: node.name.token.line,
isPrivate: node.isPrivate,
belongsTo: self.currentFunction,
valueType: kind,
))
name = scope[declaredName][^1]
name = Name(depth: self.scopeDepth,
module: self.currentModule,
node: node,
ident: node.name,
line: node.name.token.line,
isPrivate: node.isPrivate,
owner: self.currentFunction,
valueType: kind)
self.addName(name)
else:
discard # TODO: enums
if not name.isNil():
@ -1190,7 +1278,7 @@ proc genericExpr(self: TypeChecker, node: GenericExpr): TypedExpr =
var args: seq[TypedExpr] = @[]
for arg in node.args:
args.add(self.expression(arg))
result = newTypedExpr(node, self.specialize(self.findOrError(node.ident.token.lexeme), args))
result = newTypedExpr(node, self.specialize(self.findOrError(node.ident.token.lexeme), args, node))
proc call(self: TypeChecker, node: CallExpr): TypedExpr =
@ -1272,27 +1360,23 @@ proc refExpr(self: TypeChecker, node: Ref): TypedExpr =
## Typechecks ref expressions
result = self.check(node.value, "typevar".toIntrinsic())
result.kind = result.kind.toRef()
result.kind = result.kind.wrapType()
proc constExpr(self: TypeChecker, node: ast.Const): TypedExpr =
## Typechecks const expressions
var kind = "typevar".toIntrinsic()
kind.wrapped = kind.wrapped.toConst()
var kind = "any".toIntrinsic().toConst()
result = self.check(node.value, kind)
result.kind = result.kind.toConst()
result.kind = result.kind.wrapType()
proc lentExpr(self: TypeChecker, node: ast.Lent): TypedExpr =
## Typechecks lent expressions
var kind = "typevar".toIntrinsic()
# Only match references
kind.wrapped = kind.wrapped.toRef()
var kind = "any".toIntrinsic().toRef()
result = self.check(node.value, kind)
# Wrap the result back
result.kind = result.kind.toLent()
result.kind = result.kind.wrapType()
#[
method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
@ -1353,7 +1437,6 @@ proc expression(self: TypeChecker, node: Expression): TypedExpr =
of ptrExpr:
result = self.check(Ref(node).value, "typevar".toIntrinsic())
result.kind = result.kind.toPtr()
result.kind = result.kind.wrapType()
of NodeKind.lentExpr:
result = self.lentExpr(ast.Lent(node))
of NodeKind.constExpr:
@ -1453,8 +1536,8 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
if not node.returnType.isNil():
# 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.unwrapType()
# TODO
# name.valueType.returnType = self.check(node.returnType, "typevar".toIntrinsic()).kind
if not name.valueType.isAuto and self.compare(name.valueType.returnType, "auto".toIntrinsic()):
name.valueType.isAuto = true
if node.body.isNil():
@ -1479,7 +1562,7 @@ 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 != TypeKind.Structure:
if name.valueType.kind notin [TypeKind.Structure, TypeKind.Function]:
return
var
constraints: seq[tuple[match: bool, kind: Type, value: Expression]] = @[]
@ -1488,8 +1571,40 @@ proc declareGenerics(self: TypeChecker, name: Name) =
if gen.cond.isNil():
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
else:
self.unpackTypes(gen.cond, constraints, expectTypes=true)
# TODO
self.unpackTypes(gen.cond, constraints)
let generic = Name(kind: Default,
ident: gen.name,
module: self.currentModule,
owner: self.currentFunction,
file: self.currentModule.file,
depth: self.scopeDepth,
isPrivate: true,
valueType: Type(kind: Union, types: constraints),
line: gen.name.token.line,
)
self.addName(generic)
name.valueType.genericTypes[gen.name.token.lexeme] = generic.valueType
constraints.setLen(0)
for gen in name.node.genericValues:
if gen.cond.isNil():
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
else:
self.unpackTypes(gen.cond, constraints)
let generic = Name(kind: Default,
ident: gen.name,
module: self.currentModule,
owner: self.currentFunction,
file: self.currentModule.file,
depth: self.scopeDepth,
isPrivate: true,
valueType: Type(kind: Union, types: constraints).unwrapType(),
line: gen.name.token.line,
)
self.addName(generic)
name.valueType.genericValues[gen.name.token.lexeme] = generic.valueType
constraints.setLen(0)
proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDecl =
@ -1561,7 +1676,10 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
# This always eventually runs
self.error(&"cannot to re-declare type member '{field}'", f.name)
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()
# TODO: Check interfaces
self.endScope()

View File

@ -39,7 +39,6 @@ type
EnumEntry,
Reference,
Pointer,
Generic,
Union,
Function,
Lent,
@ -74,7 +73,7 @@ type
isAuto*: bool
parameters*: TypeSignature
returnType*: Type
safe*: bool
unsafe*: bool
of Typevar:
wrapped*: Type
of Structure:
@ -124,7 +123,7 @@ type
# The name's identifier
ident*: IdentExpr
# Owner of the identifier (module)
owner*: Name
module*: Name
# File where the name is declared
file*: string
# Scope depth
@ -134,8 +133,8 @@ type
# The type of the name's associated
# value
valueType*: Type
# The function that owns this name (may be nil!)
belongsTo*: Name
# The function that "owns" this name (may be nil!)
owner*: Name
# Where is this node declared in its file?
line*: int
# The AST node associated with this node. This

View File

@ -855,9 +855,11 @@ proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] =
of genericExpr:
var self = GenericExpr(self)
let ident = getRelativeBoundaries(self.ident)
var stop: int = ident.stop + 2
var stop: int = ident.stop
if self.args.len() > 0:
stop = getRelativeBoundaries(self.args[^1]).stop
# Take the "]" into account
inc(stop)
result = (ident.start, stop)
of refExpr:
var self = Ref(self)

View File

@ -118,7 +118,7 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
# *Technically* an expression statement has no type, but that isn't really useful for debug
# purposes, so we print the type of the expression within it instead
let exprNode = TypedExprStmt(typedNode).expression
styledEcho fgGreen, &"\t{typedNode.node} -> {exprNode.node} -> {typeChecker.stringify(TypedExprStmt(typedNode).expression)}\n"
styledEcho fgGreen, &"\t{typedNode.node} (inner) -> {typeChecker.stringify(exprNode.kind)}\n"
else:
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
case backend: