typechecker/post-type-checking-verifier.spice
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | // Std imports | ||
| 4 | import "std/type/any"; | ||
| 5 | |||
| 6 | // Own imports | ||
| 7 | import "@/compiler-pass"; | ||
| 8 | import "@/ast/abstract-ast-visitor-intf"; | ||
| 9 | import "@/ast/ast-nodes"; | ||
| 10 | import "@/global/global-resource-manager"; | ||
| 11 | import "@/model/function"; | ||
| 12 | import "@/source-file"; | ||
| 13 | import "@/symboltablebuilder/scope"; | ||
| 14 | import "@/symboltablebuilder/symbol-table-entry"; | ||
| 15 | import "@/typechecker/builtin-functions"; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Verifies that the TypeChecker has fully annotated the AST before codegen begins. | ||
| 19 | * | ||
| 20 | * Runs only in debug builds (guarded by the spice.is_debug build var in source-file.spice). Asserts | ||
| 21 | * that every field the IRGenerator reads — entry pointers, per-manifestation vectors, | ||
| 22 | * FctCallData::callee — is non-nil and correctly sized. A failure here means a TC | ||
| 23 | * regression left a gap in the semantic model, which would otherwise surface as a | ||
| 24 | * cryptic nil-dereference inside the IRGenerator. | ||
| 25 | */ | ||
| 26 | public type PostTypeCheckingVerifier struct : IAbstractAstVisitor { | ||
| 27 | compose public CompilerPass compilerPass | ||
| 28 | } | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Construct a verifier for the given source file | ||
| 32 | * | ||
| 33 | * @param resourceManager Global resource manager | ||
| 34 | * @param sourceFile Source file, whose AST is verified | ||
| 35 | */ | ||
| 36 | 3696 | public p PostTypeCheckingVerifier.ctor(GlobalResourceManager& resourceManager, SourceFile* sourceFile) { | |
| 37 | 3696 | this.compilerPass = CompilerPass(resourceManager, sourceFile); | |
| 38 | 3696 | } | |
| 39 | |||
| 40 | /** | ||
| 41 | * Verify that the type checker fully annotated the given AST. Fails an assertion otherwise. | ||
| 42 | * | ||
| 43 | * @param ast Root node of the AST to verify | ||
| 44 | */ | ||
| 45 | 3696 | public p PostTypeCheckingVerifier.verify(ASTEntryNode* ast) { | |
| 46 | 3696 | this.visit(ast); | |
| 47 | 3696 | } | |
| 48 | |||
| 49 | // ======================================================== Verification ======================================================== | ||
| 50 | |||
| 51 | 28513 | public f<Any> PostTypeCheckingVerifier.visitFctDef(ASTFctDefNode* node) { | |
| 52 | 28513 | assert node.node.entry != nil<SymbolTableEntry*>; | |
| 53 | 28513 | assert !node.node.manifestations.isEmpty(); | |
| 54 | 28513 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 55 | } | ||
| 56 | |||
| 57 | 17644 | public f<Any> PostTypeCheckingVerifier.visitProcDef(ASTProcDefNode* node) { | |
| 58 | 17644 | assert node.node.entry != nil<SymbolTableEntry*>; | |
| 59 | 17644 | assert !node.node.manifestations.isEmpty(); | |
| 60 | 17644 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 61 | } | ||
| 62 | |||
| 63 | 3136 | public f<Any> PostTypeCheckingVerifier.visitStructDef(ASTStructDefNode* node) { | |
| 64 | 3136 | assert node.entry != nil<SymbolTableEntry*>; | |
| 65 | 3136 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 66 | } | ||
| 67 | |||
| 68 | 297 | public f<Any> PostTypeCheckingVerifier.visitInterfaceDef(ASTInterfaceDefNode* node) { | |
| 69 | 297 | assert node.entry != nil<SymbolTableEntry*>; | |
| 70 | 297 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 71 | } | ||
| 72 | |||
| 73 | 17 | public f<Any> PostTypeCheckingVerifier.visitUnionDef(ASTUnionDefNode* node) { | |
| 74 | 17 | assert node.entry != nil<SymbolTableEntry*>; | |
| 75 | 17 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 76 | } | ||
| 77 | |||
| 78 | 443 | public f<Any> PostTypeCheckingVerifier.visitEnumDef(ASTEnumDefNode* node) { | |
| 79 | 443 | assert node.entry != nil<SymbolTableEntry*>; | |
| 80 | 443 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 81 | } | ||
| 82 | |||
| 83 | 153 | public f<Any> PostTypeCheckingVerifier.visitAliasDef(ASTAliasDefNode* node) { | |
| 84 | 153 | assert node.entry != nil<SymbolTableEntry*>; | |
| 85 | 153 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 86 | } | ||
| 87 | |||
| 88 | 4259 | public f<Any> PostTypeCheckingVerifier.visitGlobalVarDef(ASTGlobalVarDefNode* node) { | |
| 89 | 4259 | assert node.entry != nil<SymbolTableEntry*>; | |
| 90 | 4259 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 91 | } | ||
| 92 | |||
| 93 | 699 | public f<Any> PostTypeCheckingVerifier.visitSignature(ASTSignatureNode* node) { | |
| 94 | 699 | assert node.entry != nil<SymbolTableEntry*>; | |
| 95 | 699 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 96 | } | ||
| 97 | |||
| 98 | 83700 | public f<Any> PostTypeCheckingVerifier.visitDeclStmt(ASTDeclStmtNode* node) { | |
| 99 | // entries is resized to manifestationCount; unsubstantiated generic slots remain nil. | ||
| 100 | // We can only assert the vector was populated at all. | ||
| 101 | 83700 | if !node.isForEachItem { | |
| 102 | 83269 | assert !node.entries.isEmpty(); | |
| 103 | 83269 | } | |
| 104 | 83700 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 105 | } | ||
| 106 | |||
| 107 | 398591 | public f<Any> PostTypeCheckingVerifier.visitAtomicExpr(ASTAtomicExprNode* node) { | |
| 108 | // data is only populated for identifier accesses (not for constants / nested exprs). | ||
| 109 | // Within identifier accesses, only substantiated manifestation slots have accessScope set; | ||
| 110 | // unsubstantiated generic slots are left at the default-initialized nil. | ||
| 111 | 398591 | if !node.fqIdentifier.isEmpty() { | |
| 112 | 229958 | assert !node.data.isEmpty(); | |
| 113 | 556856 | foreach const VarAccessData& d : node.data { | |
| 114 | 326898 | assert d.accessScope == nil<Scope*> || d.entry != nil<SymbolTableEntry*>; | |
| 115 | 326898 | } | |
| 116 | 229958 | } | |
| 117 | 398591 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 118 | } | ||
| 119 | |||
| 120 | 82507 | public f<Any> PostTypeCheckingVerifier.visitFctCall(ASTFctCallNode* node) { | |
| 121 | // Builtin calls (printf, len, panic, etc.) return early in the TypeChecker before | ||
| 122 | // populating callee/calleeParentScope — skip them, matching the IRGenerator's behavior. | ||
| 123 | 82507 | if isBuiltinFunction(node.fqFunctionName) { | |
| 124 | 10596 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 125 | } | ||
| 126 | |||
| 127 | 71911 | assert !node.data.isEmpty(); | |
| 128 | // calleeParentScope is set for every visited non-fct-ptr non-builtin slot; use it as the | ||
| 129 | // "was this slot actually resolved?" indicator. Unsubstantiated/uncompiled slots have it nil. | ||
| 130 | 165973 | foreach const FctCallData& d : node.data { | |
| 131 | 94062 | assert d.calleeParentScope == nil<Scope*> || d.callee != nil<const Function*>; | |
| 132 | 94062 | } | |
| 133 | 71911 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 134 | } | ||
| 135 | |||
| 136 | 1462 | public f<Any> PostTypeCheckingVerifier.visitStructInstantiation(ASTStructInstantiationNode* node) { | |
| 137 | // instantiatedStructs is resized to manifestationCount; unsubstantiated generic slots remain nil. | ||
| 138 | // We can only safely assert the vector was populated — element-level nil checks would false-positive | ||
| 139 | // on unsubstantiated generic manifestation slots. | ||
| 140 | 1462 | assert !node.instantiatedStructs.isEmpty(); | |
| 141 | 1462 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 142 | } | ||
| 143 | |||
| 144 | 61 | public f<Any> PostTypeCheckingVerifier.visitLambdaFunc(ASTLambdaFuncNode* node) { | |
| 145 | 61 | assert !node.node.manifestations.isEmpty(); | |
| 146 | 61 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 147 | } | ||
| 148 | |||
| 149 | 71 | public f<Any> PostTypeCheckingVerifier.visitLambdaProc(ASTLambdaProcNode* node) { | |
| 150 | 71 | assert !node.node.manifestations.isEmpty(); | |
| 151 | 71 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 152 | } | ||
| 153 | |||
| 154 | 4 | public f<Any> PostTypeCheckingVerifier.visitLambdaExpr(ASTLambdaExprNode* node) { | |
| 155 | 4 | assert !node.node.manifestations.isEmpty(); | |
| 156 | 4 | return this.visitChildren(cast<ASTNode*>(node)); | |
| 157 | } | ||
| 158 |