From a6a944a4fa027b82d6e1166ee481ca2f20b48600 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Tue, 5 Dec 2023 12:48:41 +0100 Subject: [PATCH] Completely rework generics --- nim.cfg | 2 +- src/backend/bytecode/codegen/generator.nim | 2 +- src/frontend/compiler/typechecker.nim | 274 +++++++++++++++------ src/frontend/compiler/typesystem.nim | 9 +- src/frontend/parser/ast.nim | 4 +- src/main.nim | 2 +- 6 files changed, 206 insertions(+), 87 deletions(-) diff --git a/nim.cfg b/nim.cfg index 5f92ac5..63cab75 100644 --- a/nim.cfg +++ b/nim.cfg @@ -1,2 +1,2 @@ ---hints:off --deepCopy:on --experimental:strictFuncs +--hints:off --deepCopy:on --experimental:strictFuncs --exceptions:setjmp path="src" \ No newline at end of file diff --git a/src/backend/bytecode/codegen/generator.nim b/src/backend/bytecode/codegen/generator.nim index 464fdec..974eda8 100644 --- a/src/backend/bytecode/codegen/generator.nim +++ b/src/backend/bytecode/codegen/generator.nim @@ -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) diff --git a/src/frontend/compiler/typechecker.nim b/src/frontend/compiler/typechecker.nim index 0ee6946..ee4de34 100644 --- a/src/frontend/compiler/typechecker.nim +++ b/src/frontend/compiler/typechecker.nim @@ -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() diff --git a/src/frontend/compiler/typesystem.nim b/src/frontend/compiler/typesystem.nim index e4aa26b..47129c1 100644 --- a/src/frontend/compiler/typesystem.nim +++ b/src/frontend/compiler/typesystem.nim @@ -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 diff --git a/src/frontend/parser/ast.nim b/src/frontend/parser/ast.nim index 9ae12e4..f05baa1 100644 --- a/src/frontend/parser/ast.nim +++ b/src/frontend/parser/ast.nim @@ -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) diff --git a/src/main.nim b/src/main.nim index a81d26c..0f8d941 100644 --- a/src/main.nim +++ b/src/main.nim @@ -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: