typechecker/type-checker.spice
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | // Std imports | ||
| 4 | import "std/type/any"; | ||
| 5 | import "std/data/vector"; | ||
| 6 | import "std/data/unordered-map"; | ||
| 7 | import "std/data/pair"; | ||
| 8 | import "std/data/queue"; | ||
| 9 | import "std/type/type-conversion"; | ||
| 10 | import "std/type/types"; | ||
| 11 | import "std/math/hash"; | ||
| 12 | |||
| 13 | // Own imports | ||
| 14 | import "@/compiler-pass"; | ||
| 15 | import "@/driver"; | ||
| 16 | import "@/source-file"; | ||
| 17 | import "@/ast/ast-nodes"; | ||
| 18 | import "@/ast/attributes"; | ||
| 19 | import "@/ast/abstract-ast-visitor-intf"; | ||
| 20 | import "@/exception/error-manager"; | ||
| 21 | import "@/exception/semantic-error"; | ||
| 22 | import "@/global/global-resource-manager"; | ||
| 23 | import "@/global/runtime-module-manager"; | ||
| 24 | import "@/global/type-registry"; | ||
| 25 | import "@/model/function"; | ||
| 26 | import "@/model/generic-type"; | ||
| 27 | import "@/model/interface"; | ||
| 28 | import "@/model/struct"; | ||
| 29 | import "@/model/struct-base"; | ||
| 30 | import "@/model/union"; | ||
| 31 | import "@/reader/code-loc"; | ||
| 32 | import "@/symboltablebuilder/qual-type"; | ||
| 33 | import "@/symboltablebuilder/capture"; | ||
| 34 | import "@/symboltablebuilder/lifecycle"; | ||
| 35 | import "@/symboltablebuilder/scope"; | ||
| 36 | import "@/symboltablebuilder/scope-handle"; | ||
| 37 | import "@/symboltablebuilder/super-type"; | ||
| 38 | import "@/symboltablebuilder/symbol-table"; | ||
| 39 | import "@/symboltablebuilder/symbol-table-entry"; | ||
| 40 | import "@/symboltablebuilder/type"; | ||
| 41 | import "@/symboltablebuilder/type-chain"; | ||
| 42 | import "@/symboltablebuilder/type-qualifiers"; | ||
| 43 | import "@/typechecker/builtin-functions"; | ||
| 44 | import "@/typechecker/expr-result"; | ||
| 45 | import "@/typechecker/function-manager"; | ||
| 46 | import "@/typechecker/interface-manager"; | ||
| 47 | import "@/typechecker/op-rule-manager"; | ||
| 48 | import "@/typechecker/struct-manager"; | ||
| 49 | import "@/typechecker/type-matcher"; | ||
| 50 | import "@/typechecker/union-manager"; | ||
| 51 | import "@/util/compiler-warning"; | ||
| 52 | |||
| 53 | // Constants | ||
| 54 | const string FCT_NAME_DEALLOC = "sDealloc"; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Global switch that tells matchStruct whether a freshly substantiated struct manifestation may still be given its | ||
| 58 | * compiler-generated default members (ctor, copy ctor, move ctor, dtor). | ||
| 59 | * | ||
| 60 | * Creating those mutates the symbol table and registers new functions, which is only valid while the middle end runs. | ||
| 61 | * The back end reaches matchStruct as well - through QualType.getStruct - but by then every manifestation exists | ||
| 62 | * already, so those calls never take the creation path anyway. The switch makes that an invariant instead of an | ||
| 63 | * assumption. | ||
| 64 | */ | ||
| 65 | public bool STRUCT_DEFAULT_MEMBERS_DECIDABLE = false; | ||
| 66 | |||
| 67 | /** | ||
| 68 | * RAII marker for the section of the compiler in which struct manifestations are still allowed to receive their | ||
| 69 | * compiler-generated default members, i.e. the middle end. | ||
| 70 | */ | ||
| 71 | public type DefaultMemberCreationSection struct {} | ||
| 72 | |||
| 73 | 1009 | public p DefaultMemberCreationSection.ctor() { | |
| 74 | 1009 | STRUCT_DEFAULT_MEMBERS_DECIDABLE = true; | |
| 75 | 1009 | } | |
| 76 | |||
| 77 | 763 | public p DefaultMemberCreationSection.dtor() { | |
| 78 | 763 | STRUCT_DEFAULT_MEMBERS_DECIDABLE = false; | |
| 79 | 763 | } | |
| 80 | |||
| 81 | public type TypeCheckerMode enum { | ||
| 82 | TC_MODE_PRE, | ||
| 83 | TC_MODE_POST | ||
| 84 | } | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Jobs: | ||
| 88 | * - Ensure that all actual types match the expected types | ||
| 89 | * - Perform type inference | ||
| 90 | * | ||
| 91 | * Spice requires all methods of a struct to live in the file of the struct, so this file is split into sections, that are | ||
| 92 | * named after the checked constructs (top level definitions, statements, expressions, ...). | ||
| 93 | * | ||
| 94 | * Error handling: Spice has no exceptions, so this pass records a soft error and returns from the visit method of the offending node instead (SourceFile.checkForSoftErrors() aborts the | ||
| 95 | * compilation afterwards). | ||
| 96 | */ | ||
| 97 | 7588 | public type TypeChecker struct : IAbstractAstVisitor { | |
| 98 | compose public CompilerPass compilerPass | ||
| 99 | OpRuleManager opRuleManager | ||
| 100 | TypeCheckerMode typeCheckerMode | ||
| 101 | Vector<CompilerWarning>* warnings | ||
| 102 | TypeMapping typeMapping | ||
| 103 | 7925 | bool typeCheckedMainFct = false | |
| 104 | } | ||
| 105 | |||
| 106 | 7925 | public p TypeChecker.ctor(GlobalResourceManager& resourceManager, SourceFile* sourceFile, TypeCheckerMode typeCheckerMode) { | |
| 107 | 7925 | this.compilerPass = CompilerPass(resourceManager, sourceFile); | |
| 108 | 7925 | this.opRuleManager = OpRuleManager(this); | |
| 109 | 7925 | this.typeCheckerMode = typeCheckerMode; | |
| 110 | 7925 | this.warnings = &sourceFile.compilerOutput.warnings; | |
| 111 | 7925 | } | |
| 112 | |||
| 113 | // Default behavior of every visit method that is not implemented below: visit all children. After a hard error, it stops | ||
| 114 | // visiting further children, because the following nodes may depend on what the erroneous node failed to set up. | ||
| 115 | 87857 | #[ignoreUnusedReturnValue] | |
| 116 | 87857 | f<Any> TypeChecker.visitChildren(ASTNode* node) { | |
| 117 | 87857 | const ErrorManager& errorManager = this.resourceManager.getErrorManager(); | |
| 118 | 376824 | foreach ASTNode* child : node.children { | |
| 119 | 289007 | if errorManager.hasHardError { | |
| 120 | 40 | break; | |
| 121 | } | ||
| 122 | 288967 | assert child != nil<ASTNode*>; | |
| 123 | 288967 | assert child.visitable != nil<IVisitable*>; | |
| 124 | 288967 | this.visit(child.visitable); | |
| 125 | 288967 | } | |
| 126 | 87857 | return Any(); | |
| 127 | } | ||
| 128 | |||
| 129 | // Visit a node, which evaluates to a type (e.g. a data type node) | ||
| 130 | 736137 | f<QualType> TypeChecker.visitType(IVisitable* node) { | |
| 131 | 736137 | Any visitResult = this.visit(node); | |
| 132 | 1472274 | return visitResult.get<QualType>(); | |
| 133 | 736137 | } | |
| 134 | |||
| 135 | // Shorthand for visitType, for data type nodes | ||
| 136 | 262087 | f<QualType> TypeChecker.getDataType(ASTDataTypeNode* node) { | |
| 137 | 262087 | return this.visitType(cast<IVisitable*>(node)); | |
| 138 | } | ||
| 139 | |||
| 140 | // Visit a node, which evaluates to an expression result (e.g. an expression node) | ||
| 141 | 590026 | f<ExprResult> TypeChecker.visitExpr(IVisitable* node) { | |
| 142 | 590026 | Any visitResult = this.visit(node); | |
| 143 | 1180052 | return visitResult.get<ExprResult>(); | |
| 144 | 590026 | } | |
| 145 | |||
| 146 | // Return value of a visit method, that evaluates to an unresolved type after a soft error | ||
| 147 | 28 | f<Any> TypeChecker.softErrorQT(const ASTNode* node, SemanticErrorType errorType, const String& message) { | |
| 148 | 28 | this.softError(node, errorType, message); | |
| 149 | 28 | return Any(QualType(SuperType::TY_UNRESOLVED)); | |
| 150 | } | ||
| 151 | |||
| 152 | // Return value of a visit method, that evaluates to an expression result, after a soft error | ||
| 153 | 165 | f<Any> TypeChecker.softErrorER(const ASTNode* node, SemanticErrorType errorType, const String& message) { | |
| 154 | 165 | this.softError(node, errorType, message); | |
| 155 | 165 | return unresolvedER(); | |
| 156 | } | ||
| 157 | |||
| 158 | // Return value of a visit method, that evaluates to an unresolved expression result | ||
| 159 | 231 | f<Any> unresolvedER() { | |
| 160 | 231 | return Any(ExprResult{QualType(SuperType::TY_UNRESOLVED), nil<SymbolTableEntry*>}); | |
| 161 | } | ||
| 162 | |||
| 163 | // ======================================================== Type checker ========================================================= | ||
| 164 | |||
| 165 | 10290 | public f<Any> TypeChecker.visitEntry(ASTEntryNode* node) { | |
| 166 | // Initialize | ||
| 167 | 10290 | this.currentScope = this.rootScope; | |
| 168 | |||
| 169 | // Initialize AST nodes with size of 1 | ||
| 170 | 10290 | const bool isPrepare = this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE; | |
| 171 | 10290 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 172 | 10290 | if isPrepare { | |
| 173 | 3916 | baseNode.resizeToNumberOfManifestations(1l); | |
| 174 | 3916 | } | |
| 175 | |||
| 176 | // Find switch statements, that cover all items of an enum. This has to be done before visiting the children, because | ||
| 177 | // the control flow analysis of functions and procedures in the prepare stage relies on it | ||
| 178 | 10290 | if isPrepare { | |
| 179 | 3954 | foreach IVisitable* switchStmt : node.switchStmts { | |
| 180 | 38 | ASTSwitchStmtNode* switchStmtNode = downcastMut<ASTSwitchStmtNode>(getASTNode(switchStmt)); | |
| 181 | 38 | switchStmtNode.coversAllEnumItems = this.coversAllEnumItems(switchStmtNode); | |
| 182 | 38 | } | |
| 183 | 3916 | } | |
| 184 | |||
| 185 | // Visit children | ||
| 186 | 10290 | this.visitChildren(baseNode); | |
| 187 | // Skip the rest after a hard error (the compilation is aborted after this stage) | ||
| 188 | 10290 | const ErrorManager& errorManager = this.resourceManager.getErrorManager(); | |
| 189 | 10290 | if errorManager.hasHardError { | |
| 190 | 112 | return Any(); | |
| 191 | } | ||
| 192 | |||
| 193 | // Check which implicit structures we need for each struct, defined in this source file. Manifestations that are | ||
| 194 | // substantiated after this point are decided on directly at their creation (see createImplicitDefaultMembers). | ||
| 195 | 10234 | if isPrepare { | |
| 196 | 3890 | Vector<Struct*> manifestations = this.rootScope.getAllStructManifestationsInDeclarationOrder(); | |
| 197 | 7129 | foreach Struct* manifestation : manifestations { | |
| 198 | 3239 | createImplicitDefaultMembers(*manifestation, baseNode); | |
| 199 | 3239 | } | |
| 200 | 3890 | } | |
| 201 | |||
| 202 | 20468 | return Any(); | |
| 203 | 10290 | } | |
| 204 | |||
| 205 | /** | ||
| 206 | * Check if the case constants of a switch statement cover all items of a single enum | ||
| 207 | * | ||
| 208 | * @param node Switch statement node | ||
| 209 | * @return All enum items covered or not | ||
| 210 | */ | ||
| 211 | 38 | f<bool> TypeChecker.coversAllEnumItems(ASTSwitchStmtNode* node) { | |
| 212 | 38 | ASTEnumDefNode* enumDef = nil<ASTEnumDefNode*>; | |
| 213 | 38 | Vector<ASTEnumItemNode*> coveredItems; | |
| 214 | 133 | foreach IVisitable* caseBranch : node.caseBranches { | |
| 215 | 115 | const ASTCaseBranchNode* caseBranchNode = downcast<ASTCaseBranchNode>(caseBranch); | |
| 216 | 211 | foreach IVisitable* constant : caseBranchNode.caseConstants { | |
| 217 | 116 | const ASTCaseConstantNode* constantNode = downcast<ASTCaseConstantNode>(constant); | |
| 218 | // Literal constants can never be enum items | ||
| 219 | 116 | if constantNode.constant != nil<ASTConstantNode*> { | |
| 220 | 19 | return false; | |
| 221 | } | ||
| 222 | |||
| 223 | // Resolve the enum item via the name registry, since enum items are always referenced by their qualified name | ||
| 224 | 97 | const NameRegistryEntry* registryEntry = this.sourceFile.getNameRegistryEntry(constantNode.fqIdentifier); | |
| 225 | 97 | if registryEntry == nil<const NameRegistryEntry*> || registryEntry.targetEntry == nil<SymbolTableEntry*> { | |
| 226 | ✗ | return false; | |
| 227 | } | ||
| 228 | 97 | ASTNode* declNode = registryEntry.targetEntry.declNode; | |
| 229 | 97 | if declNode == nil<ASTNode*> || declNode.kind != ASTNodeKind::ENUM_ITEM { | |
| 230 | 1 | return false; | |
| 231 | } | ||
| 232 | 96 | const ASTEnumItemNode* enumItemNode = downcast<ASTEnumItemNode>(declNode); | |
| 233 | |||
| 234 | // All case constants have to be items of the same enum | ||
| 235 | 96 | if enumDef != nil<ASTEnumDefNode*> && enumItemNode.enumDef != enumDef { | |
| 236 | ✗ | return false; | |
| 237 | } | ||
| 238 | 96 | enumDef = enumItemNode.enumDef; | |
| 239 | 96 | bool alreadyCovered = false; | |
| 240 | 421 | foreach ASTEnumItemNode* coveredItem : coveredItems { | |
| 241 | 325 | if coveredItem == enumItemNode { | |
| 242 | ✗ | alreadyCovered = true; | |
| 243 | ✗ | } | |
| 244 | 325 | } | |
| 245 | 96 | if !alreadyCovered { | |
| 246 | 96 | coveredItems.pushBack(enumItemNode); | |
| 247 | 96 | } | |
| 248 | 96 | } | |
| 249 | 95 | } | |
| 250 | 18 | if enumDef == nil<ASTEnumDefNode*> { | |
| 251 | 1 | return false; | |
| 252 | } | ||
| 253 | 17 | const ASTEnumItemLstNode* itemLst = downcast<ASTEnumItemLstNode>(enumDef.itemLst); | |
| 254 | 34 | return coveredItems.getSize() == itemLst.items.getSize(); | |
| 255 | 38 | } | |
| 256 | |||
| 257 | /** | ||
| 258 | * Check if the capture rules for async lambdas are enforced if the async attribute is set | ||
| 259 | * | ||
| 260 | * Only one capture with pointer type, pass-by-val is allowed, since only then we can store it in the second field of the | ||
| 261 | * fat pointer and can ensure, that no stack variable is referenced inside the lambda. | ||
| 262 | * | ||
| 263 | * @param node Lambda node | ||
| 264 | * @param attrs Lambda attributes | ||
| 265 | * @return False if the rules are violated, true otherwise | ||
| 266 | */ | ||
| 267 | 138 | #[ignoreUnusedReturnValue] | |
| 268 | 138 | f<bool> TypeChecker.checkAsyncLambdaCaptureRules(ASTNode* node, const ASTLambdaAttrNode* attrs) { | |
| 269 | // If the async attribute is not set, we can return early | ||
| 270 | 138 | if attrs == nil<const ASTLambdaAttrNode*> { | |
| 271 | 117 | return true; // Not violated | |
| 272 | } | ||
| 273 | 21 | ASTAttrLstNode* attrLst = attrs.attrLst; | |
| 274 | 21 | if !attrLst.hasAttr(String(ATTR_ASYNC)) { | |
| 275 | ✗ | return true; // Not violated | |
| 276 | } | ||
| 277 | 21 | const CompileTimeValue asyncValue = attrLst.getAttrValueByName(String(ATTR_ASYNC)); | |
| 278 | 21 | if !asyncValue.getBoolValue() { | |
| 279 | ✗ | return true; // Not violated | |
| 280 | } | ||
| 281 | |||
| 282 | // If we don't have any captures, we can return early | ||
| 283 | 21 | ASTLambdaBaseNode* lambdaBaseNode = asLambdaBaseNode(node); | |
| 284 | 21 | Scope* bodyScope = lambdaBaseNode.bodyScope; | |
| 285 | 21 | CaptureMap& captures = bodyScope.symbolTable.captures; | |
| 286 | 21 | if captures.isEmpty() { | |
| 287 | ✗ | return true; // Not violated | |
| 288 | } | ||
| 289 | |||
| 290 | // Check for the capture rules | ||
| 291 | 21 | bool violated = captures.getSize() > 1ul; | |
| 292 | 21 | foreach Pair<const String&, Capture&> captureEntry : captures { | |
| 293 | // Only the first capture is inspected | ||
| 294 | 21 | Capture& capture = captureEntry.getSecond(); | |
| 295 | 21 | const QualType capturedType = capture.capturedSymbol.getQualType(); | |
| 296 | 21 | violated = violated || !capturedType.isPtr() || capture.getMode() != CapturePassMode::BY_VALUE; | |
| 297 | 21 | break; | |
| 298 | } | ||
| 299 | 21 | if violated { | |
| 300 | 8 | const String warningMessage = String("Async lambdas can only capture one pointer by value without storing captures in the caller stack frame, which can lead to bugs due to references, outliving the validity scope of the referenced variable."); | |
| 301 | 8 | this.addWarning(node.codeLoc, CompilerWarningType::ASYNC_LAMBDA_CAPTURE_RULE_VIOLATION, warningMessage); | |
| 302 | 8 | } | |
| 303 | |||
| 304 | 21 | return false; // Violated | |
| 305 | 21 | } | |
| 306 | |||
| 307 | /** | ||
| 308 | * Match the copy ctor of the given struct type | ||
| 309 | * | ||
| 310 | * @param thisType Struct type | ||
| 311 | * @param node Current AST node | ||
| 312 | * @return Copy ctor, or nil if there is none | ||
| 313 | */ | ||
| 314 | 72 | f<Function*> TypeChecker.matchCopyCtor(const QualType& thisType, const ASTNode* node) { | |
| 315 | 72 | Scope* matchScope = thisType.getBodyScope(); | |
| 316 | 72 | assert matchScope != nil<Scope*>; | |
| 317 | 72 | const QualType argType = thisType.toConstRef(node); | |
| 318 | 72 | ArgList args; | |
| 319 | 72 | args.pushBack(Pair<QualType, bool>(argType, false)); | |
| 320 | // The new instance being constructed is never const, regardless of whether the copy source (thisType) is const | ||
| 321 | 72 | const QualType nonConstThisType = thisType.toNonConst(); | |
| 322 | 72 | const QualTypeList noTemplateTypeHints; | |
| 323 | 144 | return matchFunction(matchScope, String(CTOR_FUNCTION_NAME), nonConstThisType, args, noTemplateTypeHints, true, node); | |
| 324 | 72 | } | |
| 325 | |||
| 326 | /** | ||
| 327 | * Resolve the copy ctors for the by-value params of a function, that own their argument (see QualType.isOwnedByValueParam). | ||
| 328 | * This is required for calls, that do not resolve the copy ctors per argument, like calls of operator functions or ctor calls | ||
| 329 | * via __new and __placement_new. The IR generator looks the copy ctors up again, when it copies the arguments. | ||
| 330 | * | ||
| 331 | * @param callee Called function | ||
| 332 | * @param node Call AST node | ||
| 333 | */ | ||
| 334 | 5561 | public p TypeChecker.matchCopyCtorsForOwnedParams(const Function* callee, const ASTNode* node) { | |
| 335 | 16157 | foreach const QualType& paramType : callee.getParamTypes() { | |
| 336 | 10596 | if paramType.isOwnedByValueParam(node) { | |
| 337 | // Only resolve the copy ctor here. The IR generator looks it up again | ||
| 338 | 3 | Function* _ = this.matchCopyCtor(paramType.toNonConst(), node); | |
| 339 | 3 | } | |
| 340 | 10596 | } | |
| 341 | 5561 | } | |
| 342 | |||
| 343 | /** | ||
| 344 | * Map a local type to the type in an imported scope. If the target file does not know about the struct, it is shown how to | ||
| 345 | * find the struct. | ||
| 346 | * | ||
| 347 | * @param targetScope Scope of the imported source file | ||
| 348 | * @param symbolType Local type | ||
| 349 | * @return Type in the imported scope | ||
| 350 | */ | ||
| 351 | 418842 | public f<QualType> TypeChecker.mapLocalTypeToImportedScopeType(const Scope* targetScope, const QualType& symbolType) { | |
| 352 | // Skip all types, except structs | ||
| 353 | 418842 | if !symbolType.isBase(SuperType::TY_STRUCT) { | |
| 354 | 307898 | return symbolType; | |
| 355 | } | ||
| 356 | |||
| 357 | // If the target scope is in the current source file, we can return the symbol type as is | ||
| 358 | 110944 | SourceFile* targetSourceFile = targetScope.sourceFile; | |
| 359 | 110944 | if targetSourceFile == this.sourceFile { | |
| 360 | 28197 | return symbolType; | |
| 361 | } | ||
| 362 | |||
| 363 | // Check if the target file knows the struct via its name registry | ||
| 364 | 82747 | const QualType baseType = symbolType.getBase(); | |
| 365 | 82747 | const String structName = baseType.getSubType(); | |
| 366 | 82747 | if targetSourceFile.isStructKnownByNameRegistry(baseType.getBodyScope(), structName) { | |
| 367 | 151668 | return symbolType; | |
| 368 | } | ||
| 369 | |||
| 370 | // The target file does not know about the struct at all | ||
| 371 | // -> show it how to find the struct | ||
| 372 | 6913 | const NameRegistryEntry* origRegistryEntry = this.sourceFile.getNameRegistryEntry(structName); | |
| 373 | // If even this file does not know the struct by its unqualified name (deep transitive import), there is | ||
| 374 | // nothing to copy over. Skip teaching the target file; the type identity itself is unaffected, and member | ||
| 375 | // access falls back to the resolved body scope. | ||
| 376 | 6913 | if origRegistryEntry == nil<const NameRegistryEntry*> { | |
| 377 | ✗ | return symbolType; | |
| 378 | } | ||
| 379 | // Do not clobber an entry the target file already has under this name (e.g. its OWN same-named struct). Teaching | ||
| 380 | // uses keepNewOnCollision=false, which would otherwise ERASE the target's existing entry and break resolution of | ||
| 381 | // its own type. The struct's QualType identity is carried by pointer regardless. | ||
| 382 | 6913 | if targetSourceFile.exportedNameRegistry.contains(structName) { | |
| 383 | 3976 | return symbolType; | |
| 384 | } | ||
| 385 | 4925 | const unsigned long targetTypeId = origRegistryEntry.typeId; | |
| 386 | 4925 | SymbolTableEntry* targetEntry = origRegistryEntry.targetEntry; | |
| 387 | 4925 | targetSourceFile.addNameRegistryEntry(structName, targetTypeId, targetEntry, origRegistryEntry.targetScope, false, nil<SymbolTableEntry*>); | |
| 388 | |||
| 389 | 9850 | return symbolType; | |
| 390 | 82747 | } | |
| 391 | |||
| 392 | /** | ||
| 393 | * Map a type from an imported scope to the local type. If this source file does not know about the struct, it is shown how | ||
| 394 | * to find the struct. | ||
| 395 | * | ||
| 396 | * @param sourceScope Scope of the imported source file | ||
| 397 | * @param symbolType Type in the imported scope | ||
| 398 | * @return Local type | ||
| 399 | */ | ||
| 400 | 20212 | public f<QualType> TypeChecker.mapImportedScopeTypeToLocalType(const Scope* sourceScope, const QualType& symbolType) { | |
| 401 | // Skip all types, except structs | ||
| 402 | 20212 | if !symbolType.isBase(SuperType::TY_STRUCT) { | |
| 403 | 2728 | return symbolType; | |
| 404 | } | ||
| 405 | |||
| 406 | // If the given source file is in the current one, we can return the symbol type as is | ||
| 407 | 17484 | SourceFile* sourceSourceFile = sourceScope.sourceFile; | |
| 408 | 17484 | if sourceSourceFile == this.sourceFile { | |
| 409 | 4232 | return symbolType; | |
| 410 | } | ||
| 411 | |||
| 412 | // Check if this source file knows the struct via its name registry | ||
| 413 | 13252 | const QualType baseType = symbolType.getBase(); | |
| 414 | 13252 | if this.sourceFile.isStructKnownByNameRegistry(baseType.getBodyScope(), baseType.getSubType()) { | |
| 415 | 13033 | return symbolType; | |
| 416 | } | ||
| 417 | |||
| 418 | // This source file does not know about the struct at all | ||
| 419 | // -> show it how to find the struct | ||
| 420 | 219 | const String structName = baseType.getSubType(); | |
| 421 | 219 | const NameRegistryEntry* origRegistryEntry = sourceSourceFile.getNameRegistryEntry(structName); | |
| 422 | // If even the source file does not know the struct by its unqualified name (deep transitive import), there is | ||
| 423 | // nothing to copy over. Skip teaching this file; the type identity itself is unaffected, and member access | ||
| 424 | // falls back to the resolved body scope. | ||
| 425 | 219 | if origRegistryEntry == nil<const NameRegistryEntry*> { | |
| 426 | ✗ | return symbolType; | |
| 427 | } | ||
| 428 | // Do not clobber an entry this file already has under this name (see mapLocalTypeToImportedScopeType): teaching | ||
| 429 | // with keepNewOnCollision=false would ERASE this file's own same-named struct entry. The QualType identity is | ||
| 430 | // carried by pointer, so an already-taken name needs no (re-)teaching here. | ||
| 431 | 219 | if this.sourceFile.exportedNameRegistry.contains(structName) { | |
| 432 | ✗ | return symbolType; | |
| 433 | } | ||
| 434 | 219 | const unsigned long typeId = origRegistryEntry.typeId; | |
| 435 | 219 | SymbolTableEntry* targetEntry = origRegistryEntry.targetEntry; | |
| 436 | 219 | this.sourceFile.addNameRegistryEntry(structName, typeId, targetEntry, origRegistryEntry.targetScope, false, nil<SymbolTableEntry*>); | |
| 437 | |||
| 438 | 438 | return symbolType; | |
| 439 | 219 | } | |
| 440 | |||
| 441 | /** | ||
| 442 | * Check type name against well-known type names that require a runtime import. If found one, auto-import the runtime module. | ||
| 443 | * | ||
| 444 | * @param typeName Given type name | ||
| 445 | */ | ||
| 446 | 140855 | p TypeChecker.ensureLoadedRuntimeForTypeName(const String& typeName) { | |
| 447 | 140855 | RuntimeModule runtimeModule = RuntimeModule::STRING_RT; | |
| 448 | 140855 | if getRuntimeModuleForTypeName(typeName, runtimeModule) && !this.sourceFile.isRT(runtimeModule) { | |
| 449 | 14944 | this.sourceFile.requestRuntimeModule(runtimeModule); | |
| 450 | 14944 | } | |
| 451 | 140855 | } | |
| 452 | |||
| 453 | /** | ||
| 454 | * Check function name against well-known function names that require a runtime import. If found one, auto-import the runtime | ||
| 455 | * module. | ||
| 456 | * | ||
| 457 | * @param functionName Given function name | ||
| 458 | */ | ||
| 459 | 40741 | p TypeChecker.ensureLoadedRuntimeForFunctionName(const String& functionName) { | |
| 460 | 40741 | RuntimeModule runtimeModule = RuntimeModule::STRING_RT; | |
| 461 | 40741 | if getRuntimeModuleForFunctionName(functionName, runtimeModule) && !this.sourceFile.isRT(runtimeModule) { | |
| 462 | 4188 | this.sourceFile.requestRuntimeModule(runtimeModule); | |
| 463 | 4188 | } | |
| 464 | 40741 | } | |
| 465 | |||
| 466 | /** | ||
| 467 | * Get the operator overloading functions of the given node for the current manifestation | ||
| 468 | * | ||
| 469 | * @param node AST node, that supports operator overloading | ||
| 470 | * @return Operator overloading functions | ||
| 471 | */ | ||
| 472 | 5522 | public f<Vector<const Function*>&> TypeChecker.getOpFctPointers(ASTNode* node) { | |
| 473 | 5522 | Vector<Vector<const Function*>>* opFctPointers = node.getOpFctPointers(); | |
| 474 | 5522 | assert opFctPointers.getSize() > cast<long>(this.manIdx); | |
| 475 | 5522 | return opFctPointers.get(this.manIdx); | |
| 476 | } | ||
| 477 | |||
| 478 | /** | ||
| 479 | * Request a re-visit of the source file of the given function, if the function body was not type-checked yet | ||
| 480 | * | ||
| 481 | * @param fct Function to check | ||
| 482 | */ | ||
| 483 | 115053 | public p requestRevisitIfRequired(const Function* fct) { | |
| 484 | 115053 | if fct != nil<const Function*> && !fct.alreadyTypeChecked { | |
| 485 | 70834 | SourceFile* sourceFile = fct.entry.scope.sourceFile; | |
| 486 | 70834 | sourceFile.reVisitRequested = true; | |
| 487 | 70834 | } | |
| 488 | 115053 | } | |
| 489 | |||
| 490 | /** | ||
| 491 | * Add a soft error to the error list | ||
| 492 | */ | ||
| 493 | 247 | p TypeChecker.softError(const ASTNode* node, SemanticErrorType errorType, const String& message) { | |
| 494 | 247 | ErrorManager& errorManager = this.resourceManager.getErrorManager(); | |
| 495 | 247 | errorManager.addSoftError(node, errorType, message); | |
| 496 | 247 | } | |
| 497 | |||
| 498 | // Add a warning to the warning list of the current source file | ||
| 499 | 5310 | p TypeChecker.addWarning(const CodeLoc& codeLoc, CompilerWarningType warningType, const String& message) { | |
| 500 | 5310 | this.warnings.pushBack(CompilerWarning(codeLoc, warningType, message.getRaw())); | |
| 501 | 5310 | } | |
| 502 | |||
| 503 | /** | ||
| 504 | * Check if the given ctor args denote a copy ctor call (like __new<T>(other) or __placement_new<T>(ptr, other)), i.e. there | ||
| 505 | * is exactly one ctor arg and it has the same type as the struct to construct | ||
| 506 | * | ||
| 507 | * @param ctorArgs Ctor args (without the target pointer of __placement_new) | ||
| 508 | * @param thisType Type of the struct to construct | ||
| 509 | * @return Copy ctor call or not | ||
| 510 | */ | ||
| 511 | 26 | f<bool> TypeChecker.isCopyCtorCall(const ArgList& ctorArgs, const QualType& thisType) { | |
| 512 | 26 | if ctorArgs.getSize() != 1l { | |
| 513 | 2 | return false; | |
| 514 | } | ||
| 515 | 24 | const Pair<QualType, bool>& arg = ctorArgs.front(); | |
| 516 | 24 | QualType argType = arg.getFirst(); | |
| 517 | 24 | argType = argType.removeReferenceWrapper(); | |
| 518 | 24 | argType = argType.toNonConst(); | |
| 519 | 24 | return thisType.matches(argType, false, false, true); | |
| 520 | } | ||
| 521 | |||
| 522 | /** | ||
| 523 | * Collect the generic types of a template type list (used by the prepare visitors of functions, procedures, structs and | ||
| 524 | * unions). | ||
| 525 | * | ||
| 526 | * @param templateTypeLst Template type list node | ||
| 527 | * @param genericTypes Receives the generic types | ||
| 528 | * @return False if an error occurred | ||
| 529 | */ | ||
| 530 | 7811 | f<bool> TypeChecker.collectTemplateTypes(ASTTypeLstNode* templateTypeLst, Vector<GenericType>& genericTypes) { | |
| 531 | 17134 | foreach ASTDataTypeNode* dataType : templateTypeLst.dataTypes { | |
| 532 | // Visit template type | ||
| 533 | 9325 | const QualType templateType = this.getDataType(dataType); | |
| 534 | 9325 | if templateType.is(SuperType::TY_UNRESOLVED) { | |
| 535 | ✗ | continue; | |
| 536 | } | ||
| 537 | // Check if it is a generic type | ||
| 538 | 9325 | if !templateType.is(SuperType::TY_GENERIC) { | |
| 539 | 2 | throwSemanticError(cast<ASTNode*>(dataType), SemanticErrorType::EXPECTED_GENERIC_TYPE, String("A template list can only contain generic types")); | |
| 540 | 2 | return false; | |
| 541 | 2 | } | |
| 542 | // Convert generic symbol type to generic type | ||
| 543 | 9323 | GenericType* genericType = this.rootScope.lookupGenericTypeStrict(templateType.getSubType()); | |
| 544 | 9323 | assert genericType != nil<GenericType*>; | |
| 545 | 9323 | genericTypes.pushBack(*genericType); | |
| 546 | 9323 | } | |
| 547 | 7809 | return true; | |
| 548 | } | ||
| 549 | |||
| 550 | /** | ||
| 551 | * Add a template type of the 'this' type to the given generic types, if it is not included yet, and mark the last generic type | ||
| 552 | * as used. | ||
| 553 | * | ||
| 554 | * @param genericTypes Generic types | ||
| 555 | * @param templateType Template type of the 'this' type | ||
| 556 | */ | ||
| 557 | 11195 | p addUsedTemplateType(Vector<GenericType>& genericTypes, const QualType& templateType) { | |
| 558 | 11195 | bool isContained = false; | |
| 559 | 13187 | foreach const GenericType& genericType : genericTypes { | |
| 560 | 2728 | if genericType.qualType == templateType { | |
| 561 | 736 | isContained = true; | |
| 562 | 736 | break; | |
| 563 | } | ||
| 564 | 1992 | } | |
| 565 | 11195 | if !isContained { | |
| 566 | 10459 | genericTypes.pushBack(GenericType(templateType)); | |
| 567 | 10459 | } | |
| 568 | 11195 | GenericType& lastGenericType = genericTypes.back(); | |
| 569 | 11195 | lastGenericType.used = true; | |
| 570 | 11195 | } | |
| 571 | |||
| 572 | // ==================================================== Top level definitions ==================================================== | ||
| 573 | |||
| 574 | 2109 | public f<Any> TypeChecker.visitMainFctDef(ASTMainFctDefNode* node) { | |
| 575 | 2109 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 576 | 974 | return this.visitMainFctDefPrepare(node); | |
| 577 | } | ||
| 578 | 1135 | return this.visitMainFctDefCheck(node); | |
| 579 | } | ||
| 580 | |||
| 581 | 89813 | public f<Any> TypeChecker.visitFctDef(ASTFctDefNode* node) { | |
| 582 | 89813 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 583 | 28089 | return this.visitFctDefPrepare(node); | |
| 584 | } | ||
| 585 | 61724 | return this.visitFctDefCheck(node); | |
| 586 | } | ||
| 587 | |||
| 588 | 57426 | public f<Any> TypeChecker.visitProcDef(ASTProcDefNode* node) { | |
| 589 | 57426 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 590 | 17620 | return this.visitProcDefPrepare(node); | |
| 591 | } | ||
| 592 | 39806 | return this.visitProcDefCheck(node); | |
| 593 | } | ||
| 594 | |||
| 595 | 9310 | public f<Any> TypeChecker.visitStructDef(ASTStructDefNode* node) { | |
| 596 | 9310 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 597 | 3225 | return this.visitStructDefPrepare(node); | |
| 598 | } | ||
| 599 | 6085 | return this.visitStructDefCheck(node); | |
| 600 | } | ||
| 601 | |||
| 602 | 678 | public f<Any> TypeChecker.visitInterfaceDef(ASTInterfaceDefNode* node) { | |
| 603 | 678 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 604 | 323 | return this.visitInterfaceDefPrepare(node); | |
| 605 | } | ||
| 606 | 355 | return Any(); | |
| 607 | } | ||
| 608 | |||
| 609 | 46 | public f<Any> TypeChecker.visitUnionDef(ASTUnionDefNode* node) { | |
| 610 | 46 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 611 | 24 | return this.visitUnionDefPrepare(node); | |
| 612 | } | ||
| 613 | 22 | return this.visitUnionDefCheck(node); | |
| 614 | } | ||
| 615 | |||
| 616 | /** | ||
| 617 | * Assign the opaque type to a struct, interface, enum or alias that is referenced before it has been prepared. This | ||
| 618 | * effectively acts as an implicit forward declaration and is what makes circular imports work: two types in mutually | ||
| 619 | * importing files can reference each other, so neither can be prepared strictly before the other. The full prepare pass | ||
| 620 | * (visitStructDefPrepare / visitInterfaceDefPrepare / visitEnumDefPrepare / visitAliasDefPrepare) assigns the identical | ||
| 621 | * interned type later and additionally fills in the body scope and manifestations. | ||
| 622 | * | ||
| 623 | * Only non-generic structs/interfaces can be pre-declared this way, since the opaque type carries no template types. | ||
| 624 | * | ||
| 625 | * @param entry Symbol table entry of the referenced struct, interface, enum or alias (its type must still be invalid) | ||
| 626 | */ | ||
| 627 | 10 | p TypeChecker.assignDeferredOpaqueType(SymbolTableEntry* entry) { | |
| 628 | 10 | const QualType entryType = entry.getQualType(); | |
| 629 | 10 | assert entryType.is(SuperType::TY_INVALID); | |
| 630 | 10 | ASTNode* declNode = entry.declNode; | |
| 631 | 10 | TypeRegistry& typeRegistry = this.resourceManager.typeRegistry; | |
| 632 | 10 | const QualTypeList noTemplateTypes; | |
| 633 | 10 | TypeChainElementData data; | |
| 634 | 10 | switch declNode.kind { | |
| 635 | case ASTNodeKind::STRUCT_DEF: { | ||
| 636 | 5 | const ASTStructDefNode* structDef = downcast<ASTStructDefNode>(declNode); | |
| 637 | 5 | if structDef.hasTemplateTypes { | |
| 638 | ✗ | return; | |
| 639 | } | ||
| 640 | 5 | data.bodyScope = structDef.structScope; | |
| 641 | 5 | const Type* rawType = typeRegistry.getOrInsert(SuperType::TY_STRUCT, structDef.structName, structDef.typeId, data, noTemplateTypes); | |
| 642 | 5 | entry.updateType(QualType(rawType, structDef.qualifiers), false); | |
| 643 | 5 | } | |
| 644 | case ASTNodeKind::INTERFACE_DEF: { | ||
| 645 | 1 | const ASTInterfaceDefNode* interfaceDef = downcast<ASTInterfaceDefNode>(declNode); | |
| 646 | 1 | if interfaceDef.hasTemplateTypes { | |
| 647 | ✗ | return; | |
| 648 | } | ||
| 649 | 1 | data.bodyScope = interfaceDef.interfaceScope; | |
| 650 | 1 | const Type* rawType = typeRegistry.getOrInsert(SuperType::TY_INTERFACE, interfaceDef.interfaceName, interfaceDef.typeId, data, noTemplateTypes); | |
| 651 | 1 | entry.updateType(QualType(rawType, interfaceDef.qualifiers), false); | |
| 652 | 1 | } | |
| 653 | case ASTNodeKind::UNION_DEF: { | ||
| 654 | ✗ | const ASTUnionDefNode* unionDef = downcast<ASTUnionDefNode>(declNode); | |
| 655 | ✗ | if unionDef.hasTemplateTypes { | |
| 656 | ✗ | return; | |
| 657 | } | ||
| 658 | ✗ | data.bodyScope = unionDef.unionScope; | |
| 659 | ✗ | const Type* rawType = typeRegistry.getOrInsert(SuperType::TY_UNION, unionDef.unionName, unionDef.typeId, data, noTemplateTypes); | |
| 660 | ✗ | entry.updateType(QualType(rawType, unionDef.qualifiers), false); | |
| 661 | ✗ | } | |
| 662 | case ASTNodeKind::ENUM_DEF: { | ||
| 663 | 1 | const ASTEnumDefNode* enumDef = downcast<ASTEnumDefNode>(declNode); | |
| 664 | 1 | data.bodyScope = enumDef.enumScope; | |
| 665 | 1 | const Type* rawType = typeRegistry.getOrInsert(SuperType::TY_ENUM, enumDef.enumName, enumDef.typeId, data, noTemplateTypes); | |
| 666 | 1 | entry.updateType(QualType(rawType, enumDef.qualifiers), false); | |
| 667 | 1 | } | |
| 668 | case ASTNodeKind::ALIAS_DEF: { | ||
| 669 | // An alias additionally needs its aliased type resolved, so prepare it fully on demand. visitAliasDefPrepare is | ||
| 670 | // idempotent: the real prepare pass for the alias's own file skips it once the type is no longer invalid. | ||
| 671 | // The aliased type has to be resolved in the context of the file declaring the alias, not the referencing one, | ||
| 672 | // because the referencing file does not necessarily import the types the alias refers to. | ||
| 673 | 2 | TypeChecker declTypeChecker = TypeChecker(this.resourceManager, entry.scope.sourceFile, TypeCheckerMode::TC_MODE_PRE); | |
| 674 | 2 | declTypeChecker.visitAliasDefPrepare(downcastMut<ASTAliasDefNode>(declNode)); | |
| 675 | 2 | } | |
| 676 | 1 | default: {} | |
| 677 | } | ||
| 678 | 10 | } | |
| 679 | |||
| 680 | 1378 | public f<Any> TypeChecker.visitEnumDef(ASTEnumDefNode* node) { | |
| 681 | 1378 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 682 | 463 | return this.visitEnumDefPrepare(node); | |
| 683 | } | ||
| 684 | 915 | return Any(); | |
| 685 | } | ||
| 686 | |||
| 687 | 9769 | public f<Any> TypeChecker.visitGenericTypeDef(ASTGenericTypeDefNode* node) { | |
| 688 | 9769 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 689 | 3062 | return this.visitGenericTypeDefPrepare(node); | |
| 690 | } | ||
| 691 | 6707 | return Any(); | |
| 692 | } | ||
| 693 | |||
| 694 | 405 | public f<Any> TypeChecker.visitAliasDef(ASTAliasDefNode* node) { | |
| 695 | 405 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 696 | 157 | return this.visitAliasDefPrepare(node); | |
| 697 | } | ||
| 698 | 248 | return Any(); | |
| 699 | } | ||
| 700 | |||
| 701 | 10737 | public f<Any> TypeChecker.visitGlobalVarDef(ASTGlobalVarDefNode* node) { | |
| 702 | 10737 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 703 | 4280 | return this.visitGlobalVarDefPrepare(node); | |
| 704 | } | ||
| 705 | 6457 | return Any(); | |
| 706 | } | ||
| 707 | |||
| 708 | 16872 | public f<Any> TypeChecker.visitExtDecl(ASTExtDeclNode* node) { | |
| 709 | 16872 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 710 | 5226 | return this.visitExtDeclPrepare(node); | |
| 711 | } | ||
| 712 | 11646 | return Any(); | |
| 713 | } | ||
| 714 | |||
| 715 | 7147 | public f<Any> TypeChecker.visitImportDef(ASTImportDefNode* node) { | |
| 716 | 7147 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_PRE { | |
| 717 | 2501 | return this.visitImportDefPrepare(node); | |
| 718 | } | ||
| 719 | 4646 | return Any(); | |
| 720 | } | ||
| 721 | |||
| 722 | // ============================================= Top level definitions (check stage) ============================================= | ||
| 723 | |||
| 724 | 1135 | f<Any> TypeChecker.visitMainFctDefCheck(ASTMainFctDefNode* node) { | |
| 725 | // Skip if already type-checked | ||
| 726 | 1135 | if this.typeCheckedMainFct { | |
| 727 | 164 | return Any(); | |
| 728 | } | ||
| 729 | |||
| 730 | 971 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 731 | 971 | baseNode.resizeToNumberOfManifestations(1l); | |
| 732 | |||
| 733 | // Change to function body scope | ||
| 734 | 971 | this.currentScope = node.bodyScope; | |
| 735 | // Visit statements in new scope | ||
| 736 | 971 | this.visit(node.body); | |
| 737 | // Leave main function body scope | ||
| 738 | 971 | this.currentScope = this.rootScope; | |
| 739 | |||
| 740 | // Set to type-checked | ||
| 741 | 971 | this.typeCheckedMainFct = true; | |
| 742 | 1942 | return Any(); | |
| 743 | 971 | } | |
| 744 | |||
| 745 | 61724 | f<Any> TypeChecker.visitFctDefCheck(ASTFctDefNode* node) { | |
| 746 | 61724 | return this.checkFctOrProcDef(&node.node, cast<ASTNode*>(node), /*isFunction=*/true, /*isCtor=*/false); | |
| 747 | } | ||
| 748 | |||
| 749 | 39806 | f<Any> TypeChecker.visitProcDefCheck(ASTProcDefNode* node) { | |
| 750 | 39806 | return this.checkFctOrProcDef(&node.node, cast<ASTNode*>(node), /*isFunction=*/false, node.isCtor); | |
| 751 | } | ||
| 752 | |||
| 753 | /** | ||
| 754 | * Type-check the bodies of all manifestations of a function or procedure definition | ||
| 755 | * | ||
| 756 | * @param fctDefBase Function or procedure definition node | ||
| 757 | * @param baseNode AST node of the definition | ||
| 758 | * @param isFunction Whether the definition is a function (otherwise a procedure) | ||
| 759 | * @param isCtor Whether the definition is a constructor | ||
| 760 | */ | ||
| 761 | 101530 | f<Any> TypeChecker.checkFctOrProcDef(ASTFctDefBaseNode* fctDefBase, ASTNode* baseNode, bool isFunction, bool isCtor) { | |
| 762 | 101530 | baseNode.resizeToNumberOfManifestations(fctDefBase.manifestations.getSize()); | |
| 763 | 101530 | this.manIdx = 0l; // Reset the manifestation index | |
| 764 | |||
| 765 | // Get all manifestations for this function/procedure definition | ||
| 766 | // Only visit the manifestations, that exist at this point (manifestations inserted during the loop are not covered by the | ||
| 767 | // resize above and get checked in a revisit) | ||
| 768 | 101530 | const unsigned long manifestationCount = fctDefBase.manifestations.getSize(); | |
| 769 | 234636 | for unsigned long manifestationIdx = 0l; manifestationIdx < manifestationCount; manifestationIdx++ { | |
| 770 | 133106 | Function* manifestation = fctDefBase.manifestations.get(manifestationIdx); | |
| 771 | // Skip non-substantiated or already checked functions/procedures | ||
| 772 | 133106 | if !manifestation.isFullySubstantiated() || manifestation.alreadyTypeChecked { | |
| 773 | 88408 | this.manIdx++; // Increase the manifestation index | |
| 774 | 88408 | continue; | |
| 775 | } | ||
| 776 | |||
| 777 | // Change scope to concrete struct specialization scope or to the interface scope for default methods | ||
| 778 | 44698 | if manifestation.isInterfaceDefaultMethod { | |
| 779 | 10 | this.checkDefaultMethodImplementsInterfaceMethod(manifestation); | |
| 780 | 10 | this.compilerPass.changeToScope(fctDefBase.structScope, ScopeType::INTERFACE); | |
| 781 | 44698 | } else if fctDefBase.isMethod { | |
| 782 | 27222 | const QualTypeList thisTemplateTypes = manifestation.thisType.getTemplateTypes(); | |
| 783 | 27222 | const String scopeName = getStructScopeName(fctDefBase.name.structName, thisTemplateTypes); | |
| 784 | 27222 | this.compilerPass.changeToScope(scopeName, ScopeType::STRUCT); | |
| 785 | 44688 | } | |
| 786 | |||
| 787 | // Change to function/procedure scope | ||
| 788 | 44698 | this.compilerPass.changeToScope(manifestation.bodyScope, ScopeType::FUNC_PROC_BODY); | |
| 789 | |||
| 790 | // Mount type mapping for this manifestation | ||
| 791 | 44698 | assert this.typeMapping.isEmpty(); | |
| 792 | 44698 | this.typeMapping = manifestation.typeMapping; | |
| 793 | |||
| 794 | // Set return type to the result variable. A manifestation for a capturing lambda argument copies the body scope of an | ||
| 795 | // already type-checked manifestation, so the result variable might already have a type | ||
| 796 | 44698 | if isFunction { | |
| 797 | 27434 | SymbolTableEntry* resultVarEntry = this.currentScope.lookupStrict(String(RETURN_VARIABLE_NAME)); | |
| 798 | 27434 | assert resultVarEntry != nil<SymbolTableEntry*>; | |
| 799 | 27434 | resultVarEntry.updateType(manifestation.returnType, true); | |
| 800 | 27434 | resultVarEntry.used = true; | |
| 801 | 27434 | } | |
| 802 | |||
| 803 | // Visit parameters | ||
| 804 | // This happens once in the type checker prepare stage. This second time is only required if we have a generic function/procedure | ||
| 805 | 44698 | if fctDefBase.hasParams { | |
| 806 | 32087 | this.visit(fctDefBase.paramLst); | |
| 807 | 32087 | this.annotateLambdaCaptureParams(fctDefBase, manifestation); | |
| 808 | 32087 | } | |
| 809 | |||
| 810 | // Prepare generation of special ctor preamble to store VTable, default field values, etc. if required | ||
| 811 | 44698 | if isCtor { | |
| 812 | 5563 | this.createCtorBodyPreamble(manifestation.bodyScope); | |
| 813 | 5563 | } | |
| 814 | |||
| 815 | // Visit statements in new scope | ||
| 816 | 44698 | this.visit(fctDefBase.body); | |
| 817 | |||
| 818 | // Clear type mapping | ||
| 819 | 44698 | this.typeMapping.clear(); | |
| 820 | |||
| 821 | // Change to root scope | ||
| 822 | 44698 | this.currentScope = this.rootScope; | |
| 823 | 44698 | assert this.currentScope != nil<Scope*> && this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 824 | |||
| 825 | // Do not type-check this manifestation again | ||
| 826 | 44698 | manifestation.alreadyTypeChecked = true; | |
| 827 | |||
| 828 | 44698 | this.manIdx++; // Increase the manifestation index | |
| 829 | 44698 | } | |
| 830 | 101530 | this.manIdx = 0l; // Reset the manifestation index | |
| 831 | |||
| 832 | 101530 | return Any(); | |
| 833 | } | ||
| 834 | |||
| 835 | /** | ||
| 836 | * Annotate function/procedure-typed params with lambda-capture info from the resolved manifestation type. | ||
| 837 | * This must happen in the TypeChecker so that the IRGenerator can treat SymbolTableEntry as immutable. | ||
| 838 | * | ||
| 839 | * @param fctDefBase Function or procedure definition node | ||
| 840 | * @param manifestation Manifestation, whose params to annotate | ||
| 841 | */ | ||
| 842 | 32086 | p TypeChecker.annotateLambdaCaptureParams(ASTFctDefBaseNode* fctDefBase, const Function* manifestation) { | |
| 843 | 32086 | const ASTParamLstNode* paramLst = downcast<ASTParamLstNode>(getASTNode(fctDefBase.paramLst)); | |
| 844 | 32086 | const QualTypeList paramTypes = manifestation.getParamTypes(); | |
| 845 | 32086 | const dyn fctOptions = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 846 | 82911 | for unsigned long i = 0l; i < manifestation.paramList.getSize(); i++ { | |
| 847 | 50825 | const ASTDeclStmtNode* param = paramLst.params.get(i); | |
| 848 | 50825 | const QualType& paramType = paramTypes.get(i); | |
| 849 | 50825 | if paramType.isOneOf(fctOptions, len(fctOptions)) && paramType.hasLambdaCaptures() { | |
| 850 | 36 | SymbolTableEntry* paramSymbol = this.currentScope.lookupStrict(param.varName); | |
| 851 | 36 | assert paramSymbol != nil<SymbolTableEntry*>; | |
| 852 | 36 | const QualType paramSymbolType = paramSymbol.getQualType(); | |
| 853 | 36 | paramSymbol.updateType(paramSymbolType.getWithLambdaCaptures(), true); | |
| 854 | 36 | } | |
| 855 | 50825 | } | |
| 856 | 32086 | } | |
| 857 | |||
| 858 | /** | ||
| 859 | * Check if a function or procedure, that is defined as method of an interface, is a valid default method. | ||
| 860 | * | ||
| 861 | * @param fctDefBase Function or procedure definition node of the default method | ||
| 862 | * @param interfaceType Type of the interface, the default method belongs to | ||
| 863 | * @param paramList Parameters of the default method | ||
| 864 | * @return Valid default method or not | ||
| 865 | */ | ||
| 866 | 13 | f<bool> TypeChecker.checkDefaultMethod(ASTFctDefBaseNode* fctDefBase, const QualType& interfaceType, const ParamList& paramList) { | |
| 867 | 13 | ASTNode* baseNode = cast<ASTNode*>(fctDefBase); | |
| 868 | 13 | const String& name = fctDefBase.name.name; | |
| 869 | 13 | if name == CTOR_FUNCTION_NAME || name == DTOR_FUNCTION_NAME { | |
| 870 | 1 | throwSemanticError(baseNode, SemanticErrorType::INVALID_DEFAULT_METHOD, String("Interfaces cannot have constructors or destructors")); | |
| 871 | 1 | return false; | |
| 872 | 1 | } | |
| 873 | 12 | const QualTypeList interfaceTemplateTypes = interfaceType.getTemplateTypes(); | |
| 874 | 12 | if fctDefBase.hasTemplateTypes || !interfaceTemplateTypes.isEmpty() { | |
| 875 | 1 | throwSemanticError(baseNode, SemanticErrorType::INVALID_DEFAULT_METHOD, String("Default methods of generic interfaces are not supported yet")); | |
| 876 | 1 | return false; | |
| 877 | 1 | } | |
| 878 | 16 | foreach const Param& param : paramList { | |
| 879 | 6 | if param.isOptional { | |
| 880 | 1 | throwSemanticError(getASTNode(fctDefBase.paramLst), SemanticErrorType::INVALID_DEFAULT_METHOD, String("Default methods cannot have optional parameters")); | |
| 881 | 1 | return false; | |
| 882 | 1 | } | |
| 883 | 5 | } | |
| 884 | 10 | return true; | |
| 885 | 12 | } | |
| 886 | |||
| 887 | /** | ||
| 888 | * Check if a default method implements a method signature of its interface. The parameter and return types have to match | ||
| 889 | * exactly, because the default method is called through the vtable slot of the interface method. | ||
| 890 | * | ||
| 891 | * @param defaultMethod Default method to check | ||
| 892 | */ | ||
| 893 | 10 | p TypeChecker.checkDefaultMethodImplementsInterfaceMethod(const Function* defaultMethod) { | |
| 894 | 10 | const Interface* spiceInterface = defaultMethod.thisType.getInterface(defaultMethod.declNode); | |
| 895 | 10 | assert spiceInterface != nil<const Interface*>; | |
| 896 | 10 | const QualTypeList paramTypes = defaultMethod.getParamTypes(); | |
| 897 | 20 | foreach const Function* method : spiceInterface.methods { | |
| 898 | 19 | if method.name == defaultMethod.name && method.getParamTypes() == paramTypes && method.returnType == defaultMethod.returnType { | |
| 899 | 9 | return; | |
| 900 | } | ||
| 901 | 10 | } | |
| 902 | 1 | throwSemanticError(defaultMethod.declNode, SemanticErrorType::INVALID_DEFAULT_METHOD, "The default method '" + defaultMethod.getSignature() + "' does not implement any method of the interface '" + spiceInterface.structBase.name + "'"); | |
| 903 | 10 | } | |
| 904 | |||
| 905 | 6085 | f<Any> TypeChecker.visitStructDefCheck(ASTStructDefNode* node) { | |
| 906 | 6085 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 907 | 6085 | baseNode.resizeToNumberOfManifestations(node.structManifestations.getSize()); | |
| 908 | 6085 | this.manIdx = 0l; // Reset the manifestation index | |
| 909 | |||
| 910 | // Get all manifestations for this procedure definition | ||
| 911 | // Only visit the manifestations, that exist at this point (manifestations inserted during the loop are not covered by the | ||
| 912 | // resize above and get checked in a revisit) | ||
| 913 | 6085 | const unsigned long manifestationCount = node.structManifestations.getSize(); | |
| 914 | 16515 | for unsigned long manifestationIdx = 0l; manifestationIdx < manifestationCount; manifestationIdx++ { | |
| 915 | 10430 | Struct* manifestation = node.structManifestations.get(manifestationIdx); | |
| 916 | // Skip non-substantiated or already checked procedures | ||
| 917 | 10430 | if !manifestation.structBase.isFullySubstantiated() { | |
| 918 | 2352 | this.manIdx++; // Increase the manifestation index | |
| 919 | 2352 | continue; | |
| 920 | } | ||
| 921 | |||
| 922 | // Fallback for manifestations that could not be decided on when they were created, because a by-value struct field | ||
| 923 | // was not manifested yet at that point (circular import). By now the whole import graph is prepared, so the | ||
| 924 | // decision can be taken - late, but still before the struct's own default member bodies are prepared below. | ||
| 925 | 8078 | createImplicitDefaultMembers(*manifestation, baseNode, /*withMoveCtor=*/false); | |
| 926 | |||
| 927 | // Change to struct scope | ||
| 928 | 8078 | this.compilerPass.changeToScope(manifestation.structBase.scope, ScopeType::STRUCT); | |
| 929 | |||
| 930 | // Mount type mapping for this manifestation, so that the body preamble helpers below can substantiate | ||
| 931 | // generic field types (e.g. `heap T*` on `Vector<T>`). Without this, an auto-generated body preamble that | ||
| 932 | // touches a still-generic field would assert in substantiateTypeWithTypeMapping. | ||
| 933 | 8078 | assert this.typeMapping.isEmpty(); | |
| 934 | 8078 | this.typeMapping = manifestation.structBase.typeMapping; | |
| 935 | |||
| 936 | // Re-visit all default values. This is required, since the type of the default value might vary for different manifestations | ||
| 937 | 28739 | foreach IVisitable* fieldVisitable : node.fields { | |
| 938 | 20661 | const ASTFieldNode* field = downcast<ASTFieldNode>(getASTNode(fieldVisitable)); | |
| 939 | 20661 | if field.defaultValue != nil<IVisitable*> { | |
| 940 | 6250 | this.visit(field.defaultValue); | |
| 941 | 6250 | SymbolTableEntry* fieldEntry = manifestation.structBase.scope.lookupStrict(field.fieldName); | |
| 942 | 6250 | assert fieldEntry != nil<SymbolTableEntry*>; | |
| 943 | 6250 | fieldEntry.updateState(LifecycleState::INITIALIZED); | |
| 944 | 6250 | } | |
| 945 | 20661 | } | |
| 946 | |||
| 947 | // Build struct type | ||
| 948 | 8078 | const QualType structType = manifestation.structBase.entry.getQualType(); | |
| 949 | |||
| 950 | // Check if the struct implements all methods of all attached interfaces | ||
| 951 | 8078 | unsigned long vtableIndex = 0l; | |
| 952 | 9741 | foreach const QualType& interfaceType : manifestation.interfaceTypes { | |
| 953 | 1663 | const Interface* spiceInterface = interfaceType.getInterface(baseNode); | |
| 954 | 1663 | assert spiceInterface != nil<const Interface*>; | |
| 955 | |||
| 956 | // Check for all methods, that it is implemented by the struct | ||
| 957 | 5808 | foreach const Function* expMethod : spiceInterface.methods { | |
| 958 | 4145 | const String methodName = expMethod.name; | |
| 959 | 4145 | QualTypeList params = expMethod.getParamTypes(); | |
| 960 | 4145 | QualType returnType = expMethod.returnType; | |
| 961 | |||
| 962 | // Substantiate param and return types | ||
| 963 | 4145 | substantiateTypesWithTypeMapping(params, spiceInterface.structBase.typeMapping, baseNode); | |
| 964 | 4145 | if returnType.hasAnyGenericParts() { | |
| 965 | 2397 | substantiateTypeWithTypeMapping(returnType, spiceInterface.structBase.typeMapping, baseNode); | |
| 966 | 2397 | } | |
| 967 | |||
| 968 | // Build args list | ||
| 969 | 4145 | ArgList args; | |
| 970 | 4145 | args.reserve(params.getSize()); | |
| 971 | 4181 | foreach const QualType& param : params { | |
| 972 | 36 | args.pushBack(Pair<QualType, bool>(param, false)); | |
| 973 | 36 | } | |
| 974 | |||
| 975 | // Search for method that has the required signature | ||
| 976 | 4145 | const QualTypeList noTemplateTypeHints; | |
| 977 | 4145 | Function* spiceFunction = matchFunction(this.currentScope, methodName, structType, args, noTemplateTypeHints, true, baseNode); | |
| 978 | // If the struct does not implement the method itself, fall back to the default method of the interface | ||
| 979 | 4145 | if spiceFunction == nil<Function*> { | |
| 980 | 5 | spiceFunction = inheritDefaultMethod(this.currentScope, structType, spiceInterface, methodName, params, returnType); | |
| 981 | 5 | } | |
| 982 | 4145 | if spiceFunction == nil<Function*> { | |
| 983 | 5 | this.softError(baseNode, SemanticErrorType::INTERFACE_METHOD_NOT_IMPLEMENTED, "The struct '" + node.structName + "' does not implement method '" + expMethod.getSignature() + "'."); | |
| 984 | 5 | continue; | |
| 985 | 5 | } | |
| 986 | |||
| 987 | // The default methods of two interfaces must not implement the same method of the struct | ||
| 988 | 4140 | if spiceFunction.isInheritedDefaultMethod() && spiceFunction.defaultMethod.thisType.getBodyScope() != interfaceType.getBodyScope() { | |
| 989 | 1 | this.softError(baseNode, SemanticErrorType::INTERFACE_METHOD_NOT_IMPLEMENTED, "The struct '" + node.structName + "' implements multiple interfaces with method '" + expMethod.getSignature() + "', which has a default implementation. Please implement it in the struct."); | |
| 990 | 1 | continue; | |
| 991 | 1 | } | |
| 992 | |||
| 993 | // Check return type | ||
| 994 | 4139 | if spiceFunction.returnType != returnType && !returnType.matchesInterfaceImplementedByStruct(spiceFunction.returnType) { | |
| 995 | 2 | this.softError(baseNode, SemanticErrorType::INTERFACE_METHOD_NOT_IMPLEMENTED, "The struct '" + node.structName + "' does not implement method '" + expMethod.getSignature() + "'. The return type does not match."); | |
| 996 | 2 | continue; | |
| 997 | 2 | } | |
| 998 | // Set to virtual, since it overrides the interface method | ||
| 999 | 4137 | spiceFunction.isVirtual = true; | |
| 1000 | 4137 | spiceFunction.vtableIndex = vtableIndex++; | |
| 1001 | 4145 | } | |
| 1002 | 1663 | } | |
| 1003 | |||
| 1004 | // Check default ctor body if required | ||
| 1005 | 8078 | const ArgList noArgs; | |
| 1006 | 8078 | const Function* ctorFunc = lookupFunction(this.currentScope, String(CTOR_FUNCTION_NAME), structType, noArgs, true); | |
| 1007 | 8078 | if ctorFunc != nil<const Function*> && ctorFunc.implicitDefault { | |
| 1008 | 332 | this.createCtorBodyPreamble(ctorFunc.bodyScope); | |
| 1009 | 332 | assert manifestation.areAllFieldsInitialized() == nil<SymbolTableEntry*>; | |
| 1010 | 332 | } | |
| 1011 | |||
| 1012 | // Check default copy ctor body if required | ||
| 1013 | 8078 | ArgList args; | |
| 1014 | 8078 | args.pushBack(Pair<QualType, bool>(structType.toConstRef(baseNode), false /* always non-temporary */)); | |
| 1015 | 8078 | const Function* copyCtorFunc = lookupFunction(this.currentScope, String(CTOR_FUNCTION_NAME), structType, args, true); | |
| 1016 | 8078 | if copyCtorFunc != nil<const Function*> && copyCtorFunc.implicitDefault { | |
| 1017 | 1272 | this.createCopyCtorBodyPreamble(copyCtorFunc.bodyScope); | |
| 1018 | 1272 | assert manifestation.areAllFieldsInitialized() == nil<SymbolTableEntry*>; | |
| 1019 | 1272 | } | |
| 1020 | |||
| 1021 | // Check default move ctor body if required. findMoveCtor scans the manifestations directly to avoid the | ||
| 1022 | // constify-based false-positive that lookupFunction with a non-const ref arg can produce. | ||
| 1023 | 8078 | const Function* moveCtorFunc = findMoveCtor(this.currentScope); | |
| 1024 | 8078 | if moveCtorFunc != nil<const Function*> && moveCtorFunc.implicitDefault { | |
| 1025 | 36 | this.createMoveCtorBodyPreamble(moveCtorFunc.bodyScope); | |
| 1026 | 36 | assert manifestation.areAllFieldsInitialized() == nil<SymbolTableEntry*>; | |
| 1027 | 36 | } | |
| 1028 | |||
| 1029 | // Check default dtor body if required | ||
| 1030 | 8078 | const Function* dtorFunc = lookupFunction(this.currentScope, String(DTOR_FUNCTION_NAME), structType, noArgs, true); | |
| 1031 | 8078 | if dtorFunc != nil<const Function*> && dtorFunc.implicitDefault { | |
| 1032 | 1146 | this.createDtorBodyPreamble(dtorFunc.bodyScope, baseNode); | |
| 1033 | 1146 | } | |
| 1034 | |||
| 1035 | // Reset field symbols to declared state for the next manifestation | ||
| 1036 | 8078 | manifestation.resetFieldSymbolsToDeclared(); | |
| 1037 | |||
| 1038 | // Clear type mapping | ||
| 1039 | 8078 | this.typeMapping.clear(); | |
| 1040 | |||
| 1041 | // Return to the root scope | ||
| 1042 | 8078 | this.currentScope = this.rootScope; | |
| 1043 | 8078 | assert this.currentScope != nil<Scope*> && this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1044 | |||
| 1045 | 8078 | this.manIdx++; // Increase the manifestation index | |
| 1046 | 8078 | } | |
| 1047 | 6085 | this.manIdx = 0l; // Reset the manifestation index | |
| 1048 | |||
| 1049 | 6085 | return Any(); | |
| 1050 | } | ||
| 1051 | |||
| 1052 | 22 | f<Any> TypeChecker.visitUnionDefCheck(ASTUnionDefNode* node) { | |
| 1053 | 22 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 1054 | 22 | baseNode.resizeToNumberOfManifestations(node.unionManifestations.getSize()); | |
| 1055 | 22 | this.manIdx = 0l; // Reset the manifestation index | |
| 1056 | |||
| 1057 | // Get all manifestations for this union definition | ||
| 1058 | // Only visit the manifestations, that exist at this point (manifestations inserted during the loop are not covered by the | ||
| 1059 | // resize above and get checked in a revisit) | ||
| 1060 | 22 | const unsigned long manifestationCount = node.unionManifestations.getSize(); | |
| 1061 | 46 | for unsigned long manifestationIdx = 0l; manifestationIdx < manifestationCount; manifestationIdx++ { | |
| 1062 | 24 | Union* manifestation = node.unionManifestations.get(manifestationIdx); | |
| 1063 | // Skip non-substantiated manifestations | ||
| 1064 | 24 | if !manifestation.structBase.isFullySubstantiated() { | |
| 1065 | 4 | this.manIdx++; // Increase the manifestation index | |
| 1066 | 4 | continue; | |
| 1067 | } | ||
| 1068 | |||
| 1069 | // Change to union scope | ||
| 1070 | 20 | this.compilerPass.changeToScope(manifestation.structBase.scope, ScopeType::UNION); | |
| 1071 | |||
| 1072 | // Mount type mapping for this manifestation, so that the default value below can substantiate generic field types | ||
| 1073 | 20 | assert this.typeMapping.isEmpty(); | |
| 1074 | 20 | this.typeMapping = manifestation.structBase.typeMapping; | |
| 1075 | |||
| 1076 | // Re-visit the default value (at most one field may have one). This is required, since the type of the default | ||
| 1077 | // value might vary for different manifestations | ||
| 1078 | 64 | foreach IVisitable* fieldVisitable : node.fields { | |
| 1079 | 44 | const ASTFieldNode* field = downcast<ASTFieldNode>(getASTNode(fieldVisitable)); | |
| 1080 | 44 | if field.defaultValue != nil<IVisitable*> { | |
| 1081 | 4 | this.visit(field.defaultValue); | |
| 1082 | 4 | SymbolTableEntry* fieldEntry = manifestation.structBase.scope.lookupStrict(field.fieldName); | |
| 1083 | 4 | assert fieldEntry != nil<SymbolTableEntry*>; | |
| 1084 | 4 | fieldEntry.updateState(LifecycleState::INITIALIZED); | |
| 1085 | 4 | } | |
| 1086 | 44 | } | |
| 1087 | |||
| 1088 | // Clear type mapping | ||
| 1089 | 20 | this.typeMapping.clear(); | |
| 1090 | |||
| 1091 | // Return to the root scope | ||
| 1092 | 20 | this.currentScope = this.rootScope; | |
| 1093 | 20 | assert this.currentScope != nil<Scope*> && this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1094 | |||
| 1095 | 20 | this.manIdx++; // Increase the manifestation index | |
| 1096 | 20 | } | |
| 1097 | 22 | this.manIdx = 0l; // Reset the manifestation index | |
| 1098 | |||
| 1099 | 22 | return Any(); | |
| 1100 | } | ||
| 1101 | |||
| 1102 | // ============================================ Top level definitions (prepare stage) ============================================ | ||
| 1103 | |||
| 1104 | 974 | f<Any> TypeChecker.visitMainFctDefPrepare(ASTMainFctDefNode* node) { | |
| 1105 | // Mark unreachable statements | ||
| 1106 | 974 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 1107 | 974 | bool doSetPredecessorsUnreachable = true; | |
| 1108 | 974 | const bool _ = baseNode.returnsOnAllControlPaths(&doSetPredecessorsUnreachable, this.manIdx); | |
| 1109 | |||
| 1110 | // Retrieve return type | ||
| 1111 | 974 | const QualType returnType = QualType(SuperType::TY_INT); | |
| 1112 | |||
| 1113 | // Change to function body scope | ||
| 1114 | 974 | this.currentScope = node.bodyScope; | |
| 1115 | |||
| 1116 | // Set type of 'result' variable to int | ||
| 1117 | 974 | SymbolTableEntry* resultEntry = this.currentScope.lookupStrict(String(RETURN_VARIABLE_NAME)); | |
| 1118 | 974 | assert resultEntry != nil<SymbolTableEntry*>; | |
| 1119 | 974 | resultEntry.updateType(returnType, false); | |
| 1120 | 974 | resultEntry.used = true; | |
| 1121 | |||
| 1122 | // Retrieve param types | ||
| 1123 | 974 | QualTypeList paramTypes; | |
| 1124 | 974 | if node.takesArgs { | |
| 1125 | 7 | Any paramLstResult = this.visit(node.paramLst); | |
| 1126 | 7 | const ParamList visitedParams = paramLstResult.get<ParamList>(); | |
| 1127 | 21 | foreach const Param& visitedParam : visitedParams { | |
| 1128 | 14 | paramTypes.pushBack(visitedParam.qualType); | |
| 1129 | 14 | } | |
| 1130 | 7 | } | |
| 1131 | |||
| 1132 | // Prepare type of function | ||
| 1133 | 974 | const QualType functionTypeBase = QualType(SuperType::TY_FUNCTION); | |
| 1134 | 974 | const QualType functionType = functionTypeBase.getWithFunctionParamAndReturnTypes(returnType, paramTypes); | |
| 1135 | |||
| 1136 | // Update main function symbol type | ||
| 1137 | 974 | SymbolTableEntry* functionEntry = this.rootScope.lookupStrict(String(MAIN_FUNCTION_NAME)); | |
| 1138 | 974 | assert functionEntry != nil<SymbolTableEntry*>; | |
| 1139 | 974 | functionEntry.updateType(functionType, false); | |
| 1140 | 974 | functionEntry.used = true; | |
| 1141 | |||
| 1142 | // Leave main function body scope | ||
| 1143 | 974 | this.currentScope = this.rootScope; | |
| 1144 | |||
| 1145 | 1948 | return Any(); | |
| 1146 | 974 | } | |
| 1147 | |||
| 1148 | 28089 | f<Any> TypeChecker.visitFctDefPrepare(ASTFctDefNode* node) { | |
| 1149 | 56178 | return this.prepareFctOrProcDef(&node.node, cast<ASTNode*>(node), node.getScopeId(), node.returnType); | |
| 1150 | 28089 | } | |
| 1151 | |||
| 1152 | 17620 | f<Any> TypeChecker.visitProcDefPrepare(ASTProcDefNode* node) { | |
| 1153 | 35240 | return this.prepareFctOrProcDef(&node.node, cast<ASTNode*>(node), node.getScopeId(), nil<ASTDataTypeNode*>); | |
| 1154 | 17620 | } | |
| 1155 | |||
| 1156 | /** | ||
| 1157 | * Prepare a function or procedure definition: check its signature, build the function object and create the body scopes | ||
| 1158 | * of its manifestations | ||
| 1159 | * | ||
| 1160 | * @param fctDefBase Function or procedure definition node | ||
| 1161 | * @param baseNode AST node of the definition | ||
| 1162 | * @param scopeId Scope id of the definition | ||
| 1163 | * @param returnTypeNode Return type of a function, nil for a procedure | ||
| 1164 | */ | ||
| 1165 | 45706 | f<Any> TypeChecker.prepareFctOrProcDef(ASTFctDefBaseNode* fctDefBase, ASTNode* baseNode, const String& scopeId, ASTDataTypeNode* returnTypeNode) { | |
| 1166 | 45706 | const bool isFunction = returnTypeNode != nil<ASTDataTypeNode*>; | |
| 1167 | 45706 | const string kind = isFunction ? "function" : "procedure"; | |
| 1168 | |||
| 1169 | // Check if name is dtor | ||
| 1170 | 45706 | if isFunction && fctDefBase.name.name == DTOR_FUNCTION_NAME { | |
| 1171 | 1 | this.softError(baseNode, SemanticErrorType::DTOR_MUST_BE_PROCEDURE, String("Destructors are not allowed to be of type function")); | |
| 1172 | 2 | return Any(); | |
| 1173 | 1 | } | |
| 1174 | |||
| 1175 | // Mark unreachable statements and check if all control paths in a function return | ||
| 1176 | 45705 | bool doSetPredecessorsUnreachable = true; | |
| 1177 | 45705 | const bool returnsOnAllControlPaths = baseNode.returnsOnAllControlPaths(&doSetPredecessorsUnreachable, this.manIdx); | |
| 1178 | 45705 | if isFunction && !returnsOnAllControlPaths { | |
| 1179 | 3 | this.softError(baseNode, SemanticErrorType::MISSING_RETURN_STMT, String("Not all control paths of this function have a return statement")); | |
| 1180 | 6 | return Any(); | |
| 1181 | 3 | } | |
| 1182 | |||
| 1183 | // Check if dtor and has params | ||
| 1184 | 45702 | if !isFunction && fctDefBase.hasParams && fctDefBase.name.name == DTOR_FUNCTION_NAME { | |
| 1185 | 1 | throwSemanticError(baseNode, SemanticErrorType::DTOR_WITH_PARAMS, String("It is not allowed to specify parameters for destructors")); | |
| 1186 | 2 | return Any(); | |
| 1187 | 1 | } | |
| 1188 | |||
| 1189 | // Change to function/procedure scope | ||
| 1190 | 45701 | this.currentScope = fctDefBase.scope; | |
| 1191 | 45701 | assert this.currentScope.scopeType == ScopeType::FUNC_PROC_BODY; | |
| 1192 | |||
| 1193 | // Retrieve template types | ||
| 1194 | 45701 | Vector<GenericType> usedGenericTypes; | |
| 1195 | 45701 | if fctDefBase.hasTemplateTypes { | |
| 1196 | 6884 | if !this.collectTemplateTypes(fctDefBase.templateTypeLst, usedGenericTypes) { | |
| 1197 | 4 | return Any(); | |
| 1198 | } | ||
| 1199 | 6882 | } | |
| 1200 | |||
| 1201 | // Retrieve 'this' type | ||
| 1202 | 45699 | QualType thisType = QualType(SuperType::TY_DYN); // If the function/procedure is not a method, the default this type is TY_DYN | |
| 1203 | 45699 | if fctDefBase.isMethod { | |
| 1204 | 26578 | Scope* structParentScope = fctDefBase.structScope.parent; | |
| 1205 | 26578 | SymbolTableEntry* structEntry = structParentScope.lookupStrict(fctDefBase.name.structName); | |
| 1206 | 26578 | assert structEntry != nil<SymbolTableEntry*>; | |
| 1207 | // Set struct to used | ||
| 1208 | 26578 | structEntry.used = true; | |
| 1209 | // Get type and ptr type | ||
| 1210 | 26578 | thisType = structEntry.getQualType(); | |
| 1211 | 26578 | const QualType thisPtrType = thisType.toPtr(baseNode); | |
| 1212 | // Collect template types of 'this' type | ||
| 1213 | 26578 | const QualTypeList thisTemplateTypes = thisType.getTemplateTypes(); | |
| 1214 | 37108 | foreach const QualType& templateType : thisTemplateTypes { | |
| 1215 | 10530 | addUsedTemplateType(usedGenericTypes, templateType); | |
| 1216 | 10530 | } | |
| 1217 | |||
| 1218 | // Set type of 'this' variable | ||
| 1219 | 26578 | SymbolTableEntry* thisEntry = this.currentScope.lookupStrict(String(THIS_VARIABLE_NAME)); | |
| 1220 | 26578 | assert thisEntry != nil<SymbolTableEntry*>; | |
| 1221 | 26578 | thisEntry.updateType(thisPtrType, false); | |
| 1222 | 26578 | } | |
| 1223 | |||
| 1224 | // Visit parameters | ||
| 1225 | 45699 | QualTypeList paramTypes; | |
| 1226 | 45699 | ParamList paramList; | |
| 1227 | 45699 | if fctDefBase.hasParams { | |
| 1228 | // Visit param list to retrieve the params | ||
| 1229 | 33035 | Any paramLstResult = this.visit(fctDefBase.paramLst); | |
| 1230 | 33035 | const ParamList visitedParams = paramLstResult.get<ParamList>(); | |
| 1231 | 86964 | foreach const Param& visitedParam : visitedParams { | |
| 1232 | 53935 | const QualType qualType = visitedParam.qualType; | |
| 1233 | 53935 | if qualType.is(SuperType::TY_UNRESOLVED) { | |
| 1234 | 6 | return Any(); | |
| 1235 | } | ||
| 1236 | 53932 | paramTypes.pushBack(qualType); | |
| 1237 | 53932 | paramList.pushBack(Param{qualType, visitedParam.isOptional}); | |
| 1238 | // Check if the type is present in the template for generic types | ||
| 1239 | 53932 | if !qualType.isCoveredByGenericTypeList(usedGenericTypes) { | |
| 1240 | 3 | const String message = String("Generic param type not included in the template type list of the ") + kind; | |
| 1241 | 3 | throwSemanticError(getASTNode(fctDefBase.paramLst), SemanticErrorType::GENERIC_TYPE_NOT_IN_TEMPLATE, message); | |
| 1242 | 6 | return Any(); | |
| 1243 | 3 | } | |
| 1244 | 53929 | } | |
| 1245 | 33035 | } | |
| 1246 | |||
| 1247 | // Retrieve return type. Procedures have no return type, which is represented by TY_DYN | ||
| 1248 | 45693 | QualType returnType = QualType(SuperType::TY_DYN); | |
| 1249 | 45693 | if isFunction { | |
| 1250 | 28080 | returnType = this.getDataType(returnTypeNode); | |
| 1251 | 28080 | if returnType.is(SuperType::TY_UNRESOLVED) { | |
| 1252 | ✗ | return Any(); | |
| 1253 | } | ||
| 1254 | 28080 | if returnType.is(SuperType::TY_DYN) { | |
| 1255 | 2 | this.softError(baseNode, SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Dyn return types are not allowed")); | |
| 1256 | 4 | return Any(); | |
| 1257 | 2 | } | |
| 1258 | 28078 | if !returnType.isCoveredByGenericTypeList(usedGenericTypes) { | |
| 1259 | 1 | this.softError(cast<ASTNode*>(returnTypeNode), SemanticErrorType::GENERIC_TYPE_NOT_IN_TEMPLATE, String("Generic return type not included in the template type list of the function")); | |
| 1260 | 2 | return Any(); | |
| 1261 | 1 | } | |
| 1262 | 28077 | } | |
| 1263 | |||
| 1264 | // Check if the function/procedure is a valid default method, if it is defined as method of an interface | ||
| 1265 | 45690 | if thisType.is(SuperType::TY_INTERFACE) && !this.checkDefaultMethod(fctDefBase, thisType, paramList) { | |
| 1266 | 3 | this.currentScope = this.rootScope; | |
| 1267 | 6 | return Any(); | |
| 1268 | } | ||
| 1269 | |||
| 1270 | // Leave function/procedure body scope | ||
| 1271 | 45687 | this.currentScope = fctDefBase.scope.parent; | |
| 1272 | 45687 | assert this.currentScope.scopeType == ScopeType::GLOBAL || this.currentScope.scopeType == ScopeType::STRUCT || this.currentScope.scopeType == ScopeType::INTERFACE; | |
| 1273 | |||
| 1274 | // Prepare type of function/procedure | ||
| 1275 | 45687 | const QualType functionTypeBase = QualType(isFunction ? SuperType::TY_FUNCTION : SuperType::TY_PROCEDURE); | |
| 1276 | 45687 | QualType functionType = functionTypeBase.getWithFunctionParamAndReturnTypes(returnType, paramTypes); | |
| 1277 | 45687 | functionType.setQualifiers(fctDefBase.qualifiers); | |
| 1278 | |||
| 1279 | // Update type of function/procedure entry | ||
| 1280 | 45687 | const String entryName = fctDefBase.getSymbolTableEntryName(); | |
| 1281 | 45687 | SymbolTableEntry* functionEntry = this.currentScope.lookupStrict(entryName); | |
| 1282 | 45687 | assert functionEntry != nil<SymbolTableEntry*>; | |
| 1283 | 45687 | functionEntry.updateType(functionType, false); | |
| 1284 | |||
| 1285 | // Build function object | ||
| 1286 | 45687 | Function spiceFunc = Function(fctDefBase.name.name, functionEntry, thisType, returnType, paramList, usedGenericTypes, baseNode); | |
| 1287 | 45687 | spiceFunc.bodyScope = fctDefBase.scope; | |
| 1288 | 45687 | spiceFunc.isInterfaceDefaultMethod = thisType.is(SuperType::TY_INTERFACE); | |
| 1289 | 45687 | insertFunction(this.currentScope, spiceFunc, &fctDefBase.manifestations); | |
| 1290 | // Stop at a duplicate declaration, the compilation is aborted after this stage | ||
| 1291 | 45687 | const ErrorManager& errorManager = this.resourceManager.getErrorManager(); | |
| 1292 | 45687 | if errorManager.hasHardError { | |
| 1293 | 2 | this.currentScope = this.rootScope; | |
| 1294 | 4 | return Any(); | |
| 1295 | } | ||
| 1296 | |||
| 1297 | // Check function/procedure attributes | ||
| 1298 | 45685 | if fctDefBase.attrs != nil<IVisitable*> { | |
| 1299 | 1114 | const ASTTopLevelDefAttrNode* attrs = downcast<ASTTopLevelDefAttrNode>(fctDefBase.attrs); | |
| 1300 | 1114 | ASTAttrLstNode* attrLst = attrs.attrLst; | |
| 1301 | 1114 | Function* firstManifestation = fctDefBase.manifestations.front(); | |
| 1302 | 1114 | if attrLst.hasAttr(String(ATTR_CORE_COMPILER_MANGLE)) { | |
| 1303 | 2 | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_CORE_COMPILER_MANGLE)); | |
| 1304 | 2 | firstManifestation.mangleFunctionName = value.getBoolValue(); | |
| 1305 | 2 | } | |
| 1306 | 1114 | if attrLst.hasAttr(String(ATTR_CORE_COMPILER_MANGLED_NAME)) { | |
| 1307 | 1 | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_CORE_COMPILER_MANGLED_NAME)); | |
| 1308 | 1 | firstManifestation.predefinedMangledName = this.resourceManager.getCompileTimeStringValue(value.getStringValueOffset()); | |
| 1309 | 1 | } | |
| 1310 | // The TPDE backend always uses the red zone in x86_64 leaf functions (see the --no-red-zone check in the driver) | ||
| 1311 | 1114 | if attrLst.hasAttr(String(ATTR_CORE_COMPILER_RED_ZONE)) { | |
| 1312 | 5 | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_CORE_COMPILER_RED_ZONE)); | |
| 1313 | 5 | if !value.getBoolValue() && this.cliOptions.backend == Backend::TPDE && this.cliOptions.targetArch == "x86_64" { | |
| 1314 | 1 | throwSemanticError(getASTNode(attrLst), SemanticErrorType::UNSUPPORTED_ATTR_VALUE, String("The TPDE backend does not support disabling the red zone on x86_64")); | |
| 1315 | 2 | return Any(); | |
| 1316 | 1 | } | |
| 1317 | 5 | } | |
| 1318 | 1113 | if isFunction && attrLst.hasAttr(String(ATTR_TEST)) { | |
| 1319 | 38 | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_TEST)); | |
| 1320 | 38 | if value.getBoolValue() { | |
| 1321 | // Make sure that the function has the correct signature | ||
| 1322 | 36 | if fctDefBase.hasParams { | |
| 1323 | 1 | throwSemanticError(getASTNode(fctDefBase.paramLst), SemanticErrorType::TEST_FUNCTION_WITH_PARAMS, String("Test function may not have parameters")); | |
| 1324 | 2 | return Any(); | |
| 1325 | 1 | } | |
| 1326 | 35 | if !returnType.is(SuperType::TY_BOOL) { | |
| 1327 | 1 | throwSemanticError(cast<ASTNode*>(returnTypeNode), SemanticErrorType::TEST_FUNCTION_WRONG_RETURN_TYPE, String("Test function must return a bool")); | |
| 1328 | 2 | return Any(); | |
| 1329 | 1 | } | |
| 1330 | // Add to test function list | ||
| 1331 | 34 | firstManifestation.entry.used = true; // Avoid printing unused warnings | |
| 1332 | // Only keep test functions, when the test main calls them implicitly. Otherwise, they are not emitted at all | ||
| 1333 | 34 | if this.cliOptions.generateTestMain { | |
| 1334 | 21 | firstManifestation.used = true; | |
| 1335 | 21 | } | |
| 1336 | 34 | const Function* testFunction = firstManifestation; | |
| 1337 | 34 | this.sourceFile.testFunctions.pushBack(testFunction); | |
| 1338 | 34 | } | |
| 1339 | 38 | } | |
| 1340 | 1111 | } | |
| 1341 | |||
| 1342 | // Duplicate / rename the original child scope to reflect the substantiated versions of the function/procedure | ||
| 1343 | 48367 | for unsigned long i = 1l; i < fctDefBase.manifestations.getSize(); i++ { | |
| 1344 | 2685 | Function* manifestation = fctDefBase.manifestations.get(i); | |
| 1345 | 2685 | manifestation.bodyScope = this.currentScope.copyChildScope(scopeId, manifestation.getScopeName()); | |
| 1346 | 2685 | } | |
| 1347 | 45682 | Function* firstManifestation = fctDefBase.manifestations.front(); | |
| 1348 | 45682 | this.currentScope.renameChildScope(scopeId, firstManifestation.getScopeName()); | |
| 1349 | |||
| 1350 | // Change to the root scope | ||
| 1351 | 45682 | this.currentScope = this.rootScope; | |
| 1352 | 45682 | assert this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1353 | |||
| 1354 | 91364 | return Any(); | |
| 1355 | 45701 | } | |
| 1356 | |||
| 1357 | /** | ||
| 1358 | * Determine whether a field type (transitively) contains a struct or union with the given origin body scope by value, | ||
| 1359 | * which would give the origin struct/union an infinite size. Only by-value struct/union fields are followed, including | ||
| 1360 | * through fixed-size arrays thereof (e.g. `T[3]`), since those embed their element by value as well. Dynamic arrays, | ||
| 1361 | * pointers and references break the cycle and have a fixed size. Struct/union types that are not manifested yet (e.g. | ||
| 1362 | * still being prepared as part of a circular import) end a branch - such a cycle is still detected once the last one | ||
| 1363 | * in it is prepared, when all the others are manifested. | ||
| 1364 | * | ||
| 1365 | * @param fieldType Type of the field to inspect | ||
| 1366 | * @param originScope Body scope of the struct/union whose infinite size we are checking for | ||
| 1367 | * @param node Accessing AST node (for struct/union lookup diagnostics) | ||
| 1368 | * @param visited Already-visited struct/union scopes, to keep the walk finite | ||
| 1369 | * @return true if the origin struct/union is reachable by value, i.e. it has infinite size | ||
| 1370 | */ | ||
| 1371 | 11884 | f<bool> fieldContainsAggregateByValue(const QualType& fieldType, const Scope* originScope, const ASTNode* node, Vector<const Scope*>& visited) { | |
| 1372 | // Unwrap fixed-size arrays, so that e.g. a `T[3]` field is treated like a `T` field for cycle detection. Dynamic | ||
| 1373 | // arrays, pointers and references keep breaking the cycle, since they do not embed the element by value. | ||
| 1374 | 11884 | if fieldType.isArray() && fieldType.getArraySize() > 0u { | |
| 1375 | 367 | const QualType containedType = fieldType.getContained(); | |
| 1376 | 367 | return fieldContainsAggregateByValue(containedType, originScope, node, visited); | |
| 1377 | } | ||
| 1378 | 11517 | const dyn aggregateTypes = [SuperType::TY_STRUCT, SuperType::TY_UNION]; | |
| 1379 | 11517 | if !fieldType.isOneOf(aggregateTypes, len(aggregateTypes)) { | |
| 1380 | 10121 | return false; | |
| 1381 | } | ||
| 1382 | 1396 | const Scope* fieldScope = fieldType.getBodyScope(); | |
| 1383 | 1396 | if fieldScope == originScope { | |
| 1384 | 4 | return true; | |
| 1385 | } | ||
| 1386 | 1392 | if fieldScope == nil<const Scope*> { | |
| 1387 | ✗ | return false; | |
| 1388 | } | ||
| 1389 | 2945 | foreach const Scope* visitedScope : visited { | |
| 1390 | 1723 | if visitedScope == fieldScope { | |
| 1391 | 170 | return false; | |
| 1392 | } | ||
| 1393 | 1553 | } | |
| 1394 | 1222 | visited.pushBack(fieldScope); | |
| 1395 | |||
| 1396 | 1222 | QualTypeList memberTypes; | |
| 1397 | 1222 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 1398 | 1218 | const Struct* spiceStruct = fieldType.getStruct(node); | |
| 1399 | 1218 | if spiceStruct == nil<const Struct*> { | |
| 1400 | 1 | return false; // Not manifested yet; the cycle is caught once the last aggregate in it is prepared | |
| 1401 | } | ||
| 1402 | 1217 | memberTypes = spiceStruct.fieldTypes; | |
| 1403 | 1217 | } else { | |
| 1404 | 4 | const Union* spiceUnion = fieldType.getUnion(node); | |
| 1405 | 4 | if spiceUnion == nil<const Union*> { | |
| 1406 | ✗ | return false; // Not manifested yet; the cycle is caught once the last aggregate in it is prepared | |
| 1407 | } | ||
| 1408 | 4 | memberTypes = spiceUnion.fieldTypes; | |
| 1409 | 4 | } | |
| 1410 | |||
| 1411 | 4902 | foreach const QualType& memberType : memberTypes { | |
| 1412 | 3682 | if fieldContainsAggregateByValue(memberType, originScope, node, visited) { | |
| 1413 | 1 | return true; | |
| 1414 | } | ||
| 1415 | 3681 | } | |
| 1416 | 1220 | return false; | |
| 1417 | 1222 | } | |
| 1418 | |||
| 1419 | 3223 | f<Any> TypeChecker.visitStructDefPrepare(ASTStructDefNode* node) { | |
| 1420 | 3223 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 1421 | 3223 | QualTypeList usedTemplateTypes; | |
| 1422 | 3223 | Vector<GenericType> templateTypesGeneric; | |
| 1423 | |||
| 1424 | // Retrieve struct template types | ||
| 1425 | 3223 | if node.hasTemplateTypes { | |
| 1426 | 923 | if !this.collectTemplateTypes(node.templateTypeLst, templateTypesGeneric) { | |
| 1427 | ✗ | return Any(); | |
| 1428 | } | ||
| 1429 | 2149 | foreach const GenericType& genericType : templateTypesGeneric { | |
| 1430 | 1226 | usedTemplateTypes.pushBack(genericType.qualType); | |
| 1431 | 1226 | } | |
| 1432 | 923 | } | |
| 1433 | |||
| 1434 | // Retrieve implemented interfaces | ||
| 1435 | 3223 | QualTypeList interfaceTypes; | |
| 1436 | 3223 | if node.hasInterfaces { | |
| 1437 | 845 | foreach ASTDataTypeNode* interfaceNode : node.interfaceTypeLst.dataTypes { | |
| 1438 | // Visit interface type | ||
| 1439 | 424 | const QualType interfaceType = this.getDataType(interfaceNode); | |
| 1440 | 424 | if interfaceType.is(SuperType::TY_UNRESOLVED) { | |
| 1441 | 1 | continue; | |
| 1442 | } | ||
| 1443 | // Check if it is an interface type | ||
| 1444 | 423 | if !interfaceType.is(SuperType::TY_INTERFACE) { | |
| 1445 | 1 | throwSemanticError(cast<ASTNode*>(interfaceNode), SemanticErrorType::EXPECTED_INTERFACE_TYPE, "Expected interface type, got " + interfaceType.getName(false)); | |
| 1446 | 2 | return Any(); | |
| 1447 | 1 | } | |
| 1448 | // Check for visibility | ||
| 1449 | 422 | Scope* interfaceScope = interfaceType.getBodyScope(); | |
| 1450 | 422 | if interfaceScope.isImportedBy(this.rootScope) && !interfaceType.isPublic() { | |
| 1451 | 1 | throwSemanticError(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, "Cannot access interface '" + interfaceType.getSubType() + "' due to its private visibility"); | |
| 1452 | 2 | return Any(); | |
| 1453 | 1 | } | |
| 1454 | // Add to interface types | ||
| 1455 | 421 | interfaceTypes.pushBack(interfaceType); | |
| 1456 | // Update the type of the entry for that interface field | ||
| 1457 | 421 | ASTCustomDataTypeNode* customDataType = interfaceNode.baseDataType.customDataType; | |
| 1458 | 421 | const String& interfaceName = customDataType.typeNameFragments.back(); | |
| 1459 | 421 | SymbolTableEntry* interfaceEntry = node.structScope.lookupStrict("this." + interfaceName); | |
| 1460 | 421 | assert interfaceEntry != nil<SymbolTableEntry*>; | |
| 1461 | 421 | interfaceEntry.updateType(interfaceType, false); | |
| 1462 | 421 | } | |
| 1463 | 421 | } | |
| 1464 | |||
| 1465 | // Update type of struct entry | ||
| 1466 | 3221 | assert node.entry != nil<SymbolTableEntry*>; | |
| 1467 | 3221 | TypeChainElementData data; | |
| 1468 | 3221 | data.bodyScope = node.structScope; | |
| 1469 | 3221 | const Type* rawType = this.resourceManager.typeRegistry.getOrInsert(SuperType::TY_STRUCT, node.structName, node.typeId, data, usedTemplateTypes); | |
| 1470 | // If the entry was implicitly forward-declared because it is referenced across a circular import | ||
| 1471 | // (assignDeferredOpaqueType), the existing type was already set to the opaque struct type. Overwriting it with the | ||
| 1472 | // identical interned type is expected here. | ||
| 1473 | 3221 | const QualType existingType = node.entry.getQualType(); | |
| 1474 | 3221 | const bool overwrite = existingType.is(SuperType::TY_STRUCT); | |
| 1475 | 3221 | node.entry.updateType(QualType(rawType, node.qualifiers), overwrite); | |
| 1476 | |||
| 1477 | // Change to struct scope | ||
| 1478 | 3221 | this.currentScope = node.structScope; | |
| 1479 | 3221 | assert this.currentScope.scopeType == ScopeType::STRUCT; | |
| 1480 | |||
| 1481 | // Retrieve field types | ||
| 1482 | 3221 | QualTypeList fieldTypes; | |
| 1483 | // Shared across all fields: the origin (node.structScope) is constant, so a struct already proven not to reach it | ||
| 1484 | // via one field need not be re-walked for another. | ||
| 1485 | 3221 | Vector<const Scope*> visitedScopes; | |
| 1486 | 11001 | foreach IVisitable* fieldVisitable : node.fields { | |
| 1487 | 7783 | const ASTFieldNode* field = downcast<ASTFieldNode>(fieldVisitable); | |
| 1488 | 7783 | ASTNode* fieldBaseNode = cast<ASTNode*>(field); | |
| 1489 | |||
| 1490 | // Visit field type | ||
| 1491 | 7783 | const QualType fieldType = this.visitType(fieldVisitable); | |
| 1492 | 7783 | if fieldType.is(SuperType::TY_UNRESOLVED) { | |
| 1493 | 2 | this.sourceFile.checkForSoftErrors(); // We get into trouble if we continue without the field type -> abort | |
| 1494 | 2 | } | |
| 1495 | |||
| 1496 | // Check for struct with infinite size. This happens if a struct (transitively, through by-value struct fields) | ||
| 1497 | // contains itself - either directly (struct A has a field of type A) or through a cycle of structs in mutually | ||
| 1498 | // importing files (A has a field of type B and B has a field of type A). | ||
| 1499 | 7783 | if fieldContainsAggregateByValue(fieldType, node.structScope, fieldBaseNode, visitedScopes) { | |
| 1500 | 2 | throwSemanticError(fieldBaseNode, SemanticErrorType::STRUCT_INFINITE_SIZE, String("Struct with infinite size detected")); | |
| 1501 | 4 | return Any(); | |
| 1502 | 2 | } | |
| 1503 | |||
| 1504 | // Add to field types | ||
| 1505 | 7781 | fieldTypes.pushBack(fieldType); | |
| 1506 | |||
| 1507 | // Update type of field entry | ||
| 1508 | 7781 | SymbolTableEntry* fieldEntry = this.currentScope.lookupStrict(field.fieldName); | |
| 1509 | 7781 | assert fieldEntry != nil<SymbolTableEntry*>; | |
| 1510 | 7781 | fieldEntry.updateType(fieldType, false); | |
| 1511 | |||
| 1512 | // Check if the template type list contains this type | ||
| 1513 | 7781 | if !fieldType.isCoveredByGenericTypeList(templateTypesGeneric) { | |
| 1514 | 1 | throwSemanticError(cast<ASTNode*>(field.dataType), SemanticErrorType::GENERIC_TYPE_NOT_IN_TEMPLATE, String("Generic field type not included in struct template")); | |
| 1515 | 2 | return Any(); | |
| 1516 | 1 | } | |
| 1517 | 7780 | } | |
| 1518 | |||
| 1519 | // Change to the root scope | ||
| 1520 | 3218 | this.currentScope = this.rootScope; | |
| 1521 | 3218 | assert this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1522 | |||
| 1523 | // Build struct object | ||
| 1524 | 3218 | Struct spiceStruct = Struct(node.structName, node.entry, node.structScope, fieldTypes, templateTypesGeneric, interfaceTypes, baseNode); | |
| 1525 | 3218 | insertStruct(this.currentScope, spiceStruct, &node.structManifestations); | |
| 1526 | 3218 | spiceStruct.scope = node.structScope; | |
| 1527 | |||
| 1528 | // Request RTTI runtime if the struct is polymorphic | ||
| 1529 | 3218 | node.emitVTable = node.emitVTable || node.hasInterfaces; | |
| 1530 | 3218 | if node.attrs != nil<IVisitable*> { | |
| 1531 | 202 | const ASTTopLevelDefAttrNode* attrs = downcast<ASTTopLevelDefAttrNode>(node.attrs); | |
| 1532 | 202 | ASTAttrLstNode* attrLst = attrs.attrLst; | |
| 1533 | 202 | if attrLst.hasAttr(String(ATTR_CORE_COMPILER_EMIT_VTABLE)) { | |
| 1534 | 192 | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_CORE_COMPILER_EMIT_VTABLE)); | |
| 1535 | 192 | node.emitVTable = node.emitVTable || value.getBoolValue(); | |
| 1536 | 192 | } | |
| 1537 | 202 | } | |
| 1538 | 3218 | if node.emitVTable && !this.sourceFile.isRttiRT() { | |
| 1539 | 428 | this.sourceFile.requestRuntimeModule(RuntimeModule::RTTI_RT); | |
| 1540 | 428 | } | |
| 1541 | |||
| 1542 | 6436 | return Any(); | |
| 1543 | 3223 | } | |
| 1544 | |||
| 1545 | 323 | f<Any> TypeChecker.visitInterfaceDefPrepare(ASTInterfaceDefNode* node) { | |
| 1546 | 323 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 1547 | 323 | QualTypeList usedTemplateTypes; | |
| 1548 | 323 | Vector<GenericType> templateTypesGeneric; | |
| 1549 | |||
| 1550 | // Retrieve interface template types | ||
| 1551 | 323 | if node.hasTemplateTypes { | |
| 1552 | 516 | foreach ASTDataTypeNode* dataType : node.templateTypeLst.dataTypes { | |
| 1553 | // Visit template type | ||
| 1554 | 258 | const QualType templateType = this.getDataType(dataType); | |
| 1555 | 258 | if templateType.is(SuperType::TY_UNRESOLVED) { | |
| 1556 | ✗ | return Any(); | |
| 1557 | } | ||
| 1558 | // Check if it is a generic type | ||
| 1559 | 258 | if !templateType.is(SuperType::TY_GENERIC) { | |
| 1560 | ✗ | this.softError(cast<ASTNode*>(dataType), SemanticErrorType::EXPECTED_GENERIC_TYPE, String("A template list can only contain generic types")); | |
| 1561 | ✗ | continue; | |
| 1562 | ✗ | } | |
| 1563 | // Convert generic symbol type to generic type | ||
| 1564 | 258 | GenericType* genericType = this.rootScope.lookupGenericTypeStrict(templateType.getSubType()); | |
| 1565 | 258 | assert genericType != nil<GenericType*>; | |
| 1566 | 258 | usedTemplateTypes.pushBack(genericType.qualType); | |
| 1567 | 258 | templateTypesGeneric.pushBack(*genericType); | |
| 1568 | 258 | } | |
| 1569 | 258 | } | |
| 1570 | |||
| 1571 | // Update type of interface entry | ||
| 1572 | 323 | TypeChainElementData data; | |
| 1573 | 323 | data.bodyScope = node.interfaceScope; | |
| 1574 | 323 | const Type* rawType = this.resourceManager.typeRegistry.getOrInsert(SuperType::TY_INTERFACE, node.interfaceName, node.typeId, data, usedTemplateTypes); | |
| 1575 | 323 | const QualType interfaceType = QualType(rawType, node.qualifiers); | |
| 1576 | 323 | assert node.entry != nil<SymbolTableEntry*>; | |
| 1577 | // If the entry was implicitly forward-declared because it is referenced across a circular import | ||
| 1578 | // (assignDeferredOpaqueType), the existing type was already set to the opaque interface type. Overwriting it with the | ||
| 1579 | // identical interned type is expected here. | ||
| 1580 | 323 | const QualType existingType = node.entry.getQualType(); | |
| 1581 | 323 | const bool overwrite = existingType.is(SuperType::TY_INTERFACE); | |
| 1582 | 323 | node.entry.updateType(interfaceType, overwrite); | |
| 1583 | |||
| 1584 | // Change to interface scope | ||
| 1585 | 323 | this.currentScope = node.interfaceScope; | |
| 1586 | 323 | assert this.currentScope.scopeType == ScopeType::INTERFACE; | |
| 1587 | |||
| 1588 | // Visit methods | ||
| 1589 | 323 | unsigned long vtableIndex = 0l; | |
| 1590 | 323 | Vector<Function*> methods; | |
| 1591 | 323 | const QualTypeList interfaceTemplateTypes = interfaceType.getTemplateTypes(); | |
| 1592 | 1064 | foreach IVisitable* signature : node.signatures { | |
| 1593 | 742 | Any signatureResult = this.visit(signature); | |
| 1594 | 742 | Vector<Function*>* method = signatureResult.get<Vector<Function*>*>(); | |
| 1595 | 742 | if method == nil<Vector<Function*>*> { | |
| 1596 | 2 | return Any(); | |
| 1597 | } | ||
| 1598 | |||
| 1599 | // Set 'this' type | ||
| 1600 | 1482 | foreach Function* m : *method { | |
| 1601 | 741 | m.isVirtual = true; // Interface methods are always virtual | |
| 1602 | 741 | m.vtableIndex = vtableIndex; | |
| 1603 | 741 | m.thisType = interfaceType; | |
| 1604 | |||
| 1605 | // Merge the enclosing interface's own template types into the method's template types, mirroring how struct | ||
| 1606 | // methods inherit the struct's generic params in visitFctDefPrepare. Without this, a method that does not | ||
| 1607 | // redeclare its own template list (e.g. isValid() inside IIterator<T>) ends up with an empty templateTypes | ||
| 1608 | // list, even though its thisType references the interface's generic type - so generic type resolution fails | ||
| 1609 | // while matching the 'this' type of a call through an interface reference. | ||
| 1610 | 1406 | foreach const QualType& templateType : interfaceTemplateTypes { | |
| 1611 | 665 | addUsedTemplateType(m.templateTypes, templateType); | |
| 1612 | 665 | } | |
| 1613 | 741 | } | |
| 1614 | |||
| 1615 | 1482 | foreach Function* m : *method { | |
| 1616 | 741 | methods.pushBack(m); | |
| 1617 | 741 | } | |
| 1618 | 741 | vtableIndex++; | |
| 1619 | 742 | } | |
| 1620 | |||
| 1621 | // Change to root scope | ||
| 1622 | 322 | this.currentScope = this.rootScope; | |
| 1623 | 322 | assert this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1624 | |||
| 1625 | // Build interface object | ||
| 1626 | 322 | Interface spiceInterface = Interface(node.interfaceName, node.entry, node.interfaceScope, methods, templateTypesGeneric, baseNode); | |
| 1627 | 322 | insertInterface(this.currentScope, spiceInterface, &node.interfaceManifestations); | |
| 1628 | 322 | spiceInterface.scope = node.interfaceScope; | |
| 1629 | |||
| 1630 | // Request RTTI runtime, that is always required when dealing with interfaces due to polymorphism | ||
| 1631 | 322 | if !this.sourceFile.isRttiRT() { | |
| 1632 | 322 | this.sourceFile.requestRuntimeModule(RuntimeModule::RTTI_RT); | |
| 1633 | 322 | } | |
| 1634 | |||
| 1635 | 644 | return Any(); | |
| 1636 | 323 | } | |
| 1637 | |||
| 1638 | 24 | f<Any> TypeChecker.visitUnionDefPrepare(ASTUnionDefNode* node) { | |
| 1639 | 24 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 1640 | 24 | QualTypeList usedTemplateTypes; | |
| 1641 | 24 | Vector<GenericType> templateTypesGeneric; | |
| 1642 | |||
| 1643 | // Retrieve union template types | ||
| 1644 | 24 | if node.hasTemplateTypes { | |
| 1645 | 4 | if !this.collectTemplateTypes(node.templateTypeLst, templateTypesGeneric) { | |
| 1646 | ✗ | return Any(); | |
| 1647 | } | ||
| 1648 | 8 | foreach const GenericType& genericType : templateTypesGeneric { | |
| 1649 | 4 | usedTemplateTypes.pushBack(genericType.qualType); | |
| 1650 | 4 | } | |
| 1651 | 4 | } | |
| 1652 | |||
| 1653 | // Update type of union entry | ||
| 1654 | 24 | assert node.entry != nil<SymbolTableEntry*>; | |
| 1655 | 24 | TypeChainElementData data; | |
| 1656 | 24 | data.bodyScope = node.unionScope; | |
| 1657 | 24 | const Type* rawType = this.resourceManager.typeRegistry.getOrInsert(SuperType::TY_UNION, node.unionName, node.typeId, data, usedTemplateTypes); | |
| 1658 | // If the entry was implicitly forward-declared because it is referenced across a circular import | ||
| 1659 | // (assignDeferredOpaqueType), the existing type was already set to the opaque union type. Overwriting it with the | ||
| 1660 | // identical interned type is expected here. | ||
| 1661 | 24 | const QualType existingType = node.entry.getQualType(); | |
| 1662 | 24 | const bool overwrite = existingType.is(SuperType::TY_UNION); | |
| 1663 | 24 | node.entry.updateType(QualType(rawType, node.qualifiers), overwrite); | |
| 1664 | |||
| 1665 | // Change to union scope | ||
| 1666 | 24 | this.currentScope = node.unionScope; | |
| 1667 | 24 | assert this.currentScope.scopeType == ScopeType::UNION; | |
| 1668 | |||
| 1669 | // Retrieve field types | ||
| 1670 | 24 | QualTypeList fieldTypes; | |
| 1671 | // Shared across all fields: the origin (node.unionScope) is constant, so an aggregate already proven not to reach | ||
| 1672 | // it via one field need not be re-walked for another. | ||
| 1673 | 24 | Vector<const Scope*> visitedScopes; | |
| 1674 | 24 | unsigned long defaultFieldIndex = ULONG_MAX_VALUE; | |
| 1675 | 74 | for unsigned long i = 0l; i < node.fields.getSize(); i++ { | |
| 1676 | 52 | IVisitable* fieldVisitable = node.fields.get(i); | |
| 1677 | 52 | const ASTFieldNode* field = downcast<ASTFieldNode>(fieldVisitable); | |
| 1678 | 52 | ASTNode* fieldBaseNode = cast<ASTNode*>(field); | |
| 1679 | |||
| 1680 | // Visit field type | ||
| 1681 | 52 | const QualType fieldType = this.visitType(fieldVisitable); | |
| 1682 | 52 | if fieldType.is(SuperType::TY_UNRESOLVED) { | |
| 1683 | ✗ | this.sourceFile.checkForSoftErrors(); // We get into trouble if we continue without the field type -> abort | |
| 1684 | ✗ | } | |
| 1685 | |||
| 1686 | // Check for union/struct with infinite size. This happens if the union (transitively, through by-value | ||
| 1687 | // struct/union fields) contains itself - either directly or through a cycle of aggregates in mutually | ||
| 1688 | // importing files. | ||
| 1689 | 52 | if fieldContainsAggregateByValue(fieldType, node.unionScope, fieldBaseNode, visitedScopes) { | |
| 1690 | 2 | throwSemanticError(fieldBaseNode, SemanticErrorType::UNION_INFINITE_SIZE, String("Union with infinite size detected")); | |
| 1691 | 4 | return Any(); | |
| 1692 | 2 | } | |
| 1693 | |||
| 1694 | // Union fields must not be references | ||
| 1695 | 50 | if fieldType.isRef() { | |
| 1696 | 1 | this.softError(fieldBaseNode, SemanticErrorType::UNION_FIELD_MUST_NOT_BE_REFERENCE, String("A union field must not be a reference")); | |
| 1697 | 1 | } | |
| 1698 | |||
| 1699 | // Union fields must be trivially constructible, copyable and destructible, since the union does not know which | ||
| 1700 | // field is currently active and therefore cannot run any non-trivial special member on its own. | ||
| 1701 | 50 | if !fieldType.isRef() && (!fieldType.isTriviallyConstructible(fieldBaseNode) || !fieldType.isTriviallyCopyable(fieldBaseNode) || !fieldType.isTriviallyDestructible(fieldBaseNode)) { | |
| 1702 | 1 | this.softError(fieldBaseNode, SemanticErrorType::UNION_FIELD_TYPE_NOT_TRIVIAL, "The type of the union field '" + field.fieldName + "' is not trivial. Only trivial types are allowed as union fields"); | |
| 1703 | 1 | } | |
| 1704 | |||
| 1705 | // At most one field may carry a default value | ||
| 1706 | 50 | if field.defaultValue != nil<IVisitable*> { | |
| 1707 | 4 | if defaultFieldIndex != ULONG_MAX_VALUE { | |
| 1708 | 1 | this.softError(fieldBaseNode, SemanticErrorType::DUPLICATE_UNION_DEFAULT_VALUE, String("A union may only have one field with a default value")); | |
| 1709 | 1 | } else { | |
| 1710 | 3 | defaultFieldIndex = i; | |
| 1711 | 3 | } | |
| 1712 | 4 | } | |
| 1713 | |||
| 1714 | // Add to field types | ||
| 1715 | 50 | fieldTypes.pushBack(fieldType); | |
| 1716 | |||
| 1717 | // Update type of field entry | ||
| 1718 | 50 | SymbolTableEntry* fieldEntry = this.currentScope.lookupStrict(field.fieldName); | |
| 1719 | 50 | assert fieldEntry != nil<SymbolTableEntry*>; | |
| 1720 | 50 | fieldEntry.updateType(fieldType, false); | |
| 1721 | |||
| 1722 | // Check if the template type list contains this type | ||
| 1723 | 50 | if !fieldType.isCoveredByGenericTypeList(templateTypesGeneric) { | |
| 1724 | ✗ | throwSemanticError(cast<ASTNode*>(field.dataType), SemanticErrorType::GENERIC_TYPE_NOT_IN_TEMPLATE, String("Generic field type not included in union template")); | |
| 1725 | ✗ | return Any(); | |
| 1726 | ✗ | } | |
| 1727 | 50 | } | |
| 1728 | |||
| 1729 | // Change to the root scope | ||
| 1730 | 22 | this.currentScope = this.rootScope; | |
| 1731 | 22 | assert this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1732 | |||
| 1733 | // Build union object | ||
| 1734 | 22 | Union spiceUnion = Union(node.unionName, node.entry, node.unionScope, fieldTypes, templateTypesGeneric, baseNode); | |
| 1735 | 22 | spiceUnion.defaultFieldIndex = defaultFieldIndex; | |
| 1736 | 22 | insertUnion(this.currentScope, spiceUnion, &node.unionManifestations); | |
| 1737 | |||
| 1738 | 44 | return Any(); | |
| 1739 | 24 | } | |
| 1740 | |||
| 1741 | 463 | f<Any> TypeChecker.visitEnumDefPrepare(ASTEnumDefNode* node) { | |
| 1742 | // Update type of enum entry | ||
| 1743 | 463 | TypeChainElementData data; | |
| 1744 | 463 | data.bodyScope = node.enumScope; | |
| 1745 | 463 | const QualTypeList noTemplateTypes; | |
| 1746 | 463 | const Type* rawType = this.resourceManager.typeRegistry.getOrInsert(SuperType::TY_ENUM, node.enumName, node.typeId, data, noTemplateTypes); | |
| 1747 | 463 | assert node.entry != nil<SymbolTableEntry*>; | |
| 1748 | // The enum may already carry its opaque type if it was referenced across a circular import before being prepared | ||
| 1749 | // (assignDeferredOpaqueType). Overwriting it with the identical interned type is expected here. | ||
| 1750 | 463 | const QualType existingType = node.entry.getQualType(); | |
| 1751 | 463 | const bool overwrite = existingType.is(SuperType::TY_ENUM); | |
| 1752 | 463 | node.entry.updateType(QualType(rawType, node.qualifiers), overwrite); | |
| 1753 | |||
| 1754 | // Change to enum scope | ||
| 1755 | 463 | this.currentScope = node.enumScope; | |
| 1756 | 463 | assert this.currentScope.scopeType == ScopeType::ENUM; | |
| 1757 | |||
| 1758 | // Loop through all items with values | ||
| 1759 | 463 | const ASTEnumItemLstNode* itemLst = downcast<ASTEnumItemLstNode>(node.itemLst); | |
| 1760 | 463 | Vector<String> names; | |
| 1761 | 463 | Vector<unsigned int> values; | |
| 1762 | 2203 | foreach ASTEnumItemNode* enumItem : itemLst.items { | |
| 1763 | // Save name | ||
| 1764 | 1740 | names.pushBack(enumItem.itemName); | |
| 1765 | // Check for duplicate value | ||
| 1766 | 1740 | if enumItem.hasValue { | |
| 1767 | 1157 | if containsValue(values, enumItem.itemValue) { | |
| 1768 | 1 | this.softError(cast<ASTNode*>(enumItem), SemanticErrorType::DUPLICATE_ENUM_ITEM_VALUE, String("Duplicate enum item value, please use another")); | |
| 1769 | 1 | continue; | |
| 1770 | 1 | } | |
| 1771 | 1156 | values.pushBack(enumItem.itemValue); | |
| 1772 | 1156 | } | |
| 1773 | 1739 | } | |
| 1774 | |||
| 1775 | // Loop through all items without values | ||
| 1776 | 463 | unsigned int nextValue = 0u; | |
| 1777 | 463 | const QualType intSymbolType = QualType(SuperType::TY_INT); | |
| 1778 | 2203 | foreach ASTEnumItemNode* enumItem : itemLst.items { | |
| 1779 | // Update type of enum item entry | ||
| 1780 | 1740 | SymbolTableEntry* itemEntry = this.currentScope.lookupStrict(enumItem.itemName); | |
| 1781 | 1740 | assert itemEntry != nil<SymbolTableEntry*>; | |
| 1782 | 1740 | itemEntry.updateType(intSymbolType, false); | |
| 1783 | // Fill in value if not filled yet | ||
| 1784 | 1740 | if !enumItem.hasValue { | |
| 1785 | 1074 | while containsValue(values, nextValue) { | |
| 1786 | 491 | nextValue++; | |
| 1787 | 491 | } | |
| 1788 | 583 | enumItem.itemValue = nextValue; | |
| 1789 | 583 | values.pushBack(nextValue); | |
| 1790 | 583 | } | |
| 1791 | 1740 | } | |
| 1792 | |||
| 1793 | // Change to root scope | ||
| 1794 | 463 | this.currentScope = this.rootScope; | |
| 1795 | 463 | assert this.currentScope.scopeType == ScopeType::GLOBAL; | |
| 1796 | |||
| 1797 | 926 | return Any(); | |
| 1798 | 463 | } | |
| 1799 | |||
| 1800 | // Check if the given list of enum item values contains the given value | ||
| 1801 | 2231 | f<bool> containsValue(const Vector<unsigned int>& values, unsigned int value) { | |
| 1802 | 18472 | foreach const unsigned int existingValue : values { | |
| 1803 | 16733 | if existingValue == value { | |
| 1804 | 492 | return true; | |
| 1805 | } | ||
| 1806 | 16241 | } | |
| 1807 | 1739 | return false; | |
| 1808 | } | ||
| 1809 | |||
| 1810 | 3062 | f<Any> TypeChecker.visitGenericTypeDefPrepare(ASTGenericTypeDefNode* node) { | |
| 1811 | // Retrieve type conditions | ||
| 1812 | 3062 | const ASTTypeAltsLstNode* typeAltsLst = downcast<ASTTypeAltsLstNode>(node.typeAltsLst); | |
| 1813 | 3062 | QualTypeList typeConditions; | |
| 1814 | 8563 | foreach ASTDataTypeNode* typeAlt : typeAltsLst.dataTypes { | |
| 1815 | 5501 | const QualType typeCondition = this.getDataType(typeAlt); | |
| 1816 | 5501 | if typeCondition.is(SuperType::TY_UNRESOLVED) { | |
| 1817 | ✗ | return Any(); | |
| 1818 | } | ||
| 1819 | 5501 | typeConditions.pushBack(typeCondition); | |
| 1820 | 5501 | } | |
| 1821 | |||
| 1822 | // Add generic type to the scope | ||
| 1823 | 3062 | const GenericType genericType = GenericType(node.typeName, typeConditions); | |
| 1824 | 3062 | this.rootScope.insertGenericType(node.typeName, genericType); | |
| 1825 | |||
| 1826 | // Check if only one type condition is set | ||
| 1827 | 3062 | if typeConditions.getSize() == 1l { | |
| 1828 | 1362 | const QualType& typeCondition = typeConditions.front(); | |
| 1829 | 1362 | if !typeCondition.is(SuperType::TY_DYN) { | |
| 1830 | 1 | this.addWarning(typeAltsLst.codeLoc, CompilerWarningType::SINGLE_GENERIC_TYPE_CONDITION, String("Generic type is locked to one type")); | |
| 1831 | 1 | } | |
| 1832 | 1362 | } | |
| 1833 | |||
| 1834 | // Check if the list contains the dyn type, along with other types | ||
| 1835 | 3062 | bool containsDynType = false; | |
| 1836 | 8563 | foreach const QualType& typeCondition : typeConditions { | |
| 1837 | 5501 | if typeCondition.is(SuperType::TY_DYN) { | |
| 1838 | 1363 | containsDynType = true; | |
| 1839 | 1363 | } | |
| 1840 | 5501 | } | |
| 1841 | 3062 | if containsDynType && typeConditions.getSize() > 1l { | |
| 1842 | 2 | this.addWarning(typeAltsLst.codeLoc, CompilerWarningType::INEFFECTIVE_GENERIC_TYPE_CONDITION, String("One or several type conditions are superfluous, because the dyn type condition is given")); | |
| 1843 | 2 | } | |
| 1844 | |||
| 1845 | 6124 | return Any(); | |
| 1846 | 3062 | } | |
| 1847 | |||
| 1848 | 159 | #[ignoreUnusedReturnValue] | |
| 1849 | 159 | f<Any> TypeChecker.visitAliasDefPrepare(ASTAliasDefNode* node) { | |
| 1850 | 159 | assert node.entry != nil<SymbolTableEntry*> && node.aliasedTypeContainerEntry != nil<SymbolTableEntry*>; | |
| 1851 | |||
| 1852 | // The alias may already have been prepared on demand because it was referenced across a circular import | ||
| 1853 | // (assignDeferredOpaqueType). In that case there is nothing left to do. | ||
| 1854 | 159 | const QualType existingType = node.entry.getQualType(); | |
| 1855 | 159 | if existingType.is(SuperType::TY_ALIAS) { | |
| 1856 | 2 | return Any(); | |
| 1857 | } | ||
| 1858 | |||
| 1859 | // Ensure the aliased-type subtree is sized for manifestations before it is visited below. On the normal path | ||
| 1860 | // visitEntry already sized the whole file to 1, so this is a no-op; on the on-demand path (a cross-import reference | ||
| 1861 | // reaching this alias before its own file's visitEntry ran) it establishes the precondition setEvaluatedSymbolType | ||
| 1862 | // requires. | ||
| 1863 | 157 | ASTNode* dataTypeBaseNode = cast<ASTNode*>(node.dataType); | |
| 1864 | 157 | dataTypeBaseNode.resizeToNumberOfManifestations(1l); | |
| 1865 | |||
| 1866 | // Update type of alias entry | ||
| 1867 | 157 | const TypeChainElementData noData; | |
| 1868 | 157 | const QualTypeList noTemplateTypes; | |
| 1869 | 157 | const Type* rawType = this.resourceManager.typeRegistry.getOrInsert(SuperType::TY_ALIAS, node.aliasName, node.typeId, noData, noTemplateTypes); | |
| 1870 | 157 | node.entry.updateType(QualType(rawType, node.qualifiers), false); | |
| 1871 | |||
| 1872 | // Update type of the aliased type container entry | ||
| 1873 | 157 | const QualType aliasedType = this.getDataType(node.dataType); | |
| 1874 | 157 | if aliasedType.is(SuperType::TY_UNRESOLVED) { | |
| 1875 | 2 | return Any(); | |
| 1876 | } | ||
| 1877 | 156 | node.aliasedTypeContainerEntry.updateType(aliasedType, false); | |
| 1878 | 156 | node.aliasedTypeContainerEntry.used = true; // The container type is always used per default | |
| 1879 | |||
| 1880 | 312 | return Any(); | |
| 1881 | 157 | } | |
| 1882 | |||
| 1883 | 4280 | f<Any> TypeChecker.visitGlobalVarDefPrepare(ASTGlobalVarDefNode* node) { | |
| 1884 | // Insert variable name to symbol table | ||
| 1885 | 4280 | QualType globalVarType = this.getDataType(node.dataType); | |
| 1886 | 4280 | if globalVarType.is(SuperType::TY_UNRESOLVED) { | |
| 1887 | ✗ | return Any(); | |
| 1888 | } | ||
| 1889 | 4280 | const bool isInferred = globalVarType.is(SuperType::TY_DYN); | |
| 1890 | 4280 | const bool isDeclaredConst = globalVarType.qualifiers.isConst; | |
| 1891 | 4280 | const bool isDeclaredPublic = globalVarType.qualifiers.isPublic; | |
| 1892 | |||
| 1893 | 4280 | if node.constant != nil<ASTConstantNode*> { // Variable is initialized here | |
| 1894 | 4271 | const ExprResult constantResult = this.visitExpr(cast<IVisitable*>(node.constant)); | |
| 1895 | 4271 | const QualType rhsType = constantResult.ty; | |
| 1896 | 4271 | if rhsType.is(SuperType::TY_UNRESOLVED) { | |
| 1897 | ✗ | return Any(); | |
| 1898 | } | ||
| 1899 | 4271 | if globalVarType.is(SuperType::TY_DYN) { // Perform type inference | |
| 1900 | 12 | globalVarType = rhsType; | |
| 1901 | 4271 | } else if !globalVarType.matches(rhsType, false, true, true) { // Check if types are matching | |
| 1902 | 2 | this.softError(cast<ASTNode*>(node.constant), SemanticErrorType::OPERATOR_WRONG_DATA_TYPE, "Expected " + globalVarType.getName(false) + ", but got " + rhsType.getName(false)); | |
| 1903 | 4 | return Any(); | |
| 1904 | 4259 | } | |
| 1905 | 4269 | } | |
| 1906 | |||
| 1907 | // Check if the type is still missing | ||
| 1908 | 4278 | if globalVarType.is(SuperType::TY_DYN) { | |
| 1909 | 1 | this.softError(cast<ASTNode*>(node.dataType), SemanticErrorType::GLOBAL_OF_TYPE_DYN, String("Global variables must have an explicit data type")); | |
| 1910 | 2 | return Any(); | |
| 1911 | 1 | } | |
| 1912 | |||
| 1913 | // Check if we would need to insert instructions in the global scope to initialize the variable | ||
| 1914 | 4277 | if !globalVarType.isPrimitive() && !globalVarType.isPtr() { | |
| 1915 | 1 | this.softError(cast<ASTNode*>(node.dataType), SemanticErrorType::GLOBAL_OF_INVALID_TYPE, String("Spice does only support global variables of primitive or pointer type")); | |
| 1916 | 2 | return Any(); | |
| 1917 | 1 | } | |
| 1918 | |||
| 1919 | // The inferred type comes from the value, so it does not carry the declared qualifiers yet | ||
| 1920 | 4276 | if isInferred { | |
| 1921 | 12 | globalVarType.makeConst(isDeclaredConst); | |
| 1922 | 12 | globalVarType.makePublic(isDeclaredPublic); | |
| 1923 | 12 | } | |
| 1924 | |||
| 1925 | // Update type of global var entry | ||
| 1926 | 4276 | assert node.entry != nil<SymbolTableEntry*>; | |
| 1927 | 4276 | node.entry.updateType(globalVarType, false); | |
| 1928 | |||
| 1929 | // Check if a value is attached | ||
| 1930 | 4276 | if node.constant == nil<ASTConstantNode*> && globalVarType.isConst() { | |
| 1931 | 1 | this.softError(cast<ASTNode*>(node), SemanticErrorType::GLOBAL_CONST_WITHOUT_VALUE, String("You must specify a value for constant global variables")); | |
| 1932 | 2 | return Any(); | |
| 1933 | 1 | } | |
| 1934 | |||
| 1935 | 4275 | return Any(); | |
| 1936 | } | ||
| 1937 | |||
| 1938 | 5226 | f<Any> TypeChecker.visitExtDeclPrepare(ASTExtDeclNode* node) { | |
| 1939 | 5226 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 1940 | |||
| 1941 | // Collect argument types | ||
| 1942 | 5226 | QualTypeList argTypes; | |
| 1943 | 5226 | ParamList argList; | |
| 1944 | 5226 | ASTTypeLstWithEllipsisNode* argTypeLst = nil<ASTTypeLstWithEllipsisNode*>; | |
| 1945 | 5226 | if node.argTypeLst != nil<IVisitable*> { | |
| 1946 | 5154 | argTypeLst = downcast<ASTTypeLstWithEllipsisNode>(node.argTypeLst); | |
| 1947 | 5154 | } | |
| 1948 | 5226 | if node.hasArgs { | |
| 1949 | 16265 | foreach ASTDataTypeNode* arg : argTypeLst.typeLst.dataTypes { | |
| 1950 | // Visit argument | ||
| 1951 | 11111 | const QualType argType = this.getDataType(arg); | |
| 1952 | 11111 | if argType.is(SuperType::TY_UNRESOLVED) { | |
| 1953 | ✗ | return Any(); | |
| 1954 | } | ||
| 1955 | // Check if the argument type is 'dyn' | ||
| 1956 | 11111 | if argType.is(SuperType::TY_DYN) { | |
| 1957 | 1 | this.softError(cast<ASTNode*>(arg), SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Dyn data type is not allowed as arg type for external functions")); | |
| 1958 | 1 | continue; | |
| 1959 | 1 | } | |
| 1960 | // Save argument | ||
| 1961 | 11110 | argTypes.pushBack(argType); | |
| 1962 | 11110 | argList.pushBack(Param{argType, false}); | |
| 1963 | 11110 | } | |
| 1964 | 5154 | } | |
| 1965 | |||
| 1966 | // Retrieve return type | ||
| 1967 | 5226 | QualType returnType = QualType(SuperType::TY_DYN); | |
| 1968 | 5226 | const bool isFunction = node.returnType != nil<ASTDataTypeNode*>; | |
| 1969 | 5226 | if isFunction { // External function | |
| 1970 | 4547 | returnType = this.getDataType(node.returnType); | |
| 1971 | 4547 | if returnType.is(SuperType::TY_UNRESOLVED) { | |
| 1972 | ✗ | return Any(); | |
| 1973 | } | ||
| 1974 | // Check if return type is dyn | ||
| 1975 | 4547 | if returnType.is(SuperType::TY_DYN) { | |
| 1976 | 1 | this.softError(cast<ASTNode*>(node.returnType), SemanticErrorType::UNEXPECTED_DYN_TYPE, String("dyn is not allowed as return type for external functions")); | |
| 1977 | 2 | return Any(); | |
| 1978 | 1 | } | |
| 1979 | 4546 | } | |
| 1980 | |||
| 1981 | // Add function to current scope | ||
| 1982 | 5225 | const Vector<GenericType> noTemplateTypes; | |
| 1983 | 5225 | const Function spiceFunc = Function(node.extFunctionName, node.entry, QualType(SuperType::TY_DYN), returnType, argList, noTemplateTypes, baseNode); | |
| 1984 | 5225 | node.extFunction = insertFunction(this.currentScope, spiceFunc, &node.extFunctionManifestations); | |
| 1985 | 5225 | node.extFunction.mangleFunctionName = false; | |
| 1986 | 5225 | node.extFunction.alreadyTypeChecked = true; | |
| 1987 | 5225 | node.extFunction.isVararg = argTypeLst != nil<ASTTypeLstWithEllipsisNode*> && argTypeLst.hasEllipsis; | |
| 1988 | |||
| 1989 | // Check procedure attributes | ||
| 1990 | 5225 | if node.attrs != nil<IVisitable*> { | |
| 1991 | 1 | const ASTTopLevelDefAttrNode* attrs = downcast<ASTTopLevelDefAttrNode>(node.attrs); | |
| 1992 | 1 | ASTAttrLstNode* attrLst = attrs.attrLst; | |
| 1993 | 1 | if attrLst.hasAttr(String(ATTR_CORE_COMPILER_MANGLE)) { | |
| 1994 | ✗ | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_CORE_COMPILER_MANGLE)); | |
| 1995 | ✗ | node.extFunction.mangleFunctionName = value.getBoolValue(); | |
| 1996 | ✗ | } | |
| 1997 | 1 | if attrLst.hasAttr(String(ATTR_CORE_COMPILER_MANGLED_NAME)) { | |
| 1998 | 1 | const CompileTimeValue value = attrLst.getAttrValueByName(String(ATTR_CORE_COMPILER_MANGLED_NAME)); | |
| 1999 | 1 | node.extFunction.predefinedMangledName = this.resourceManager.getCompileTimeStringValue(value.getStringValueOffset()); | |
| 2000 | 1 | } | |
| 2001 | 1 | } | |
| 2002 | |||
| 2003 | // Prepare ext function type | ||
| 2004 | 5225 | const SuperType superType = isFunction ? SuperType::TY_FUNCTION : SuperType::TY_PROCEDURE; | |
| 2005 | 5225 | const QualType extFunctionTypeBase = QualType(superType); | |
| 2006 | 5225 | const QualType extFunctionType = extFunctionTypeBase.getWithFunctionParamAndReturnTypes(returnType, argTypes); | |
| 2007 | |||
| 2008 | // Set type of external function | ||
| 2009 | 5225 | node.entry.updateType(extFunctionType, false); | |
| 2010 | |||
| 2011 | // Rename the original child scope to reflect the substantiated versions of the external function | ||
| 2012 | 5225 | this.currentScope.renameChildScope(node.getScopeId(), spiceFunc.getScopeName()); | |
| 2013 | |||
| 2014 | 10450 | return Any(); | |
| 2015 | 5226 | } | |
| 2016 | |||
| 2017 | 2501 | f<Any> TypeChecker.visitImportDefPrepare(ASTImportDefNode* node) { | |
| 2018 | // Set entry to import type | ||
| 2019 | 2501 | const QualType importType = QualType(SuperType::TY_IMPORT, node.importName); | |
| 2020 | 2501 | assert node.entry != nil<SymbolTableEntry*>; | |
| 2021 | 2501 | node.entry.updateType(importType, false); | |
| 2022 | |||
| 2023 | 2501 | return Any(); | |
| 2024 | } | ||
| 2025 | |||
| 2026 | // ============================================================ Meta ============================================================= | ||
| 2027 | |||
| 2028 | 65224 | public f<Any> TypeChecker.visitParamLst(ASTParamLstNode* node) { | |
| 2029 | 65224 | ParamList params; | |
| 2030 | 65224 | bool metOptional = false; | |
| 2031 | |||
| 2032 | 172717 | foreach ASTDeclStmtNode* param : node.params { | |
| 2033 | 107493 | ASTNode* paramBaseNode = cast<ASTNode*>(param); | |
| 2034 | |||
| 2035 | // Visit param | ||
| 2036 | 107493 | const QualType paramType = this.visitType(cast<IVisitable*>(param)); | |
| 2037 | |||
| 2038 | // Check if the type could be inferred. Dyn without a default value is forbidden | ||
| 2039 | 107493 | if paramType.is(SuperType::TY_DYN) { | |
| 2040 | 3 | this.softError(cast<ASTNode*>(node), SemanticErrorType::FCT_PARAM_IS_TYPE_DYN, "Type of parameter '" + param.varName + "' is invalid"); | |
| 2041 | 3 | continue; | |
| 2042 | 3 | } | |
| 2043 | |||
| 2044 | // Ensure that no optional param comes after a mandatory param | ||
| 2045 | 107490 | if param.hasAssignment { | |
| 2046 | 8013 | metOptional = true; | |
| 2047 | 107490 | } else if metOptional { | |
| 2048 | 6 | this.softError(paramBaseNode, SemanticErrorType::INVALID_PARAM_ORDER, String("Mandatory parameters must go before any optional parameters")); | |
| 2049 | 6 | continue; | |
| 2050 | 99477 | } | |
| 2051 | |||
| 2052 | // Warn about non-trivially copyable types being passed by value, since that requires an implicit copy on every call. | ||
| 2053 | // Only check in the check stage (not the prepare stage), because generic param types are not substantiated yet there. | ||
| 2054 | 107484 | if this.typeCheckerMode == TypeCheckerMode::TC_MODE_POST && !paramType.isTriviallyCopyable(paramBaseNode) { | |
| 2055 | 101 | const String message = "Parameter '" + param.varName + "' has the non-trivially copyable type '" + paramType.getName() + "' and is passed by value, which requires an implicit copy on every call. Consider passing it by reference instead."; | |
| 2056 | 101 | this.addWarning(param.codeLoc, CompilerWarningType::NON_TRIVIAL_TYPE_PASSED_BY_VALUE, message); | |
| 2057 | 101 | } | |
| 2058 | |||
| 2059 | // Add parameter to param list | ||
| 2060 | 107484 | params.pushBack(Param{paramType, metOptional}); | |
| 2061 | 107484 | } | |
| 2062 | |||
| 2063 | 130448 | return Any(params); | |
| 2064 | 65224 | } | |
| 2065 | |||
| 2066 | 7837 | public f<Any> TypeChecker.visitField(ASTFieldNode* node) { | |
| 2067 | 7837 | const QualType fieldType = this.getDataType(node.dataType); | |
| 2068 | 7837 | if fieldType.is(SuperType::TY_UNRESOLVED) { | |
| 2069 | 1 | return Any(fieldType); | |
| 2070 | } | ||
| 2071 | |||
| 2072 | 7836 | if node.defaultValue != nil<IVisitable*> { | |
| 2073 | 1900 | const ExprResult defaultValueResult = this.visitExpr(node.defaultValue); | |
| 2074 | 1900 | const QualType defaultValueType = defaultValueResult.ty; | |
| 2075 | 1900 | if defaultValueType.is(SuperType::TY_UNRESOLVED) { | |
| 2076 | ✗ | return Any(defaultValueType); | |
| 2077 | } | ||
| 2078 | 1900 | if !fieldType.matches(defaultValueType, false, true, true) { | |
| 2079 | 2 | return this.softErrorQT(cast<ASTNode*>(node), SemanticErrorType::FIELD_TYPE_NOT_MATCHING, String("Type of the default values does not match the field type")); | |
| 2080 | 1 | } | |
| 2081 | 1899 | } | |
| 2082 | |||
| 2083 | 7835 | return Any(fieldType); | |
| 2084 | } | ||
| 2085 | |||
| 2086 | 742 | public f<Any> TypeChecker.visitSignature(ASTSignatureNode* node) { | |
| 2087 | 742 | const bool isFunction = node.signatureType == SignatureType::TYPE_FUNCTION; | |
| 2088 | 742 | const Any noSignatureManifestations = Any(nil<Vector<Function*>*>); | |
| 2089 | |||
| 2090 | // Retrieve function template types | ||
| 2091 | 742 | Vector<GenericType> usedGenericTypes; | |
| 2092 | 742 | if node.hasTemplateTypes { | |
| 2093 | 781 | foreach ASTDataTypeNode* dataType : node.templateTypeLst.dataTypes { | |
| 2094 | // Visit template type | ||
| 2095 | 391 | const QualType templateType = this.getDataType(dataType); | |
| 2096 | 391 | if templateType.is(SuperType::TY_UNRESOLVED) { | |
| 2097 | ✗ | return noSignatureManifestations; | |
| 2098 | } | ||
| 2099 | // Check if it is a generic type | ||
| 2100 | 391 | if !templateType.is(SuperType::TY_GENERIC) { | |
| 2101 | 1 | this.softError(cast<ASTNode*>(dataType), SemanticErrorType::EXPECTED_GENERIC_TYPE, String("A template list can only contain generic types")); | |
| 2102 | 2 | return noSignatureManifestations; | |
| 2103 | 1 | } | |
| 2104 | // Convert generic symbol type to generic type | ||
| 2105 | 390 | GenericType* genericType = this.rootScope.lookupGenericTypeStrict(templateType.getSubType()); | |
| 2106 | 390 | assert genericType != nil<GenericType*>; | |
| 2107 | 390 | usedGenericTypes.pushBack(*genericType); | |
| 2108 | 390 | } | |
| 2109 | 390 | } | |
| 2110 | |||
| 2111 | // Visit return type | ||
| 2112 | 741 | QualType returnType = QualType(SuperType::TY_DYN); | |
| 2113 | 741 | if isFunction { | |
| 2114 | 576 | returnType = this.getDataType(node.returnType); | |
| 2115 | 576 | if returnType.is(SuperType::TY_UNRESOLVED) { | |
| 2116 | ✗ | return noSignatureManifestations; | |
| 2117 | } | ||
| 2118 | |||
| 2119 | 576 | if !returnType.isCoveredByGenericTypeList(usedGenericTypes) { | |
| 2120 | 3 | this.softError(cast<ASTNode*>(node.returnType), SemanticErrorType::GENERIC_TYPE_NOT_IN_TEMPLATE, String("Generic return type not included in the template type list of the function")); | |
| 2121 | 3 | } | |
| 2122 | 576 | } | |
| 2123 | |||
| 2124 | // Visit params | ||
| 2125 | 741 | QualTypeList paramTypes; | |
| 2126 | 741 | ParamList paramList; | |
| 2127 | 741 | if node.hasParams { | |
| 2128 | 39 | foreach ASTDataTypeNode* param : node.paramTypeLst.dataTypes { | |
| 2129 | 21 | const QualType paramType = this.getDataType(param); | |
| 2130 | 21 | if paramType.is(SuperType::TY_UNRESOLVED) { | |
| 2131 | ✗ | return noSignatureManifestations; | |
| 2132 | } | ||
| 2133 | |||
| 2134 | // Check if the type is present in the template for generic types | ||
| 2135 | 21 | if !paramType.isCoveredByGenericTypeList(usedGenericTypes) { | |
| 2136 | 2 | this.softError(cast<ASTNode*>(node.paramTypeLst), SemanticErrorType::GENERIC_TYPE_NOT_IN_TEMPLATE, String("Generic param type not included in the template type list of the function")); | |
| 2137 | 2 | continue; | |
| 2138 | 2 | } | |
| 2139 | |||
| 2140 | 19 | paramTypes.pushBack(paramType); | |
| 2141 | 19 | paramList.pushBack(Param{paramType, false}); | |
| 2142 | 19 | } | |
| 2143 | 18 | } | |
| 2144 | |||
| 2145 | // Build signature object | ||
| 2146 | 741 | const Function signature = Function(node.methodName, nil<SymbolTableEntry*>, QualType(SuperType::TY_DYN), returnType, paramList, usedGenericTypes, cast<ASTNode*>(node)); | |
| 2147 | |||
| 2148 | // Add signature to current scope | ||
| 2149 | 741 | Function* manifestation = insertFunction(this.currentScope, signature, &node.signatureManifestations); | |
| 2150 | 741 | manifestation.entry = node.entry; | |
| 2151 | 741 | manifestation.used = true; | |
| 2152 | |||
| 2153 | // Prepare signature type | ||
| 2154 | 741 | const SuperType superType = isFunction ? SuperType::TY_FUNCTION : SuperType::TY_PROCEDURE; | |
| 2155 | 741 | const QualType signatureTypeBase = QualType(superType); | |
| 2156 | 741 | QualType signatureType = signatureTypeBase.getWithFunctionParamAndReturnTypes(returnType, paramTypes); | |
| 2157 | 741 | signatureType.setQualifiers(node.signatureQualifiers); | |
| 2158 | |||
| 2159 | // Set entry to signature type | ||
| 2160 | 741 | assert node.entry != nil<SymbolTableEntry*>; | |
| 2161 | 741 | node.entry.updateType(signatureType, false); | |
| 2162 | 741 | node.entry.used = true; | |
| 2163 | |||
| 2164 | 1482 | return Any(&node.signatureManifestations); | |
| 2165 | 742 | } | |
| 2166 | |||
| 2167 | 262093 | public f<Any> TypeChecker.visitDataType(ASTDataTypeNode* node) { | |
| 2168 | 262093 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2169 | |||
| 2170 | // Visit base data type | ||
| 2171 | 262093 | QualType qualType = this.visitType(cast<IVisitable*>(node.baseDataType)); | |
| 2172 | 262093 | if qualType.is(SuperType::TY_UNRESOLVED) { | |
| 2173 | 12 | return Any(qualType); | |
| 2174 | } | ||
| 2175 | |||
| 2176 | 262081 | Queue<TypeModifier> tmQueue = node.tmQueue; | |
| 2177 | 337082 | while tmQueue.getSize() > 0l { | |
| 2178 | 75018 | const TypeModifier typeModifier = tmQueue.front(); | |
| 2179 | |||
| 2180 | // Only the outermost array can have an unknown size | ||
| 2181 | 75018 | if qualType.isArray() && qualType.getArraySize() == ARRAY_SIZE_UNKNOWN { | |
| 2182 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::ARRAY_SIZE_INVALID, String("Usage of incomplete array type. Only the outermost array type may have unknown size")); | |
| 2183 | 1 | } | |
| 2184 | |||
| 2185 | 75017 | switch typeModifier.modifierType { | |
| 2186 | case TypeModifierType::TYPE_PTR: { | ||
| 2187 | 37822 | qualType = qualType.toPtr(baseNode); | |
| 2188 | 37822 | } | |
| 2189 | case TypeModifierType::TYPE_REF: { | ||
| 2190 | 36335 | qualType = qualType.toRef(baseNode); | |
| 2191 | 36335 | } | |
| 2192 | case TypeModifierType::TYPE_ARRAY: { | ||
| 2193 | 854 | unsigned int hardcodedSize = typeModifier.hardcodedSize; | |
| 2194 | 854 | const String& varName = typeModifier.sizeVarName; | |
| 2195 | 854 | if !varName.isEmpty() { | |
| 2196 | 422 | const SymbolTableEntry* globalVar = this.rootScope.lookupStrict(varName); | |
| 2197 | 422 | if globalVar == nil<const SymbolTableEntry*> { | |
| 2198 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, "Could not find global variable '" + varName + "' "); | |
| 2199 | 1 | } | |
| 2200 | 421 | const QualType globalVarType = globalVar.getQualType(); | |
| 2201 | 421 | if !globalVarType.isConst() { | |
| 2202 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::EXPECTED_CONST_VARIABLE, String("The size of the array must be known at compile time")); | |
| 2203 | 1 | } | |
| 2204 | 420 | if !globalVarType.is(SuperType::TY_INT) { | |
| 2205 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::OPERATOR_WRONG_DATA_TYPE, String("Expected variable of type int")); | |
| 2206 | 1 | } | |
| 2207 | 419 | const CompileTimeValue globalVarValue = globalVar.declNode.getCompileTimeValue(this.manIdx); | |
| 2208 | 419 | hardcodedSize = cast<unsigned int>(globalVarValue.getIntValue()); | |
| 2209 | 419 | } | |
| 2210 | |||
| 2211 | 851 | if typeModifier.hasSize && hardcodedSize <= 1u { | |
| 2212 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::ARRAY_SIZE_INVALID, String("The size of an array must be > 1 and explicitly stated")); | |
| 2213 | 1 | } | |
| 2214 | 850 | qualType = qualType.toArr(baseNode, hardcodedSize); | |
| 2215 | 850 | } | |
| 2216 | } | ||
| 2217 | 75013 | tmQueue.pop(); | |
| 2218 | 75018 | } | |
| 2219 | |||
| 2220 | // Attach the qualifiers to the type | ||
| 2221 | 262064 | if node.qualifierLst != nil<ASTQualifierLstNode*> { | |
| 2222 | 111696 | const QualType baseType = qualType.getBase(); | |
| 2223 | 111696 | const dyn signableTypes = [SuperType::TY_INT, SuperType::TY_LONG, SuperType::TY_SHORT, SuperType::TY_BYTE, SuperType::TY_CHAR, SuperType::TY_GENERIC]; | |
| 2224 | 236388 | foreach ASTQualifierNode* qualifier : node.qualifierLst.qualifiers { | |
| 2225 | 124694 | ASTNode* qualifierBaseNode = cast<ASTNode*>(qualifier); | |
| 2226 | 124694 | TypeQualifiers& qualifiers = qualType.getQualifiers(); | |
| 2227 | 124694 | if qualifier.qualifierType == QualifierType::CONST { | |
| 2228 | 46697 | qualifiers.isConst = true; | |
| 2229 | 124694 | } else if qualifier.qualifierType == QualifierType::SIGNED { | |
| 2230 | 7 | if !baseType.isOneOf(signableTypes, len(signableTypes)) { | |
| 2231 | ✗ | return this.softErrorQT(qualifierBaseNode, SemanticErrorType::QUALIFIER_AT_ILLEGAL_CONTEXT, "Cannot use this qualifier on type " + baseType.getName(false)); | |
| 2232 | ✗ | } | |
| 2233 | 7 | qualifiers.isSigned = true; | |
| 2234 | 7 | qualifiers.isUnsigned = false; | |
| 2235 | 77997 | } else if qualifier.qualifierType == QualifierType::UNSIGNED { | |
| 2236 | 52009 | if !baseType.isOneOf(signableTypes, len(signableTypes)) { | |
| 2237 | ✗ | return this.softErrorQT(qualifierBaseNode, SemanticErrorType::QUALIFIER_AT_ILLEGAL_CONTEXT, "Cannot use this qualifier on type " + baseType.getName(false)); | |
| 2238 | ✗ | } | |
| 2239 | 52009 | qualifiers.isSigned = false; | |
| 2240 | 52009 | qualifiers.isUnsigned = true; | |
| 2241 | 77990 | } else if qualifier.qualifierType == QualifierType::HEAP { | |
| 2242 | // Heap variables can only be pointers | ||
| 2243 | 23571 | const QualType unwrappedType = qualType.removeReferenceWrapper(); | |
| 2244 | 23571 | const dyn heapTypes = [SuperType::TY_PTR, SuperType::TY_ARRAY, SuperType::TY_STRING]; | |
| 2245 | 23571 | if !unwrappedType.isOneOf(heapTypes, len(heapTypes)) { | |
| 2246 | 2 | return this.softErrorQT(qualifierBaseNode, SemanticErrorType::QUALIFIER_AT_ILLEGAL_CONTEXT, "The heap qualifier can only be applied to symbols of pointer type, you provided " + baseType.getName(false)); | |
| 2247 | 1 | } | |
| 2248 | 23570 | qualifiers.isHeap = true; | |
| 2249 | 25980 | } else if qualifier.qualifierType == QualifierType::COMPOSE && node.isFieldType { | |
| 2250 | 19 | if !qualType.is(SuperType::TY_STRUCT) { | |
| 2251 | 2 | return this.softErrorQT(qualifierBaseNode, SemanticErrorType::QUALIFIER_AT_ILLEGAL_CONTEXT, String("The compose qualifier can only be used on plain struct fields")); | |
| 2252 | 1 | } | |
| 2253 | 18 | qualifiers.isComposition = true; | |
| 2254 | 2409 | } else if qualifier.qualifierType == QualifierType::PUBLIC && (node.isFieldType || node.isGlobalType) { | |
| 2255 | 2391 | qualifiers.isPublic = true; | |
| 2256 | 2391 | } else { | |
| 2257 | ✗ | String entryName = String("local variable"); | |
| 2258 | ✗ | if node.isGlobalType { | |
| 2259 | ✗ | entryName = String("global variable"); | |
| 2260 | ✗ | } else if node.isFieldType { | |
| 2261 | ✗ | entryName = String("field"); | |
| 2262 | ✗ | } else if node.isParamType { | |
| 2263 | ✗ | entryName = String("param"); | |
| 2264 | ✗ | } else if node.isReturnType { | |
| 2265 | ✗ | entryName = String("return variable"); | |
| 2266 | ✗ | } | |
| 2267 | ✗ | return this.softErrorQT(qualifierBaseNode, SemanticErrorType::QUALIFIER_AT_ILLEGAL_CONTEXT, "Cannot use this qualifier on a " + entryName + " definition"); | |
| 2268 | 77995 | } | |
| 2269 | 124692 | } | |
| 2270 | 111694 | } | |
| 2271 | |||
| 2272 | 524124 | return Any(node.node.setEvaluatedSymbolType(qualType, this.manIdx)); | |
| 2273 | 262069 | } | |
| 2274 | |||
| 2275 | 262087 | public f<Any> TypeChecker.visitBaseDataType(ASTBaseDataTypeNode* node) { | |
| 2276 | // Note: The type is assigned in the cases and returned afterwards | ||
| 2277 | 262087 | QualType qualType = QualType(SuperType::TY_DYN); | |
| 2278 | 262087 | switch node.baseType { | |
| 2279 | case BaseDataTypeType::TYPE_DOUBLE: { | ||
| 2280 | 1857 | qualType = QualType(SuperType::TY_DOUBLE); | |
| 2281 | 1857 | } | |
| 2282 | case BaseDataTypeType::TYPE_INT: { | ||
| 2283 | 23063 | qualType = QualType(SuperType::TY_INT); | |
| 2284 | 23063 | } | |
| 2285 | case BaseDataTypeType::TYPE_SHORT: { | ||
| 2286 | 3567 | qualType = QualType(SuperType::TY_SHORT); | |
| 2287 | 3567 | } | |
| 2288 | case BaseDataTypeType::TYPE_LONG: { | ||
| 2289 | 42863 | qualType = QualType(SuperType::TY_LONG); | |
| 2290 | 42863 | } | |
| 2291 | case BaseDataTypeType::TYPE_BYTE: { | ||
| 2292 | 15123 | qualType = QualType(SuperType::TY_BYTE); | |
| 2293 | 15123 | } | |
| 2294 | case BaseDataTypeType::TYPE_CHAR: { | ||
| 2295 | 31752 | qualType = QualType(SuperType::TY_CHAR); | |
| 2296 | 31752 | } | |
| 2297 | case BaseDataTypeType::TYPE_STRING: { | ||
| 2298 | 30879 | qualType = QualType(SuperType::TY_STRING); | |
| 2299 | 30879 | } | |
| 2300 | case BaseDataTypeType::TYPE_BOOL: { | ||
| 2301 | 15126 | qualType = QualType(SuperType::TY_BOOL); | |
| 2302 | 15126 | } | |
| 2303 | case BaseDataTypeType::TYPE_CUSTOM: { | ||
| 2304 | 95273 | qualType = this.visitType(cast<IVisitable*>(node.customDataType)); | |
| 2305 | 95273 | if qualType.is(SuperType::TY_UNRESOLVED) { | |
| 2306 | 12 | return Any(qualType); | |
| 2307 | } | ||
| 2308 | 95261 | } | |
| 2309 | case BaseDataTypeType::TYPE_FUNCTION: { | ||
| 2310 | 882 | qualType = this.visitType(node.functionDataType); | |
| 2311 | 882 | if qualType.is(SuperType::TY_UNRESOLVED) { | |
| 2312 | ✗ | return Any(qualType); | |
| 2313 | } | ||
| 2314 | 882 | } | |
| 2315 | } | ||
| 2316 | 262075 | return Any(node.node.setEvaluatedSymbolType(qualType, this.manIdx)); | |
| 2317 | } | ||
| 2318 | |||
| 2319 | 95273 | public f<Any> TypeChecker.visitCustomDataType(ASTCustomDataTypeNode* node) { | |
| 2320 | 95273 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2321 | |||
| 2322 | // It is a struct type -> get the access scope | ||
| 2323 | 95273 | const String firstFragment = node.typeNameFragments.front(); | |
| 2324 | |||
| 2325 | // Check this type requires a runtime module | ||
| 2326 | 95273 | if node.typeNameFragments.getSize() == 1l { | |
| 2327 | 95177 | this.ensureLoadedRuntimeForTypeName(firstFragment); | |
| 2328 | 95177 | } | |
| 2329 | |||
| 2330 | // A type can either be a single fragment like "Test" or multiple fragments "a.b.Test", which means it is imported. | ||
| 2331 | 95273 | bool isImported = node.typeNameFragments.getSize() > 1l; | |
| 2332 | 95273 | GenericType* genericType = this.rootScope.lookupGenericTypeStrict(firstFragment); | |
| 2333 | 95273 | if genericType != nil<GenericType*> { | |
| 2334 | 46641 | assert !isImported; | |
| 2335 | // Take the concrete replacement type for the name of this generic type if available | ||
| 2336 | 46641 | QualType symbolType = genericType.qualType; | |
| 2337 | 46641 | if this.typeMapping.contains(firstFragment) { | |
| 2338 | 17702 | symbolType = this.typeMapping.get(firstFragment); | |
| 2339 | 17702 | } | |
| 2340 | |||
| 2341 | // Check if the replacement requires a runtime module | ||
| 2342 | 46641 | if symbolType.is(SuperType::TY_STRUCT) { | |
| 2343 | 4937 | this.ensureLoadedRuntimeForTypeName(symbolType.getSubType()); | |
| 2344 | 4937 | } | |
| 2345 | |||
| 2346 | 93282 | return Any(node.node.setEvaluatedSymbolType(symbolType, this.manIdx)); | |
| 2347 | } | ||
| 2348 | |||
| 2349 | // Check if the type exists in the exported names registry | ||
| 2350 | 48632 | const NameRegistryEntry* registryEntry = this.sourceFile.getNameRegistryEntry(node.fqTypeName); | |
| 2351 | 48632 | if registryEntry == nil<const NameRegistryEntry*> { | |
| 2352 | 5 | if this.sourceFile.isAmbiguousName(node.fqTypeName) { | |
| 2353 | 4 | return this.softErrorQT(baseNode, SemanticErrorType::AMBIGUOUS_SYMBOL, this.sourceFile.getAmbiguousNameMessage(node.fqTypeName)); | |
| 2354 | 2 | } | |
| 2355 | 6 | return this.softErrorQT(baseNode, SemanticErrorType::UNKNOWN_DATATYPE, "Unknown datatype '" + node.fqTypeName + "'"); | |
| 2356 | 3 | } | |
| 2357 | 48627 | assert registryEntry.targetEntry != nil<SymbolTableEntry*> && registryEntry.targetScope != nil<Scope*>; | |
| 2358 | 48627 | SymbolTableEntry* entry = registryEntry.targetEntry; | |
| 2359 | 48627 | entry.used = true; | |
| 2360 | 48627 | Scope* defScope = registryEntry.targetScope.parent; | |
| 2361 | 48627 | QualType entryType = entry.getQualType(); | |
| 2362 | |||
| 2363 | // With circular imports, two structs/interfaces in mutually importing files can reference each other, so neither can be | ||
| 2364 | // prepared strictly before the other. If we reach one here before it has been prepared (its type is still invalid), | ||
| 2365 | // assign its opaque type now - an implicit forward declaration. The full prepare pass assigns the identical interned | ||
| 2366 | // type later and additionally fills in the body and manifestations. | ||
| 2367 | 48627 | if entryType.is(SuperType::TY_INVALID) { | |
| 2368 | 10 | this.assignDeferredOpaqueType(entry); | |
| 2369 | 10 | entryType = entry.getQualType(); | |
| 2370 | 10 | } | |
| 2371 | |||
| 2372 | // Enums can early-return | ||
| 2373 | 48627 | if entryType.is(SuperType::TY_ENUM) { | |
| 2374 | 3410 | return Any(QualType(SuperType::TY_INT)); | |
| 2375 | } | ||
| 2376 | |||
| 2377 | 46922 | const dyn aggregateTypes = [SuperType::TY_STRUCT, SuperType::TY_INTERFACE, SuperType::TY_UNION]; | |
| 2378 | 46922 | if entryType.isOneOf(aggregateTypes, len(aggregateTypes)) { | |
| 2379 | 43058 | assert baseNode.parent.parent.kind == ASTNodeKind::DATA_TYPE; | |
| 2380 | |||
| 2381 | // Remember how many template types this struct/interface/union actually declares, before it gets overwritten below | ||
| 2382 | 43058 | const QualTypeList declaredTemplateTypes = entryType.getTemplateTypes(); | |
| 2383 | 43058 | const long requiredTemplateTypeCount = declaredTemplateTypes.getSize(); | |
| 2384 | |||
| 2385 | // Collect the concrete template types | ||
| 2386 | 43058 | bool allTemplateTypesConcrete = true; | |
| 2387 | 43058 | QualTypeList templateTypes; | |
| 2388 | 43058 | if node.templateTypeLst != nil<IVisitable*> { | |
| 2389 | 12829 | assert defScope != nil<Scope*>; | |
| 2390 | 12829 | isImported = defScope.isImportedBy(this.rootScope); | |
| 2391 | |||
| 2392 | 12829 | const ASTTypeLstNode* templateTypeLst = downcast<ASTTypeLstNode>(node.templateTypeLst); | |
| 2393 | 29547 | foreach ASTDataTypeNode* dataType : templateTypeLst.dataTypes { | |
| 2394 | 16718 | const QualType templateType = this.getDataType(dataType); | |
| 2395 | 16718 | if templateType.is(SuperType::TY_UNRESOLVED) { | |
| 2396 | ✗ | return Any(templateType); | |
| 2397 | } | ||
| 2398 | 16718 | if entryType.is(SuperType::TY_GENERIC) { | |
| 2399 | ✗ | allTemplateTypesConcrete = false; | |
| 2400 | 16718 | } else if isImported { | |
| 2401 | // Introduce the local type to the imported source file | ||
| 2402 | 6000 | const QualType importedType = this.mapLocalTypeToImportedScopeType(defScope, templateType); | |
| 2403 | 6000 | assert importedType.is(templateType.getSuperType()); | |
| 2404 | 16718 | } | |
| 2405 | 16718 | templateTypes.pushBack(templateType); | |
| 2406 | 16718 | } | |
| 2407 | 12829 | entryType = entryType.getWithTemplateTypes(templateTypes); | |
| 2408 | 12829 | } | |
| 2409 | |||
| 2410 | // Reject usage with a wrong number of template type arguments (including none, if some are required) | ||
| 2411 | 43058 | if templateTypes.getSize() != requiredTemplateTypeCount { | |
| 2412 | 2 | String kind = String("Union"); | |
| 2413 | 2 | if entryType.is(SuperType::TY_STRUCT) { | |
| 2414 | 2 | kind = String("Struct"); | |
| 2415 | 2 | } else if entryType.is(SuperType::TY_INTERFACE) { | |
| 2416 | ✗ | kind = String("Interface"); | |
| 2417 | ✗ | } | |
| 2418 | 2 | const String errorMessage = kind + " '" + node.fqTypeName + "' requires " + toString(requiredTemplateTypeCount) + " template type argument(s), but got " + toString(templateTypes.getSize()); | |
| 2419 | 4 | return this.softErrorQT(baseNode, SemanticErrorType::INVALID_TEMPLATE_TYPES, errorMessage); | |
| 2420 | 2 | } | |
| 2421 | |||
| 2422 | // Check if struct is defined before the current code location, if defined in the same source file. Across files | ||
| 2423 | // (e.g. circular imports) the order does not matter, so the check only applies within the same source file. | ||
| 2424 | 43056 | const CodeLoc& declCodeLoc = entry.declNode.codeLoc; | |
| 2425 | 43056 | const CodeLoc& codeLoc = node.codeLoc; | |
| 2426 | 43056 | if String(declCodeLoc.sourceFilePath) == String(codeLoc.sourceFilePath) && declCodeLoc.isAfter(codeLoc) { | |
| 2427 | 3 | if entryType.is(SuperType::TY_STRUCT) { | |
| 2428 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_STRUCT, String("Structs must be defined before usage")); | |
| 2429 | 1 | } | |
| 2430 | 2 | if entryType.is(SuperType::TY_INTERFACE) { | |
| 2431 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_INTERFACE, String("Interfaces must be defined before usage")); | |
| 2432 | 1 | } | |
| 2433 | 1 | if entryType.is(SuperType::TY_UNION) { | |
| 2434 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_UNION, String("Unions must be defined before usage")); | |
| 2435 | 1 | } | |
| 2436 | ✗ | assert false; // Unexpected entry type | |
| 2437 | } | ||
| 2438 | |||
| 2439 | 43053 | if allTemplateTypesConcrete { // Only do the next step, if we have concrete template types | |
| 2440 | // Set the struct/interface/union instance to used, if found | ||
| 2441 | // Here, it is allowed to accept, that the struct/interface/union cannot be found, because there are | ||
| 2442 | // self-referencing ones | ||
| 2443 | 43053 | const String& typeName = node.typeNameFragments.back(); | |
| 2444 | 43053 | if entryType.is(SuperType::TY_STRUCT) { | |
| 2445 | 42373 | const Struct* spiceStruct = matchStruct(defScope, typeName, templateTypes, baseNode); | |
| 2446 | 42373 | if spiceStruct != nil<const Struct*> { | |
| 2447 | 42211 | entryType = entryType.getWithBodyScope(spiceStruct.scope); | |
| 2448 | 42211 | } | |
| 2449 | 43053 | } else if entryType.is(SuperType::TY_INTERFACE) { | |
| 2450 | 649 | const Interface* spiceInterface = matchInterface(defScope, typeName, templateTypes, baseNode); | |
| 2451 | 649 | if spiceInterface != nil<const Interface*> { | |
| 2452 | 648 | entryType = entryType.getWithBodyScope(spiceInterface.scope); | |
| 2453 | 648 | } | |
| 2454 | 649 | } else { | |
| 2455 | 31 | assert entryType.is(SuperType::TY_UNION); | |
| 2456 | 31 | const Union* spiceUnion = matchUnion(defScope, typeName, templateTypes, baseNode); | |
| 2457 | 31 | if spiceUnion != nil<const Union*> { | |
| 2458 | 27 | entryType = entryType.getWithBodyScope(spiceUnion.scope); | |
| 2459 | 27 | } | |
| 2460 | 680 | } | |
| 2461 | 43053 | } | |
| 2462 | |||
| 2463 | 86106 | return Any(node.node.setEvaluatedSymbolType(entryType, this.manIdx)); | |
| 2464 | 43058 | } | |
| 2465 | |||
| 2466 | 3864 | if entryType.is(SuperType::TY_ALIAS) { | |
| 2467 | 3862 | const QualType aliasedType = entryType.getAliased(entry); | |
| 2468 | 7724 | return Any(node.node.setEvaluatedSymbolType(aliasedType, this.manIdx)); | |
| 2469 | } | ||
| 2470 | |||
| 2471 | // We tried everything to resolve it, but this type is still unknown | ||
| 2472 | 2 | if entryType.is(SuperType::TY_INVALID) { | |
| 2473 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::EXPECTED_TYPE, String("Used type before declared")); | |
| 2474 | 1 | } | |
| 2475 | 2 | return this.softErrorQT(baseNode, SemanticErrorType::EXPECTED_TYPE, "Expected type, but got " + entryType.getName()); | |
| 2476 | 95273 | } | |
| 2477 | |||
| 2478 | 882 | public f<Any> TypeChecker.visitFunctionDataType(ASTFunctionDataTypeNode* node) { | |
| 2479 | // Visit return type | ||
| 2480 | 882 | QualType returnType = QualType(SuperType::TY_DYN); | |
| 2481 | 882 | if node.isFunction { | |
| 2482 | 446 | returnType = this.getDataType(node.returnType); | |
| 2483 | 446 | if returnType.is(SuperType::TY_UNRESOLVED) { | |
| 2484 | ✗ | return Any(returnType); | |
| 2485 | } | ||
| 2486 | 446 | if returnType.is(SuperType::TY_DYN) { | |
| 2487 | ✗ | return this.softErrorQT(cast<ASTNode*>(node.returnType), SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Function types cannot have return type dyn")); | |
| 2488 | ✗ | } | |
| 2489 | 446 | } | |
| 2490 | |||
| 2491 | // Visit param types | ||
| 2492 | 882 | QualTypeList paramTypes; | |
| 2493 | 882 | if node.paramTypeLst != nil<ASTTypeLstNode*> { | |
| 2494 | 1337 | foreach ASTDataTypeNode* paramTypeNode : node.paramTypeLst.dataTypes { | |
| 2495 | 706 | const QualType paramType = this.getDataType(paramTypeNode); | |
| 2496 | 706 | if paramType.is(SuperType::TY_UNRESOLVED) { | |
| 2497 | ✗ | return Any(paramType); | |
| 2498 | } | ||
| 2499 | 706 | paramTypes.pushBack(paramType); | |
| 2500 | 706 | } | |
| 2501 | 631 | } | |
| 2502 | |||
| 2503 | // Build function type | ||
| 2504 | 882 | const SuperType superType = node.isFunction ? SuperType::TY_FUNCTION : SuperType::TY_PROCEDURE; | |
| 2505 | 882 | const QualType functionTypeBase = QualType(superType); | |
| 2506 | 882 | const QualType functionType = functionTypeBase.getWithFunctionParamAndReturnTypes(returnType, paramTypes); | |
| 2507 | |||
| 2508 | 1764 | return Any(node.node.setEvaluatedSymbolType(functionType, this.manIdx)); | |
| 2509 | 882 | } | |
| 2510 | |||
| 2511 | // ========================================================== Statements ========================================================== | ||
| 2512 | |||
| 2513 | 94713 | public f<Any> TypeChecker.visitStmtLst(ASTStmtLstNode* node) { | |
| 2514 | // Visit nodes in this scope | ||
| 2515 | 282839 | foreach IVisitable* stmt : node.statements { | |
| 2516 | 188126 | if stmt == nil<IVisitable*> { | |
| 2517 | ✗ | continue; | |
| 2518 | } | ||
| 2519 | // Print warning if statement is unreachable | ||
| 2520 | 188126 | ASTStmtNode* stmtNode = asStmtNode(getASTNode(stmt)); | |
| 2521 | 188126 | if stmtNode.unreachable { | |
| 2522 | 3 | this.addWarning(stmtNode.codeLoc, CompilerWarningType::UNREACHABLE_CODE, String("This statement is unreachable")); | |
| 2523 | 3 | continue; | |
| 2524 | 3 | } | |
| 2525 | // Visit the statement | ||
| 2526 | 188123 | this.visit(stmt); | |
| 2527 | 188123 | } | |
| 2528 | |||
| 2529 | // Do cleanup of this scope, e.g. dtor calls for struct instances | ||
| 2530 | 94713 | this.doScopeCleanup(node.resourcesToCleanup.get(this.manIdx), cast<ASTNode*>(node)); | |
| 2531 | |||
| 2532 | 94713 | return Any(); | |
| 2533 | } | ||
| 2534 | |||
| 2535 | 141536 | public f<Any> TypeChecker.visitDeclStmt(ASTDeclStmtNode* node) { | |
| 2536 | 141536 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2537 | 141536 | DeclStmtData& data = node.data.get(this.manIdx); | |
| 2538 | |||
| 2539 | // Retrieve entry of the lhs variable | ||
| 2540 | 141536 | SymbolTableEntry* localVarEntry = this.currentScope.lookupStrict(node.varName); | |
| 2541 | 141536 | assert localVarEntry != nil<SymbolTableEntry*>; | |
| 2542 | |||
| 2543 | 141536 | QualType localVarType = QualType(SuperType::TY_INVALID); | |
| 2544 | 141536 | if node.hasAssignment { | |
| 2545 | // Visit the right side | ||
| 2546 | 40138 | const ExprResult rhs = this.visitExpr(node.assignExpr); | |
| 2547 | 40138 | const QualType rhsTy = rhs.ty; | |
| 2548 | 40138 | SymbolTableEntry* rhsEntry = rhs.entry; | |
| 2549 | |||
| 2550 | // Capture anonymous info up front: getAssignResultType may delete the anonymous entry (temp stealing | ||
| 2551 | // in performStructAssign), turning rhsEntry into a dangling pointer. | ||
| 2552 | 40138 | const bool rhsIsAnonymous = rhsEntry != nil<SymbolTableEntry*> && rhsEntry.anonymous; | |
| 2553 | 40138 | String rhsEntryName = String(""); | |
| 2554 | 40138 | if rhsIsAnonymous { | |
| 2555 | 2591 | rhsEntryName = rhsEntry.name; | |
| 2556 | 2591 | } | |
| 2557 | |||
| 2558 | // Visit data type | ||
| 2559 | 40138 | localVarType = this.getDataType(node.dataType); | |
| 2560 | |||
| 2561 | // Check if type has to be inferred or both types are fixed | ||
| 2562 | 40138 | if !localVarType.is(SuperType::TY_UNRESOLVED) && !rhsTy.is(SuperType::TY_UNRESOLVED) { | |
| 2563 | 40083 | const bool isDeclaredConst = localVarType.qualifiers.isConst; | |
| 2564 | 40083 | const ExprResult lhsResult = ExprResult{localVarType, localVarEntry}; | |
| 2565 | 40083 | Pair<QualType, Function*> assignResult = this.opRuleManager.getAssignResultType(baseNode, lhsResult, rhs, true); | |
| 2566 | 40083 | localVarType = assignResult.getFirst(); | |
| 2567 | 40083 | data.calledCopyCtor = assignResult.getSecond(); | |
| 2568 | // Some assignment rules (e.g. for structs, pointers and references) derive the type from the rhs, so the variable | ||
| 2569 | // would get the constness of the rhs instead of the declared one. A declared const always holds. Otherwise, a copy is | ||
| 2570 | // not const. Only pointers and references keep the const of the rhs, because it belongs to the pointee or referent | ||
| 2571 | 40083 | if !localVarType.is(SuperType::TY_UNRESOLVED) { | |
| 2572 | 40070 | if isDeclaredConst { | |
| 2573 | 12197 | localVarType.makeConst(); | |
| 2574 | 40070 | } else if !localVarType.isPtr() && !localVarType.isRef() { | |
| 2575 | 20812 | localVarType.makeConst(false); | |
| 2576 | 27873 | } | |
| 2577 | 40070 | } | |
| 2578 | |||
| 2579 | // If this is a struct type, check if the type is known. If not, error out. A type that already carries a | ||
| 2580 | // body scope is genuinely resolved (e.g. it reached here through a generic substantiation or a deep | ||
| 2581 | // transitive import); only its unqualified name happens to be absent from this file's name registry, which | ||
| 2582 | // is not an error. | ||
| 2583 | 40083 | if localVarType.isBase(SuperType::TY_STRUCT) { | |
| 2584 | 6583 | const QualType localVarBaseType = localVarType.getBase(); | |
| 2585 | 6583 | const String structName = localVarBaseType.getSubType(); | |
| 2586 | 6583 | if this.sourceFile.getNameRegistryEntry(structName) == nil<const NameRegistryEntry*> && localVarBaseType.getBodyScope() == nil<Scope*> { | |
| 2587 | ✗ | this.softError(cast<ASTNode*>(node.dataType), SemanticErrorType::UNKNOWN_DATATYPE, String("Unknown struct type '") + structName + "'. Forgot to import?"); | |
| 2588 | ✗ | localVarType = QualType(SuperType::TY_UNRESOLVED); | |
| 2589 | ✗ | } | |
| 2590 | 6583 | } | |
| 2591 | 40083 | } else { | |
| 2592 | 55 | localVarType = QualType(SuperType::TY_UNRESOLVED); | |
| 2593 | 55 | } | |
| 2594 | |||
| 2595 | // If there is an anonymous entry attached (e.g. for struct instantiation) and we take over ownership, delete it. | ||
| 2596 | // Safe to call even if performStructAssign already deleted it: erasing by key is a no-op when absent. | ||
| 2597 | 40138 | if !localVarType.isRef() && rhsIsAnonymous { | |
| 2598 | 2514 | this.currentScope.symbolTable.deleteAnonymous(rhsEntryName); | |
| 2599 | 2514 | } | |
| 2600 | 40138 | } else { | |
| 2601 | // Visit data type | ||
| 2602 | 101398 | localVarType = this.getDataType(node.dataType); | |
| 2603 | |||
| 2604 | // References with no initialization are illegal | ||
| 2605 | 101398 | if localVarType.isRef() && !node.isFctParam && !node.isForEachItem { | |
| 2606 | 1 | this.softError(baseNode, SemanticErrorType::REFERENCE_WITHOUT_INITIALIZER, String("References must always be initialized directly")); | |
| 2607 | 1 | } | |
| 2608 | |||
| 2609 | // If this is a struct, check for the default ctor | ||
| 2610 | 101398 | if localVarType.is(SuperType::TY_STRUCT) && !node.isFctParam && !node.isForEachItem { | |
| 2611 | 1065 | Scope* matchScope = localVarType.getBodyScope(); | |
| 2612 | 1065 | assert matchScope != nil<Scope*>; | |
| 2613 | // Check if we are required to call a ctor | ||
| 2614 | 1065 | data.isCtorCallRequired = !localVarType.isTriviallyConstructible(baseNode); | |
| 2615 | // Check if we have a no-args ctor to call | ||
| 2616 | 1065 | const QualType& thisType = localVarType; | |
| 2617 | 1065 | const ArgList noArgs; | |
| 2618 | 1065 | const QualTypeList noTemplateTypeHints; | |
| 2619 | 1065 | data.calledInitCtor = matchFunction(matchScope, String(CTOR_FUNCTION_NAME), thisType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 2620 | 1065 | if data.calledInitCtor == nil<Function*> && data.isCtorCallRequired { | |
| 2621 | 6 | const String& structName = localVarType.getSubType(); | |
| 2622 | 12 | return this.softErrorQT(baseNode, SemanticErrorType::NO_MATCHING_CTOR_FOUND, "Struct '" + structName + "' is not trivially constructible and has no no-args constructor."); | |
| 2623 | 6 | } | |
| 2624 | 1065 | } | |
| 2625 | 101392 | } | |
| 2626 | |||
| 2627 | // Update the type of the variable | ||
| 2628 | 141530 | localVarEntry.updateType(localVarType, true); | |
| 2629 | 141530 | SymbolTableEntry*& entrySlot = node.entries.get(this.manIdx); | |
| 2630 | 141530 | entrySlot = localVarEntry; | |
| 2631 | |||
| 2632 | // Update the state of the variable | ||
| 2633 | 141530 | localVarEntry.updateState(LifecycleState::INITIALIZED); | |
| 2634 | |||
| 2635 | 141530 | return Any(localVarType); | |
| 2636 | } | ||
| 2637 | |||
| 2638 | 42557 | public f<Any> TypeChecker.visitReturnStmt(ASTReturnStmtNode* node) { | |
| 2639 | 42557 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2640 | |||
| 2641 | // Retrieve return variable entry | ||
| 2642 | 42557 | SymbolTableEntry* returnVar = this.currentScope.lookupResultVar(); | |
| 2643 | 42557 | const bool isFunction = returnVar != nil<SymbolTableEntry*>; | |
| 2644 | 42557 | QualType returnType = QualType(SuperType::TY_DYN); | |
| 2645 | 42557 | if isFunction { | |
| 2646 | 38294 | returnType = returnVar.getQualType(); | |
| 2647 | 38294 | } | |
| 2648 | |||
| 2649 | // Check if procedure with return value | ||
| 2650 | 42557 | if !isFunction { | |
| 2651 | 4263 | if node.hasReturnValue { | |
| 2652 | 6 | return this.softErrorER(getASTNode(node.assignExpr), SemanticErrorType::RETURN_WITH_VALUE_IN_PROCEDURE, String("Return with value in procedure is not allowed")); | |
| 2653 | 3 | } | |
| 2654 | 4260 | return Any(); | |
| 2655 | } | ||
| 2656 | |||
| 2657 | 38294 | if !node.hasReturnValue && !returnVar.isInitialized() { | |
| 2658 | 4 | return this.softErrorQT(baseNode, SemanticErrorType::RETURN_WITHOUT_VALUE_RESULT, String("Return without value, but result variable is not initialized yet")); | |
| 2659 | 2 | } | |
| 2660 | |||
| 2661 | 38292 | if !node.hasReturnValue { | |
| 2662 | 5 | return Any(); | |
| 2663 | } | ||
| 2664 | |||
| 2665 | // Visit right side | ||
| 2666 | 38287 | const ExprResult rhs = this.visitExpr(node.assignExpr); | |
| 2667 | 38287 | if rhs.ty.is(SuperType::TY_UNRESOLVED) { return Any(rhs.ty); } | |
| 2668 | |||
| 2669 | // A native lambda stores its captures in this frame, so returning a capturing lambda would let it outlive its | ||
| 2670 | // captures. Direct the user to the owning std Lambda wrapper. Non-capturing lambdas (plain function pointers) | ||
| 2671 | // are safe to return. The std Lambda wrapper hands its captures-flagged lambda back from get(), so std is exempt. | ||
| 2672 | 38283 | const QualType rhsBaseType = rhs.ty.getBase(); | |
| 2673 | 38283 | const dyn fctOptions = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 2674 | 38283 | if rhsBaseType.isOneOf(fctOptions, len(fctOptions)) && rhs.ty.hasLambdaCaptures() && !this.sourceFile.isStdFile { | |
| 2675 | 1 | const QualType nonCapturingType = rhsBaseType.getWithLambdaCaptures(false); | |
| 2676 | 1 | const String message = String("A capturing lambda cannot be returned, because its captures live in this frame and would dangle. Wrap it in 'Lambda<") + nonCapturingType.getName(false) + ">' from std/type/lambda instead."; | |
| 2677 | 2 | return this.softErrorER(getASTNode(node.assignExpr), SemanticErrorType::LAMBDA_CAPTURE_ESCAPE, message); | |
| 2678 | 1 | } | |
| 2679 | |||
| 2680 | // Capture anonymous info up front: getAssignResultType may delete the anonymous entry (temp stealing | ||
| 2681 | // in performStructAssign), turning rhs.entry into a dangling pointer. | ||
| 2682 | 38282 | SymbolTableEntry* rhsEntry = rhs.entry; | |
| 2683 | 38282 | const bool rhsIsAnonymous = rhsEntry != nil<SymbolTableEntry*> && rhsEntry.anonymous; | |
| 2684 | 38282 | String rhsEntryName = String(""); | |
| 2685 | 38282 | if rhsIsAnonymous { | |
| 2686 | 2690 | rhsEntryName = rhsEntry.name; | |
| 2687 | 2690 | } | |
| 2688 | 38282 | const bool rhsIsOptimizable = this.isReturnValueOptimizable(rhs); | |
| 2689 | |||
| 2690 | // Check if types match | ||
| 2691 | 38282 | const ExprResult returnResult = ExprResult{returnType, returnVar}; | |
| 2692 | 38282 | Pair<QualType, Function*> assignResult = this.opRuleManager.getAssignResultType(getASTNode(node.assignExpr), returnResult, rhs, false, true, ERROR_MSG_RETURN); | |
| 2693 | 38282 | ReturnStmtData& data = node.data.get(this.manIdx); | |
| 2694 | 38282 | data.calledCopyCtor = assignResult.getSecond(); | |
| 2695 | |||
| 2696 | // Check if the dtor call on the return value can be skipped | ||
| 2697 | 38282 | SymbolTableEntry*& returnedLocal = node.returnedLocals.get(this.manIdx); | |
| 2698 | 38282 | returnedLocal = nil<SymbolTableEntry*>; | |
| 2699 | 38282 | if rhsEntry != nil<SymbolTableEntry*> { | |
| 2700 | 11049 | if rhsIsAnonymous { | |
| 2701 | // If there is an anonymous entry attached (e.g. for struct instantiation), delete it. | ||
| 2702 | // Safe even if performStructAssign already deleted it: erasing by key is a no-op when absent. | ||
| 2703 | 2690 | this.currentScope.symbolTable.deleteAnonymous(rhsEntryName); | |
| 2704 | 11049 | } else if rhsIsOptimizable { | |
| 2705 | // Otherwise omit the destructor call of the local variable on the path of this return statement, because the caller | ||
| 2706 | // destructs the value. Other paths out of the scope of the variable (e.g. other returns) still have to destruct it. | ||
| 2707 | 2297 | returnedLocal = rhsEntry; | |
| 2708 | 8359 | } | |
| 2709 | 11049 | } | |
| 2710 | |||
| 2711 | 38282 | data.returnType = returnType; | |
| 2712 | 76564 | return Any(returnType); | |
| 2713 | 38282 | } | |
| 2714 | |||
| 2715 | 760 | public f<Any> TypeChecker.visitBreakStmt(ASTBreakStmtNode* node) { | |
| 2716 | 760 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2717 | |||
| 2718 | // Check if the stated number is valid | ||
| 2719 | 760 | if node.breakTimes < 1 { | |
| 2720 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_BREAK_NUMBER, String("Break count must be >= 1, you provided ") + toString(node.breakTimes)); | |
| 2721 | 1 | } | |
| 2722 | |||
| 2723 | // Check if we can break this often | ||
| 2724 | 759 | const unsigned int maxBreaks = this.currentScope.getLoopNestingDepth(); | |
| 2725 | 759 | if cast<unsigned int>(node.breakTimes) > maxBreaks { | |
| 2726 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_BREAK_NUMBER, String("We can only break ") + toString(cast<long>(maxBreaks)) + " time(s) here"); | |
| 2727 | 1 | } | |
| 2728 | |||
| 2729 | 758 | return Any(); | |
| 2730 | } | ||
| 2731 | |||
| 2732 | 1437 | public f<Any> TypeChecker.visitContinueStmt(ASTContinueStmtNode* node) { | |
| 2733 | 1437 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2734 | |||
| 2735 | // Check if the stated number is valid | ||
| 2736 | 1437 | if node.continueTimes < 1 { | |
| 2737 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_CONTINUE_NUMBER, String("Continue count must be >= 1, you provided ") + toString(node.continueTimes)); | |
| 2738 | 1 | } | |
| 2739 | |||
| 2740 | // Check if we can continue this often | ||
| 2741 | 1436 | const unsigned int maxContinues = this.currentScope.getLoopNestingDepth(); | |
| 2742 | 1436 | if cast<unsigned int>(node.continueTimes) > maxContinues { | |
| 2743 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_CONTINUE_NUMBER, String("We can only continue ") + toString(cast<long>(maxContinues)) + " time(s) here"); | |
| 2744 | 1 | } | |
| 2745 | |||
| 2746 | 1435 | return Any(); | |
| 2747 | } | ||
| 2748 | |||
| 2749 | 16 | public f<Any> TypeChecker.visitFallthroughStmt(ASTFallthroughStmtNode* node) { | |
| 2750 | // Check if we can do a fallthrough here | ||
| 2751 | 16 | if !this.currentScope.isInCaseBranch() { | |
| 2752 | 4 | return this.softErrorER(cast<ASTNode*>(node), SemanticErrorType::FALLTHROUGH_NOT_ALLOWED, String("Fallthrough is only allowed in case branches")); | |
| 2753 | 2 | } | |
| 2754 | |||
| 2755 | // Check if there is a branch to fall through to. This is not the case in the last case branch of a switch without default | ||
| 2756 | 14 | ASTNode* caseBranch = node.parent; | |
| 2757 | 34 | while caseBranch.kind != ASTNodeKind::CASE_BRANCH { | |
| 2758 | 20 | caseBranch = caseBranch.parent; | |
| 2759 | 20 | } | |
| 2760 | 14 | assert caseBranch.parent.kind == ASTNodeKind::SWITCH_STMT; | |
| 2761 | 14 | const ASTSwitchStmtNode* switchStmt = downcast<ASTSwitchStmtNode>(caseBranch.parent); | |
| 2762 | 14 | if !switchStmt.hasDefaultBranch && getASTNode(switchStmt.caseBranches.back()) == caseBranch { | |
| 2763 | 4 | return this.softErrorER(cast<ASTNode*>(node), SemanticErrorType::FALLTHROUGH_NOT_ALLOWED, String("There is no branch to fall through to from the last case branch of a switch without default branch")); | |
| 2764 | 2 | } | |
| 2765 | |||
| 2766 | 12 | return Any(); | |
| 2767 | } | ||
| 2768 | |||
| 2769 | 4819 | public f<Any> TypeChecker.visitAssertStmt(ASTAssertStmtNode* node) { | |
| 2770 | // Visit condition | ||
| 2771 | 4819 | const ExprResult condition = this.visitExpr(node.assignExpr); | |
| 2772 | 4819 | const QualType conditionType = condition.ty; | |
| 2773 | 4819 | if conditionType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 2774 | |||
| 2775 | // Check if condition evaluates to bool | ||
| 2776 | 4819 | if !conditionType.is(SuperType::TY_BOOL) { | |
| 2777 | 2 | return this.softErrorER(getASTNode(node.assignExpr), SemanticErrorType::ASSERTION_CONDITION_BOOL, String("The asserted condition must be of type bool")); | |
| 2778 | 1 | } | |
| 2779 | |||
| 2780 | // A failed assertion prints the stack trace, so the runtime that does that has to be loaded. Assertions are not | ||
| 2781 | // generated in release builds, so nothing needs to be loaded for them there | ||
| 2782 | 4818 | if this.cliOptions.printsStackTraceOnAbort() && this.cliOptions.buildMode != BuildMode::RELEASE && !this.sourceFile.isRT(RuntimeModule::STACK_TRACE_RT) { | |
| 2783 | 3 | this.sourceFile.requestRuntimeModule(RuntimeModule::STACK_TRACE_RT); | |
| 2784 | 3 | } | |
| 2785 | |||
| 2786 | 4818 | return Any(); | |
| 2787 | } | ||
| 2788 | |||
| 2789 | // ====================================================== Control structures ======================================================= | ||
| 2790 | |||
| 2791 | 12565 | public f<Any> TypeChecker.visitUnsafeBlock(ASTUnsafeBlockNode* node) { | |
| 2792 | // Change to unsafe block body scope | ||
| 2793 | 12565 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::UNSAFE_BODY); | |
| 2794 | |||
| 2795 | // Visit body | ||
| 2796 | 12565 | this.visit(node.body); | |
| 2797 | |||
| 2798 | 25130 | return Any(); | |
| 2799 | 12565 | } | |
| 2800 | |||
| 2801 | 5269 | public f<Any> TypeChecker.visitForLoop(ASTForLoopNode* node) { | |
| 2802 | // Change to for head scope | ||
| 2803 | 5269 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::LOOP_HEAD); | |
| 2804 | |||
| 2805 | // Visit loop variable declaration | ||
| 2806 | 5269 | this.visit(node.initDecl); | |
| 2807 | |||
| 2808 | // Visit condition | ||
| 2809 | 5269 | const ExprResult condition = this.visitInExprScope(node.condAssign); | |
| 2810 | 5269 | const QualType conditionType = condition.ty; | |
| 2811 | 5269 | if conditionType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 2812 | // Check if condition evaluates to bool | ||
| 2813 | 5269 | if !conditionType.is(SuperType::TY_BOOL) { | |
| 2814 | 2 | return this.softErrorER(getASTNode(node.condAssign), SemanticErrorType::CONDITION_MUST_BE_BOOL, String("For loop condition must be of type bool")); | |
| 2815 | 1 | } | |
| 2816 | |||
| 2817 | // Visit incrementer | ||
| 2818 | 5268 | this.visitInExprScope(node.incAssign); | |
| 2819 | |||
| 2820 | // Visit body | ||
| 2821 | 5268 | ScopeHandle bodyScopeHandle = ScopeHandle(&this.compilerPass, node.getBodyScopeId(), ScopeType::FOR_BODY); | |
| 2822 | 5268 | this.visit(node.body); | |
| 2823 | 5268 | bodyScopeHandle.leaveScopeEarly(); | |
| 2824 | |||
| 2825 | // Do cleanup of the head scope, e.g. the dtor call for the loop variable. It is executed once the loop is left | ||
| 2826 | 5268 | this.doScopeCleanup(node.headResourcesToCleanup.get(this.manIdx), cast<ASTNode*>(node)); | |
| 2827 | |||
| 2828 | 10536 | return Any(); | |
| 2829 | 5269 | } | |
| 2830 | |||
| 2831 | 447 | public f<Any> TypeChecker.visitForeachLoop(ASTForeachLoopNode* node) { | |
| 2832 | 447 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 2833 | |||
| 2834 | // Visit iterator assignment | ||
| 2835 | 447 | IVisitable* iteratorNode = node.iteratorAssign; | |
| 2836 | 447 | ASTNode* iteratorBaseNode = getASTNode(iteratorNode); | |
| 2837 | 447 | const ExprResult iteratorResult = this.visitExpr(iteratorNode); | |
| 2838 | 447 | QualType iteratorOrIterableType = iteratorResult.ty; | |
| 2839 | 447 | if iteratorOrIterableType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 2840 | 447 | iteratorOrIterableType = iteratorOrIterableType.removeReferenceWrapper(); | |
| 2841 | |||
| 2842 | // Retrieve iterator type | ||
| 2843 | 447 | QualType iteratorType = iteratorOrIterableType; | |
| 2844 | |||
| 2845 | 447 | ForeachLoopData& data = node.data.get(this.manIdx); | |
| 2846 | 447 | const ArgList noArgs; | |
| 2847 | 447 | const QualTypeList noTemplateTypeHints; | |
| 2848 | 447 | if iteratorOrIterableType.isIterable(baseNode) { | |
| 2849 | 405 | const QualType& iterableType = iteratorOrIterableType; | |
| 2850 | 405 | if iteratorOrIterableType.isArray() { // Array | |
| 2851 | 8 | const NameRegistryEntry* nameRegistryEntry = this.sourceFile.getNameRegistryEntry(String(ARRAY_ITERATOR_NAME)); | |
| 2852 | 8 | if nameRegistryEntry == nil<const NameRegistryEntry*> { | |
| 2853 | 1 | this.softError(baseNode, SemanticErrorType::UNKNOWN_DATATYPE, String("Forgot to import \"std/iterator/array-iterator\"?")); | |
| 2854 | 2 | return Any(); | |
| 2855 | 1 | } | |
| 2856 | 7 | nameRegistryEntry.targetEntry.used = true; | |
| 2857 | 7 | nameRegistryEntry.importEntry.used = true; | |
| 2858 | 7 | Scope* arrayIteratorScope = nameRegistryEntry.targetScope; | |
| 2859 | 7 | Scope* matchScope = arrayIteratorScope.parent; | |
| 2860 | 7 | assert matchScope.scopeType == ScopeType::GLOBAL; | |
| 2861 | 7 | QualType unsignedLongType = QualType(SuperType::TY_LONG); | |
| 2862 | 7 | unsignedLongType.makeUnsigned(true); | |
| 2863 | 7 | ArgList argTypes; | |
| 2864 | 7 | argTypes.pushBack(Pair<QualType, bool>(iterableType, false)); | |
| 2865 | 7 | argTypes.pushBack(Pair<QualType, bool>(unsignedLongType, false)); | |
| 2866 | 7 | const QualType thisType = QualType(SuperType::TY_DYN); | |
| 2867 | 7 | data.getIteratorFct = matchFunction(matchScope, String("iterate"), thisType, argTypes, noTemplateTypeHints, true, iteratorBaseNode); | |
| 2868 | 8 | } else { // Struct, implementing Iterator interface | |
| 2869 | 397 | Scope* matchScope = iterableType.getBodyScope(); | |
| 2870 | // Methods have no constness, so a const iterable is iterated like any other method is called on a const struct | ||
| 2871 | 397 | const QualType iterableThisType = iterableType.toNonConst(); | |
| 2872 | 397 | data.getIteratorFct = matchFunction(matchScope, String("getIterator"), iterableThisType, noArgs, noTemplateTypeHints, true, iteratorBaseNode); | |
| 2873 | 397 | } | |
| 2874 | 404 | if data.getIteratorFct == nil<Function*> { | |
| 2875 | 1 | throwSemanticError(iteratorBaseNode, SemanticErrorType::INVALID_ITERATOR, String("No getIterator() function found for the given iterable type")); | |
| 2876 | 2 | return Any(); | |
| 2877 | 1 | } | |
| 2878 | |||
| 2879 | 403 | const Function* getIteratorFct = data.getIteratorFct; | |
| 2880 | 403 | iteratorType = getIteratorFct.returnType; | |
| 2881 | // Add anonymous symbol to keep track of dtor call, if non-trivially destructible | ||
| 2882 | 403 | if !iteratorType.isTriviallyDestructible(iteratorBaseNode) { | |
| 2883 | 1 | this.currentScope.symbolTable.insertAnonymous(iteratorType, iteratorBaseNode); | |
| 2884 | 1 | } | |
| 2885 | 403 | } | |
| 2886 | |||
| 2887 | // Change to foreach head scope | ||
| 2888 | 445 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::LOOP_HEAD); | |
| 2889 | |||
| 2890 | // Check iterator type | ||
| 2891 | 445 | if !iteratorType.isIterator(baseNode) { | |
| 2892 | 1 | const String errMsg = String("Can only iterate over arrays or data structures, inheriting from IIterator or IIterable. You provided ") + iteratorType.getName(false, true); | |
| 2893 | 1 | this.softError(getASTNode(node.iteratorAssign), SemanticErrorType::OPERATOR_WRONG_DATA_TYPE, errMsg); | |
| 2894 | 2 | return Any(); | |
| 2895 | 1 | } | |
| 2896 | 444 | const QualTypeList iteratorTemplateTypes = iteratorType.getTemplateTypes(); | |
| 2897 | 444 | if iteratorTemplateTypes.isEmpty() { | |
| 2898 | 2 | return this.softErrorER(getASTNode(node.iteratorAssign), SemanticErrorType::INVALID_ITERATOR, String("Iterator has no generic arguments so that the item type could not be inferred")); | |
| 2899 | 1 | } | |
| 2900 | |||
| 2901 | 443 | const bool hasIdx = node.idxVarDecl != nil<IVisitable*>; | |
| 2902 | 443 | if hasIdx { | |
| 2903 | // Visit index declaration or assignment | ||
| 2904 | 34 | const QualType indexType = this.visitType(node.idxVarDecl); | |
| 2905 | 34 | if indexType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 2906 | // Check if index type is int | ||
| 2907 | 34 | if !indexType.is(SuperType::TY_LONG) { | |
| 2908 | 1 | const String message = String("Index in foreach loop must be of type long. You provided ") + indexType.getName(false); | |
| 2909 | 2 | return this.softErrorER(getASTNode(node.idxVarDecl), SemanticErrorType::FOREACH_IDX_NOT_LONG, message); | |
| 2910 | 1 | } | |
| 2911 | 33 | } | |
| 2912 | |||
| 2913 | // Retrieve .get(), .getIdx(), .isValid() and .next() functions | ||
| 2914 | 442 | Scope* matchScope = iteratorType.getBodyScope(); | |
| 2915 | 442 | QualType iteratorItemType = QualType(SuperType::TY_INVALID); | |
| 2916 | 442 | if hasIdx { | |
| 2917 | 33 | data.getIdxFct = matchFunction(matchScope, String("getIdx"), iteratorType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 2918 | 33 | if data.getIdxFct == nil<Function*> { return Any(); } | |
| 2919 | 33 | const Function* getIdxFct = data.getIdxFct; | |
| 2920 | 33 | const QualTypeList idxTemplateTypes = getIdxFct.returnType.getTemplateTypes(); | |
| 2921 | 33 | iteratorItemType = idxTemplateTypes.back(); | |
| 2922 | 33 | } else { | |
| 2923 | 409 | data.getFct = matchFunction(matchScope, String("get"), iteratorType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 2924 | 409 | if data.getFct == nil<Function*> { return Any(); } | |
| 2925 | 409 | const Function* getFct = data.getFct; | |
| 2926 | 409 | iteratorItemType = getFct.returnType; | |
| 2927 | 409 | } | |
| 2928 | 442 | data.isValidFct = matchFunction(matchScope, String("isValid"), iteratorType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 2929 | 442 | if data.isValidFct == nil<Function*> { return Any(); } | |
| 2930 | 442 | data.nextFct = matchFunction(matchScope, String("next"), iteratorType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 2931 | 442 | if data.nextFct == nil<Function*> { return Any(); } | |
| 2932 | |||
| 2933 | // Change to foreach body scope. The item lives there, since it is copied anew in each round | ||
| 2934 | 442 | ScopeHandle bodyScopeHandle = ScopeHandle(&this.compilerPass, node.getBodyScopeId(), ScopeType::FOREACH_BODY); | |
| 2935 | |||
| 2936 | // Retrieve item variable entry | ||
| 2937 | 442 | const ASTDeclStmtNode* itemVarDecl = downcast<ASTDeclStmtNode>(node.itemVarDecl); | |
| 2938 | 442 | SymbolTableEntry* itemVarSymbol = this.currentScope.lookupStrict(itemVarDecl.varName); | |
| 2939 | 442 | assert itemVarSymbol != nil<SymbolTableEntry*>; | |
| 2940 | |||
| 2941 | // Check type of the item | ||
| 2942 | 442 | QualType itemType = this.visitType(node.itemVarDecl); | |
| 2943 | 442 | if itemType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 2944 | 442 | if itemType.is(SuperType::TY_DYN) { // Perform type inference | |
| 2945 | // Update evaluated symbol type of the declaration data type | ||
| 2946 | 12 | itemVarDecl.dataType.node.setEvaluatedSymbolType(iteratorItemType, this.manIdx); | |
| 2947 | // Update item type | ||
| 2948 | 12 | itemType = iteratorItemType; | |
| 2949 | 12 | } | |
| 2950 | |||
| 2951 | // Check result type. This is important, also for copy ctor calls, etc. | ||
| 2952 | 442 | const ExprResult itemResult = ExprResult{itemType, itemVarSymbol}; | |
| 2953 | 442 | const ExprResult iteratorItemResult = ExprResult{iteratorItemType, nil<SymbolTableEntry*> /* always a temporary */}; | |
| 2954 | 442 | Pair<QualType, Function*> assignResult = this.opRuleManager.getAssignResultType(cast<ASTNode*>(itemVarDecl), itemResult, iteratorItemResult, true, false, ERROR_FOREACH_ITEM); | |
| 2955 | 442 | data.calledItemCopyCtor = assignResult.getSecond(); | |
| 2956 | |||
| 2957 | // Update type of item | ||
| 2958 | 442 | itemVarSymbol.updateType(itemType, true); | |
| 2959 | |||
| 2960 | // Visit body | ||
| 2961 | 442 | this.visit(node.body); | |
| 2962 | 442 | bodyScopeHandle.leaveScopeEarly(); | |
| 2963 | |||
| 2964 | // Do cleanup of the head scope, e.g. the lifetime of the index variable. It is executed once the loop is left | ||
| 2965 | 442 | this.doScopeCleanup(node.headResourcesToCleanup.get(this.manIdx), baseNode); | |
| 2966 | |||
| 2967 | 884 | return Any(); | |
| 2968 | 447 | } | |
| 2969 | |||
| 2970 | 1876 | public f<Any> TypeChecker.visitWhileLoop(ASTWhileLoopNode* node) { | |
| 2971 | // Change to while body scope | ||
| 2972 | 1876 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::WHILE_BODY); | |
| 2973 | |||
| 2974 | // Visit condition | ||
| 2975 | 1876 | const ExprResult condition = this.visitInExprScope(node.condition); | |
| 2976 | 1876 | const QualType conditionType = condition.ty; | |
| 2977 | 1876 | if conditionType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 2978 | // Check if condition evaluates to bool | ||
| 2979 | 1874 | if !conditionType.is(SuperType::TY_BOOL) { | |
| 2980 | 2 | return this.softErrorER(getASTNode(node.condition), SemanticErrorType::CONDITION_MUST_BE_BOOL, String("While loop condition must be of type bool")); | |
| 2981 | 1 | } | |
| 2982 | |||
| 2983 | // Visit body | ||
| 2984 | 1873 | this.visit(node.body); | |
| 2985 | |||
| 2986 | 3746 | return Any(); | |
| 2987 | 1876 | } | |
| 2988 | |||
| 2989 | 82 | public f<Any> TypeChecker.visitDoWhileLoop(ASTDoWhileLoopNode* node) { | |
| 2990 | // Change to while body scope | ||
| 2991 | 82 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::WHILE_BODY); | |
| 2992 | |||
| 2993 | // Visit body | ||
| 2994 | 82 | this.visit(node.body); | |
| 2995 | |||
| 2996 | // Visit condition | ||
| 2997 | 82 | const ExprResult condition = this.visitInExprScope(node.condition); | |
| 2998 | 82 | const QualType conditionType = condition.ty; | |
| 2999 | 82 | if conditionType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 3000 | // Check if condition evaluates to bool | ||
| 3001 | 82 | if !conditionType.is(SuperType::TY_BOOL) { | |
| 3002 | 2 | return this.softErrorER(getASTNode(node.condition), SemanticErrorType::CONDITION_MUST_BE_BOOL, String("Do-While loop condition must be of type bool")); | |
| 3003 | 1 | } | |
| 3004 | |||
| 3005 | 162 | return Any(); | |
| 3006 | 82 | } | |
| 3007 | |||
| 3008 | 27736 | public f<Any> TypeChecker.visitIfStmt(ASTIfStmtNode* node) { | |
| 3009 | // Change to then body scope | ||
| 3010 | 27736 | ScopeHandle scopeHandle = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::IF_ELSE_BODY); | |
| 3011 | |||
| 3012 | // Visit condition | ||
| 3013 | 27736 | const ExprResult condition = this.visitInExprScope(node.condition); | |
| 3014 | 27736 | const QualType conditionType = condition.ty; | |
| 3015 | 27736 | if conditionType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 3016 | // Check if condition evaluates to bool | ||
| 3017 | 27731 | ASTNode* conditionNode = getASTNode(node.condition); | |
| 3018 | 27731 | if !conditionType.is(SuperType::TY_BOOL) { | |
| 3019 | 4 | return this.softErrorER(conditionNode, SemanticErrorType::CONDITION_MUST_BE_BOOL, String("If condition must be of type bool")); | |
| 3020 | 2 | } | |
| 3021 | |||
| 3022 | // Warning for bool assignment | ||
| 3023 | 27729 | if conditionNode.kind == ASTNodeKind::ASSIGN_EXPR { | |
| 3024 | 1 | const ASTAssignExprNode* assignExprNode = downcast<ASTAssignExprNode>(conditionNode); | |
| 3025 | 1 | if assignExprNode.op == AssignOp::OP_ASSIGN { | |
| 3026 | 1 | this.addWarning(conditionNode.codeLoc, CompilerWarningType::BOOL_ASSIGN_AS_CONDITION, String("If you want to compare the values, use '=='")); | |
| 3027 | 1 | } | |
| 3028 | 1 | } | |
| 3029 | |||
| 3030 | // Update the information, if one of the branches should be skipped. | ||
| 3031 | // This is important to check again here, because the constness of the condition can have changed after type checking. | ||
| 3032 | 27729 | const bool constantCondition = conditionNode.hasCompileTimeValue(this.manIdx); | |
| 3033 | 27729 | bool compileThenBranch = true; | |
| 3034 | 27729 | bool compileElseBranch = true; | |
| 3035 | 27729 | if constantCondition { | |
| 3036 | 387 | const CompileTimeValue compileTimeValue = conditionNode.getCompileTimeValue(this.manIdx); | |
| 3037 | 387 | compileThenBranch = compileTimeValue.getBoolValue(); | |
| 3038 | 387 | compileElseBranch = !compileTimeValue.getBoolValue(); | |
| 3039 | 387 | } | |
| 3040 | 27729 | bool& compileThenBranchSlot = node.compileThenBranch.get(this.manIdx); | |
| 3041 | 27729 | compileThenBranchSlot = compileThenBranch; | |
| 3042 | 27729 | bool& compileElseBranchSlot = node.compileElseBranch.get(this.manIdx); | |
| 3043 | 27729 | compileElseBranchSlot = compileElseBranch; | |
| 3044 | |||
| 3045 | // Visit body | ||
| 3046 | 27729 | if compileThenBranch { | |
| 3047 | 27580 | this.visit(node.thenBody); | |
| 3048 | 27580 | } | |
| 3049 | |||
| 3050 | // Leave then body scope | ||
| 3051 | 27729 | scopeHandle.leaveScopeEarly(); | |
| 3052 | |||
| 3053 | // Visit else statement if existing | ||
| 3054 | 27729 | if compileElseBranch && node.elseStmt != nil<IVisitable*> { | |
| 3055 | 1326 | this.visit(node.elseStmt); | |
| 3056 | 1326 | } | |
| 3057 | |||
| 3058 | 55458 | return Any(); | |
| 3059 | 27736 | } | |
| 3060 | |||
| 3061 | 1326 | public f<Any> TypeChecker.visitElseStmt(ASTElseStmtNode* node) { | |
| 3062 | // Visit if statement in the case of an else if branch | ||
| 3063 | 1326 | if node.isElseIf { | |
| 3064 | 566 | this.visit(node.ifStmt); | |
| 3065 | 1132 | return Any(); | |
| 3066 | 566 | } | |
| 3067 | |||
| 3068 | // Change to else body scope | ||
| 3069 | 760 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::IF_ELSE_BODY); | |
| 3070 | |||
| 3071 | // Visit body | ||
| 3072 | 760 | this.visit(node.body); | |
| 3073 | |||
| 3074 | 1520 | return Any(); | |
| 3075 | 760 | } | |
| 3076 | |||
| 3077 | 36 | public f<Any> TypeChecker.visitSwitchStmt(ASTSwitchStmtNode* node) { | |
| 3078 | // Check expression type | ||
| 3079 | 36 | const ExprResult switchExpr = this.visitExpr(node.assignExpr); | |
| 3080 | 36 | const QualType exprType = switchExpr.ty; | |
| 3081 | 36 | if exprType.is(SuperType::TY_UNRESOLVED) { return Any(); } | |
| 3082 | 36 | const dyn switchOptions = [SuperType::TY_INT, SuperType::TY_SHORT, SuperType::TY_LONG, SuperType::TY_BYTE, SuperType::TY_CHAR, SuperType::TY_BOOL]; | |
| 3083 | 36 | if !exprType.isOneOf(switchOptions, len(switchOptions)) { | |
| 3084 | 2 | return this.softErrorER(getASTNode(node.assignExpr), SemanticErrorType::SWITCH_EXPR_MUST_BE_PRIMITIVE, String("Switch expression must be of int, short, long, byte, char or bool type")); | |
| 3085 | 1 | } | |
| 3086 | |||
| 3087 | // Visit children | ||
| 3088 | 35 | this.visitChildren(cast<ASTNode*>(node)); | |
| 3089 | |||
| 3090 | // Check if case constant types match switch expression type | ||
| 3091 | 224 | foreach IVisitable* caseBranch : node.caseBranches { | |
| 3092 | 191 | const ASTCaseBranchNode* caseBranchNode = downcast<ASTCaseBranchNode>(caseBranch); | |
| 3093 | 385 | foreach IVisitable* constantNode : caseBranchNode.caseConstants { | |
| 3094 | 196 | const ExprResult constantResult = this.visitExpr(constantNode); | |
| 3095 | 196 | const QualType constantType = constantResult.ty; | |
| 3096 | 196 | if !constantType.matches(exprType, false, true, true) { | |
| 3097 | 4 | return this.softErrorER(getASTNode(constantNode), SemanticErrorType::SWITCH_CASE_TYPE_MISMATCH, String("Case value type does not match the switch expression type")); | |
| 3098 | 2 | } | |
| 3099 | 194 | } | |
| 3100 | 189 | } | |
| 3101 | |||
| 3102 | 66 | return Any(); | |
| 3103 | 35 | } | |
| 3104 | |||
| 3105 | 191 | public f<Any> TypeChecker.visitCaseBranch(ASTCaseBranchNode* node) { | |
| 3106 | // Change to case body scope | ||
| 3107 | 191 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::CASE_BODY); | |
| 3108 | |||
| 3109 | // Visit constant list | ||
| 3110 | 387 | foreach IVisitable* constant : node.caseConstants { | |
| 3111 | 196 | this.visit(constant); | |
| 3112 | 196 | } | |
| 3113 | |||
| 3114 | // Visit body | ||
| 3115 | 191 | this.visit(node.body); | |
| 3116 | |||
| 3117 | 382 | return Any(); | |
| 3118 | 191 | } | |
| 3119 | |||
| 3120 | 392 | public f<Any> TypeChecker.visitCaseConstant(ASTCaseConstantNode* node) { | |
| 3121 | // If we have a normal constant, we can take the symbol type from there | ||
| 3122 | 392 | if node.constant != nil<ASTConstantNode*> { | |
| 3123 | 200 | return this.visitConstant(node.constant); | |
| 3124 | } | ||
| 3125 | 192 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3126 | |||
| 3127 | // Check if a local or global variable can be found by searching for the name | ||
| 3128 | 192 | SymbolTableEntry* entry = nil<SymbolTableEntry*>; | |
| 3129 | 192 | if node.identifierFragments.getSize() == 1l { | |
| 3130 | 4 | entry = this.currentScope.lookup(node.identifierFragments.back()); | |
| 3131 | 4 | } | |
| 3132 | |||
| 3133 | // If no local or global was found, search in the name registry | ||
| 3134 | 192 | if entry == nil<SymbolTableEntry*> { | |
| 3135 | 188 | const NameRegistryEntry* registryEntry = this.sourceFile.getNameRegistryEntry(node.fqIdentifier); | |
| 3136 | 188 | if registryEntry == nil<const NameRegistryEntry*> { | |
| 3137 | ✗ | if this.sourceFile.isAmbiguousName(node.fqIdentifier) { | |
| 3138 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::AMBIGUOUS_SYMBOL, this.sourceFile.getAmbiguousNameMessage(node.fqIdentifier)); | |
| 3139 | ✗ | } | |
| 3140 | ✗ | const String message = String("The variable '") + node.fqIdentifier + "' could not be found"; | |
| 3141 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 3142 | ✗ | } | |
| 3143 | 188 | entry = registryEntry.targetEntry; | |
| 3144 | 188 | } | |
| 3145 | 192 | assert entry != nil<SymbolTableEntry*>; | |
| 3146 | 192 | entry.used = true; | |
| 3147 | 192 | node.entry = entry; | |
| 3148 | |||
| 3149 | // Check for the correct type | ||
| 3150 | 192 | const QualType qualType = entry.getQualType(); | |
| 3151 | 192 | const dyn caseConstantOptions = [SuperType::TY_INT, SuperType::TY_SHORT, SuperType::TY_LONG, SuperType::TY_BYTE, SuperType::TY_CHAR, SuperType::TY_BOOL]; | |
| 3152 | 192 | if !qualType.isOneOf(caseConstantOptions, len(caseConstantOptions)) { | |
| 3153 | 4 | return this.softErrorER(baseNode, SemanticErrorType::CASE_CONSTANT_INVALID_TYPE, String("Case constants must be of type int, short, long, byte, char, bool or enum")); | |
| 3154 | 2 | } | |
| 3155 | |||
| 3156 | 190 | return Any(ExprResult{node.node.setEvaluatedSymbolType(qualType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 3157 | } | ||
| 3158 | |||
| 3159 | 20 | public f<Any> TypeChecker.visitDefaultBranch(ASTDefaultBranchNode* node) { | |
| 3160 | // Change to default body scope | ||
| 3161 | 20 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::DEFAULT_BODY); | |
| 3162 | |||
| 3163 | // Visit body | ||
| 3164 | 20 | this.visit(node.body); | |
| 3165 | |||
| 3166 | 40 | return Any(); | |
| 3167 | 20 | } | |
| 3168 | |||
| 3169 | 126 | public f<Any> TypeChecker.visitAnonymousBlockStmt(ASTAnonymousBlockStmtNode* node) { | |
| 3170 | // Change to anonymous scope body scope | ||
| 3171 | 126 | ScopeHandle _ = ScopeHandle(&this.compilerPass, node.getScopeId(), ScopeType::ANONYMOUS_BLOCK_BODY); | |
| 3172 | |||
| 3173 | // Visit body | ||
| 3174 | 126 | this.visit(node.body); | |
| 3175 | |||
| 3176 | 252 | return Any(); | |
| 3177 | 126 | } | |
| 3178 | |||
| 3179 | // ========================================================= Expressions ========================================================== | ||
| 3180 | |||
| 3181 | /** | ||
| 3182 | * Check if a temporary struct is moved into the lhs of an assignment (temp stealing) and the old value of the lhs can | ||
| 3183 | * safely be destructed before it gets overwritten. | ||
| 3184 | * | ||
| 3185 | * @param node Assign expression node | ||
| 3186 | * @param rhsType Type of the right side of the assignment | ||
| 3187 | * @param isRhsTemporary Whether the right side of the assignment is a temporary | ||
| 3188 | * @return Destructible temp steal target or not | ||
| 3189 | */ | ||
| 3190 | 741 | f<bool> TypeChecker.isDestructibleTempStealTarget(ASTAssignExprNode* node, const QualType& rhsType, bool isRhsTemporary) { | |
| 3191 | // The rhs must be a temporary struct value, that is moved into the lhs, and no assign operator overload may take over | ||
| 3192 | 741 | if rhsType.isRef() || !rhsType.is(SuperType::TY_STRUCT) || !isRhsTemporary { | |
| 3193 | 272 | return false; | |
| 3194 | } | ||
| 3195 | 469 | const Vector<const Function*>& opFcts = this.getOpFctPointers(cast<ASTNode*>(node)); | |
| 3196 | 469 | if opFcts.front() != nil<const Function*> { | |
| 3197 | 424 | return false; | |
| 3198 | } | ||
| 3199 | // The initialization state of the lhs entry does not tell, whether the overwritten value was actually constructed at | ||
| 3200 | // runtime (e.g. for '*ptr = ...', 'array[i] = ...' or 'result.field = ...'), so check the lhs expression itself | ||
| 3201 | 45 | return this.isAlwaysConstructedLvalue(getASTNode(node.lhs)); | |
| 3202 | } | ||
| 3203 | |||
| 3204 | /** | ||
| 3205 | * Check if the value, that an lvalue expression denotes, has always been constructed at runtime, when it is accessed. | ||
| 3206 | * This is the case for variables (except the result variable) and chains of field accesses on them or on 'this', as long | ||
| 3207 | * as each involved field value is referenced or its struct has a no-args ctor. Variables are constructed at their | ||
| 3208 | * declaration: either explicitly, by the no-args ctor or, for trivially constructible structs, by zero-initialization. All | ||
| 3209 | * other structs require an initial value. Field values of structs with a no-args ctor are constructed in the ctor | ||
| 3210 | * preamble. Field values of other structs might still be uninitialized memory, e.g. in a ctor body. | ||
| 3211 | * | ||
| 3212 | * @param node Lvalue expression | ||
| 3213 | * @return Always constructed or not | ||
| 3214 | */ | ||
| 3215 | 2564 | f<bool> TypeChecker.isAlwaysConstructedLvalue(ASTNode* node) { | |
| 3216 | // Check the accessed value itself | ||
| 3217 | 2564 | ASTExprNode* exprNode = asExprNode(node); | |
| 3218 | 2564 | const QualType lvalueType = exprNode.getEvaluatedSymbolType(this.manIdx); | |
| 3219 | 2564 | ASTAtomicExprNode* atomicExpr = nil<ASTAtomicExprNode*>; | |
| 3220 | 2564 | if node.kind == ASTNodeKind::ATOMIC_EXPR { | |
| 3221 | 64 | atomicExpr = downcastMut<ASTAtomicExprNode>(node); | |
| 3222 | 64 | } | |
| 3223 | 2564 | const bool isThis = atomicExpr != nil<ASTAtomicExprNode*> && atomicExpr.fqIdentifier == THIS_VARIABLE_NAME; | |
| 3224 | 2564 | if !isThis && !lvalueType.isRef() { | |
| 3225 | 2519 | if !lvalueType.is(SuperType::TY_STRUCT) { | |
| 3226 | 2051 | return false; | |
| 3227 | } | ||
| 3228 | // Field values additionally require a no-args ctor (see above) | ||
| 3229 | 468 | const QualType structType = lvalueType.toNonConst(); | |
| 3230 | 468 | const ArgList noArgs; | |
| 3231 | 468 | if atomicExpr == nil<ASTAtomicExprNode*> && lookupFunction(structType.getBodyScope(), String(CTOR_FUNCTION_NAME), structType, noArgs, false) == nil<const Function*> { | |
| 3232 | 404 | return false; | |
| 3233 | } | ||
| 3234 | 468 | } | |
| 3235 | |||
| 3236 | // Variables are constructed, except the result variable, which is only constructed by assigning it | ||
| 3237 | 109 | if atomicExpr != nil<ASTAtomicExprNode*> { | |
| 3238 | 50 | return atomicExpr.fqIdentifier != RETURN_VARIABLE_NAME; | |
| 3239 | } | ||
| 3240 | |||
| 3241 | // Fields are constructed, if the value they are accessed on is constructed | ||
| 3242 | 59 | if node.kind != ASTNodeKind::POSTFIX_UNARY_EXPR { | |
| 3243 | ✗ | return false; | |
| 3244 | } | ||
| 3245 | 59 | const ASTPostfixUnaryExprNode* postfixUnaryExpr = downcast<ASTPostfixUnaryExprNode>(node); | |
| 3246 | 59 | if postfixUnaryExpr.op != PostfixUnaryOp::OP_MEMBER_ACCESS { | |
| 3247 | ✗ | return false; | |
| 3248 | } | ||
| 3249 | 59 | return this.isAlwaysConstructedLvalue(getASTNode(postfixUnaryExpr.postfixUnaryExpr)); | |
| 3250 | } | ||
| 3251 | |||
| 3252 | /** | ||
| 3253 | * Check if the value of a return statement can be returned without copying it (RVO). This is only the case for local | ||
| 3254 | * variables of the current function/procedure/lambda, because their dtor call can be omitted and the caller takes over | ||
| 3255 | * the ownership. Fields, globals, captured variables and values that are reached through a variable (e.g. by | ||
| 3256 | * dereferencing a pointer) are still owned by someone else, so they have to be copied. | ||
| 3257 | * | ||
| 3258 | * @param returnValue Value of the return statement | ||
| 3259 | * @return Return value optimizable or not | ||
| 3260 | */ | ||
| 3261 | 38868 | public f<bool> TypeChecker.isReturnValueOptimizable(const ExprResult& returnValue) { | |
| 3262 | // The return value must be a variable, not a temporary or a reference | ||
| 3263 | 38868 | if returnValue.isTemporary() || returnValue.ty.isRef() { | |
| 3264 | 30426 | return false; | |
| 3265 | } | ||
| 3266 | // The return value must be the variable itself, not a value that is reached through it | ||
| 3267 | 8442 | const SymbolTableEntry* entry = returnValue.entry; | |
| 3268 | 8442 | const QualType entryType = entry.getQualType(); | |
| 3269 | 8442 | if !entryType.matches(returnValue.ty, false, true, true) { | |
| 3270 | 1227 | return false; | |
| 3271 | } | ||
| 3272 | // The variable must be declared in the current function/procedure/lambda | ||
| 3273 | 7215 | const Scope* functionScope = this.currentScope.getFunctionScope(); | |
| 3274 | 7215 | const Scope* scope = entry.scope; | |
| 3275 | 16988 | while scope != nil<const Scope*> { | |
| 3276 | 14153 | if scope.scopeType == ScopeType::FUNC_PROC_BODY || scope.scopeType == ScopeType::LAMBDA_BODY { | |
| 3277 | 4380 | return scope == functionScope; | |
| 3278 | } | ||
| 3279 | 9773 | scope = scope.parent; | |
| 3280 | 9773 | } | |
| 3281 | 2835 | return false; | |
| 3282 | } | ||
| 3283 | |||
| 3284 | 30513 | public f<Any> TypeChecker.visitAssignExpr(ASTAssignExprNode* node) { | |
| 3285 | 30513 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3286 | |||
| 3287 | // Check if ternary | ||
| 3288 | 30513 | if node.ternaryExpr != nil<IVisitable*> { | |
| 3289 | ✗ | const ExprResult ternaryResult = this.visitExpr(node.ternaryExpr); | |
| 3290 | ✗ | node.node.setEvaluatedSymbolType(ternaryResult.ty, this.manIdx); | |
| 3291 | ✗ | return Any(ternaryResult); | |
| 3292 | } | ||
| 3293 | |||
| 3294 | // Check if assignment | ||
| 3295 | 30513 | if node.op == AssignOp::OP_NONE { | |
| 3296 | ✗ | panic(Error("AssignExpr fall-through")); | |
| 3297 | } | ||
| 3298 | |||
| 3299 | // Visit the right side first | ||
| 3300 | 30513 | const ExprResult rhs = this.visitExpr(node.rhs); | |
| 3301 | 30513 | QualType rhsType = rhs.ty; | |
| 3302 | 30513 | if rhsType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3303 | // Then visit the left side | ||
| 3304 | 30512 | const ExprResult lhs = this.visitExpr(node.lhs); | |
| 3305 | 30512 | const QualType lhsType = lhs.ty; | |
| 3306 | 30512 | SymbolTableEntry* lhsVar = lhs.entry; | |
| 3307 | 30512 | if lhsType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3308 | |||
| 3309 | // A native lambda stores its captures in the stack frame that created it. Storing a capturing lambda in a | ||
| 3310 | // struct field lets it outlive those captures, so the captures would dangle. Reject this and direct the user | ||
| 3311 | // to the owning std Lambda wrapper. Non-capturing lambdas (plain function pointers) are safe to store and are | ||
| 3312 | // not affected. The std Lambda wrapper itself stores the captures-flagged lambda in its own field, so std | ||
| 3313 | // files are exempt. | ||
| 3314 | 30509 | if node.op == AssignOp::OP_ASSIGN && lhs.entry != nil<SymbolTableEntry*> && lhs.entry.isField() { | |
| 3315 | 21691 | const QualType rhsBaseType = rhsType.getBase(); | |
| 3316 | 21691 | const dyn fctOptions = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 3317 | 21691 | if rhsBaseType.isOneOf(fctOptions, len(fctOptions)) && rhsType.hasLambdaCaptures() && !this.sourceFile.isStdFile { | |
| 3318 | 1 | const QualType nonCapturingType = rhsBaseType.getWithLambdaCaptures(false); | |
| 3319 | 1 | const String typeName = nonCapturingType.getName(false); | |
| 3320 | 1 | const String message = String("A capturing lambda cannot be stored in a field, because its captures live in the frame that created it and would dangle. Wrap it in 'Lambda<") + typeName + ">' from std/type/lambda instead."; | |
| 3321 | 2 | return this.softErrorER(getASTNode(node.rhs), SemanticErrorType::LAMBDA_CAPTURE_ESCAPE, message); | |
| 3322 | 1 | } | |
| 3323 | 21690 | } | |
| 3324 | |||
| 3325 | // Take a look at the operator | ||
| 3326 | 30508 | if node.op == AssignOp::OP_ASSIGN { | |
| 3327 | 27741 | const bool isDecl = lhs.entry != nil<SymbolTableEntry*> && lhs.entry.isField() && !lhs.entry.isInitialized(); | |
| 3328 | // Save this before checking the assignment, because stealing a temporary deletes its anonymous symbol table entry | ||
| 3329 | 27741 | const bool isRhsTemporary = rhs.isTemporary(); | |
| 3330 | // A field is only initialized by an assignment to a direct field of 'this' in a ctor body, which also binds a reference | ||
| 3331 | // field (like the IR generator does). Elsewhere, the assignment writes the field (or through the reference), so it gets | ||
| 3332 | // checked as such, e.g. for constness | ||
| 3333 | 27741 | const bool isFieldInitAssign = this.currentScope.isInCtorBody() && isDirectFieldOfThis(getASTNode(node.lhs)); | |
| 3334 | 27741 | const bool isDeclAssign = isDecl && isFieldInitAssign; | |
| 3335 | 27741 | Pair<QualType, Function*> assignResult = this.opRuleManager.getAssignResultType(baseNode, lhs, rhs, isDeclAssign); | |
| 3336 | 27741 | rhsType = assignResult.getFirst(); | |
| 3337 | 27741 | const Function* copyCtor = assignResult.getSecond(); | |
| 3338 | // If the assignment overwrites an already initialized struct, the old value of the lhs must be destructed first. | ||
| 3339 | // Otherwise its owning members (heap pointers, strings, ...) would leak. This applies when a new value is copied | ||
| 3340 | // into the lhs (non-null copy ctor) and when a temporary is moved into it (temp stealing). | ||
| 3341 | // 'isInitialized()' is false for declarations, uninitialized fields and moved-from values, so those are skipped. | ||
| 3342 | // The lifecycle of a field is tracked per struct, not per instance, so the first assignment to a field in the source code | ||
| 3343 | // can overwrite the value of a constructed object as well, if the field is reached via a variable or 'this'. Only the | ||
| 3344 | // direct fields of 'this' in a ctor body might still be uninitialized. Nested fields were constructed in the ctor | ||
| 3345 | // preamble. Fields reached via a pointer might still be uninitialized memory (e.g. a raw allocation, that is | ||
| 3346 | // initialized field by field). | ||
| 3347 | // Unsafe blocks are excluded on purpose: code that manually manages object lifetimes there (e.g. the raw | ||
| 3348 | // element shifts in container implementations) relies on assignments not implicitly destructing the lhs. | ||
| 3349 | 27741 | bool overwritesValue = lhs.entry != nil<SymbolTableEntry*> && lhs.entry.isInitialized(); | |
| 3350 | 27741 | if isDecl { | |
| 3351 | 3228 | const bool isFieldInit = this.currentScope.isInCtorBody() && isDirectFieldOfThis(getASTNode(node.lhs)); | |
| 3352 | 3228 | overwritesValue = !isFieldInit && this.isAlwaysConstructedLvalue(getASTNode(node.lhs)); | |
| 3353 | 3228 | } | |
| 3354 | 27741 | if overwritesValue && lhs.entry != nil<SymbolTableEntry*> && !this.currentScope.doesAllowUnsafeOperations() { | |
| 3355 | 17766 | const QualType lhsUnwrappedType = lhs.ty.removeReferenceWrapper(); | |
| 3356 | 17766 | const QualType lhsSType = lhsUnwrappedType.toNonConst(); | |
| 3357 | 17766 | if lhsSType.is(SuperType::TY_STRUCT) && !lhsSType.isTriviallyDestructible(baseNode) && (copyCtor != nil<const Function*> || this.isDestructibleTempStealTarget(node, rhs.ty, isRhsTemporary)) { | |
| 3358 | 44 | const ArgList noArgs; | |
| 3359 | 44 | const Function*& lhsDtorFct = node.lhsDtorFct.get(this.manIdx); | |
| 3360 | 44 | lhsDtorFct = this.implicitlyCallStructMethod(lhsSType, String(DTOR_FUNCTION_NAME), noArgs, baseNode); | |
| 3361 | 44 | } | |
| 3362 | 17766 | } | |
| 3363 | 30508 | } else if node.op == AssignOp::OP_PLUS_EQUAL { | |
| 3364 | 1998 | const ExprResult plusEqualResult = this.opRuleManager.getPlusEqualResultType(baseNode, lhs, rhs); | |
| 3365 | 1998 | rhsType = plusEqualResult.ty; | |
| 3366 | 2767 | } else if node.op == AssignOp::OP_MINUS_EQUAL { | |
| 3367 | 94 | const ExprResult minusEqualResult = this.opRuleManager.getMinusEqualResultType(baseNode, lhs, rhs); | |
| 3368 | 94 | rhsType = minusEqualResult.ty; | |
| 3369 | 769 | } else if node.op == AssignOp::OP_MUL_EQUAL { | |
| 3370 | 160 | const ExprResult mulEqualResult = this.opRuleManager.getMulEqualResultType(baseNode, lhs, rhs); | |
| 3371 | 160 | rhsType = mulEqualResult.ty; | |
| 3372 | 675 | } else if node.op == AssignOp::OP_DIV_EQUAL { | |
| 3373 | 108 | const ExprResult divEqualResult = this.opRuleManager.getDivEqualResultType(baseNode, lhs, rhs); | |
| 3374 | 108 | rhsType = divEqualResult.ty; | |
| 3375 | 515 | } else if node.op == AssignOp::OP_REM_EQUAL { | |
| 3376 | 62 | rhsType = this.opRuleManager.getRemEqualResultType(baseNode, lhs, rhs); | |
| 3377 | 407 | } else if node.op == AssignOp::OP_SHL_EQUAL { | |
| 3378 | 13 | rhsType = this.opRuleManager.getSHLEqualResultType(baseNode, lhs, rhs); | |
| 3379 | 345 | } else if node.op == AssignOp::OP_SHR_EQUAL { | |
| 3380 | 20 | rhsType = this.opRuleManager.getSHREqualResultType(baseNode, lhs, rhs); | |
| 3381 | 332 | } else if node.op == AssignOp::OP_AND_EQUAL { | |
| 3382 | 30 | rhsType = this.opRuleManager.getAndEqualResultType(baseNode, lhs, rhs); | |
| 3383 | 312 | } else if node.op == AssignOp::OP_OR_EQUAL { | |
| 3384 | 32 | rhsType = this.opRuleManager.getOrEqualResultType(baseNode, lhs, rhs); | |
| 3385 | 282 | } else if node.op == AssignOp::OP_XOR_EQUAL { | |
| 3386 | 250 | rhsType = this.opRuleManager.getXorEqualResultType(baseNode, lhs, rhs); | |
| 3387 | 2767 | } | |
| 3388 | |||
| 3389 | 30508 | if lhsVar != nil<SymbolTableEntry*> { // Variable is involved on the left side | |
| 3390 | // Perform type inference | ||
| 3391 | 30507 | if lhsType.is(SuperType::TY_DYN) { | |
| 3392 | 2 | lhsVar.updateType(rhsType, false); | |
| 3393 | 2 | } | |
| 3394 | |||
| 3395 | // In case the lhs variable is captured, notify the capture about the write access | ||
| 3396 | 30507 | Capture* lhsCapture = this.currentScope.symbolTable.lookupCapture(lhsVar.name); | |
| 3397 | 30507 | if lhsCapture != nil<Capture*> { | |
| 3398 | 21 | lhsCapture.setAccessType(CaptureAccessType::READ_WRITE); | |
| 3399 | 21 | } | |
| 3400 | |||
| 3401 | // Update the state of the variable | ||
| 3402 | 30507 | lhsVar.updateState(LifecycleState::INITIALIZED); | |
| 3403 | 30507 | } | |
| 3404 | |||
| 3405 | 30508 | return Any(ExprResult{node.node.setEvaluatedSymbolType(rhsType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 3406 | } | ||
| 3407 | |||
| 3408 | 2294 | public f<Any> TypeChecker.visitTernaryExpr(ASTTernaryExprNode* node) { | |
| 3409 | // Check if there is a ternary operator applied | ||
| 3410 | 2294 | if node.falseExpr == nil<IVisitable*> { | |
| 3411 | ✗ | return this.visit(node.condition); | |
| 3412 | } | ||
| 3413 | 2294 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3414 | |||
| 3415 | // Visit condition | ||
| 3416 | 2294 | const ExprResult condition = this.visitExpr(node.condition); | |
| 3417 | 2294 | if condition.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3418 | // Visit the branches. Only one of them is evaluated, so each one has a scope of its own for its temporaries. The dtor calls for | ||
| 3419 | // those are considered further down, once it is clear which temporaries the result takes over. | ||
| 3420 | 2294 | Pair<ExprResult, Scope*> trueBranch = Pair<ExprResult, Scope*>(condition, nil<Scope*>); | |
| 3421 | 2294 | if !node.isShortened { | |
| 3422 | 2292 | trueBranch = this.visitTernaryBranch(node.trueExpr); | |
| 3423 | 2292 | } | |
| 3424 | 2294 | const ExprResult trueExpr = trueBranch.getFirst(); | |
| 3425 | 2294 | Scope* trueScope = trueBranch.getSecond(); | |
| 3426 | 2294 | const QualType trueType = trueExpr.ty; | |
| 3427 | 2294 | SymbolTableEntry* trueEntry = trueExpr.entry; | |
| 3428 | 2294 | if trueType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3429 | 2294 | Pair<ExprResult, Scope*> falseBranch = this.visitTernaryBranch(node.falseExpr); | |
| 3430 | 2294 | const ExprResult falseExpr = falseBranch.getFirst(); | |
| 3431 | 2294 | Scope* falseScope = falseBranch.getSecond(); | |
| 3432 | 2294 | const QualType falseType = falseExpr.ty; | |
| 3433 | 2294 | SymbolTableEntry* falseEntry = falseExpr.entry; | |
| 3434 | 2294 | if falseType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3435 | |||
| 3436 | // Check if the condition evaluates to bool | ||
| 3437 | 2294 | if !condition.ty.is(SuperType::TY_BOOL) { | |
| 3438 | 2 | return this.softErrorER(getASTNode(node.condition), SemanticErrorType::OPERATOR_WRONG_DATA_TYPE, String("Condition operand in ternary must be a bool")); | |
| 3439 | 1 | } | |
| 3440 | |||
| 3441 | // Check if trueType and falseType are matching | ||
| 3442 | 2293 | const QualType trueTypeModified = trueType.removeReferenceWrapper(); | |
| 3443 | 2293 | const QualType falseTypeModified = falseType.removeReferenceWrapper(); | |
| 3444 | 2293 | if !trueTypeModified.matches(falseTypeModified, false, true, false) { | |
| 3445 | 1 | const String message = String("True and false operands in ternary must be of same data type. Got ") + trueType.getName(true) + " and " + falseType.getName(true); | |
| 3446 | 2 | return this.softErrorER(baseNode, SemanticErrorType::OPERATOR_WRONG_DATA_TYPE, message); | |
| 3447 | 1 | } | |
| 3448 | |||
| 3449 | // An anonymous entry marks a temporary (e.g. a fresh struct instantiation or a non-trivial function-call | ||
| 3450 | // result), whose storage does not outlive this expression. The only exception is an anonymous entry of reference type. It | ||
| 3451 | // belongs to a nested ternary with a reference result, which just refers to the storage of one of its operands. | ||
| 3452 | 2292 | const bool trueIsTemporary = isTemporaryTernaryOperand(trueEntry); | |
| 3453 | 2292 | const bool falseIsTemporary = isTemporaryTernaryOperand(falseEntry); | |
| 3454 | |||
| 3455 | // Whether the copy ctor is called depends on the operand types, so it is decided anew for each manifestation | ||
| 3456 | 2292 | TernaryExprData& data = node.data.get(this.manIdx); | |
| 3457 | 2292 | data.trueSideCallsCopyCtor = false; | |
| 3458 | 2292 | data.falseSideCallsCopyCtor = false; | |
| 3459 | 2292 | data.calledCopyCtor = nil<Function*>; | |
| 3460 | |||
| 3461 | // The result type must be a reference if one of true/false branch is of reference type. Otherwise, | ||
| 3462 | // the copy ctor is not called correctly. This can only be done if neither branch is a temporary though: | ||
| 3463 | // a temporary's storage ends with this expression, so the result must take a copy of it instead of | ||
| 3464 | // referencing it, or the copy ctor call for the temporary would end up being skipped further down, | ||
| 3465 | // leaking it. | ||
| 3466 | 2292 | QualType resultType = trueTypeModified; | |
| 3467 | // If both branches are lvalues, the result is one as well, so it can be bound to a non-const reference. | ||
| 3468 | 2292 | const bool bothLvalues = trueEntry != nil<SymbolTableEntry*> && falseEntry != nil<SymbolTableEntry*> && !trueIsTemporary && !falseIsTemporary; | |
| 3469 | 2292 | if !trueIsTemporary && !falseIsTemporary && (trueType.isRef() || falseType.isRef()) { | |
| 3470 | 35 | resultType = trueType.isRef() ? trueType : falseType; | |
| 3471 | 2292 | } else if bothLvalues { | |
| 3472 | 84 | resultType = trueTypeModified.toRef(baseNode); | |
| 3473 | 2257 | } | |
| 3474 | // Infer the const-ness from the more restrictive operand | ||
| 3475 | 2292 | resultType.makeConst(trueType.isConst() || falseType.isConst()); | |
| 3476 | |||
| 3477 | // If there is an anonymous symbol attached to left or right, remove it, | ||
| 3478 | // since the result takes over the ownership of any destructible object. | ||
| 3479 | // The symbol lives in the scope of its branch, or in the current scope if the branch has no scope of its own. | ||
| 3480 | 2292 | bool removedAnonymousSymbols = false; | |
| 3481 | 2292 | if trueEntry != nil<SymbolTableEntry*> { | |
| 3482 | 1104 | if trueIsTemporary { | |
| 3483 | 425 | Scope* anonymousSymbolScope = trueScope != nil<Scope*> ? trueScope : this.currentScope; | |
| 3484 | 425 | const String anonymousSymbolName = trueEntry.name; | |
| 3485 | 425 | anonymousSymbolScope.symbolTable.deleteAnonymous(anonymousSymbolName); | |
| 3486 | 425 | removedAnonymousSymbols = true; | |
| 3487 | 1104 | } else if !resultType.isRef() && !trueTypeModified.isTriviallyCopyable(baseNode) { | |
| 3488 | 9 | data.trueSideCallsCopyCtor = true; | |
| 3489 | 679 | } | |
| 3490 | 1104 | } | |
| 3491 | 2292 | if falseEntry != nil<SymbolTableEntry*> { | |
| 3492 | 1178 | if falseIsTemporary { | |
| 3493 | 417 | Scope* anonymousSymbolScope = falseScope != nil<Scope*> ? falseScope : this.currentScope; | |
| 3494 | 417 | const String anonymousSymbolName = falseEntry.name; | |
| 3495 | 417 | anonymousSymbolScope.symbolTable.deleteAnonymous(anonymousSymbolName); | |
| 3496 | 417 | removedAnonymousSymbols = true; | |
| 3497 | 1178 | } else if !resultType.isRef() && !falseTypeModified.isTriviallyCopyable(baseNode) { | |
| 3498 | 17 | data.falseSideCallsCopyCtor = true; | |
| 3499 | 761 | } | |
| 3500 | 1178 | } | |
| 3501 | |||
| 3502 | // Create a new anonymous symbol for the result if required | ||
| 3503 | 2292 | SymbolTableEntry* anonymousSymbol = nil<SymbolTableEntry*>; | |
| 3504 | 2292 | const bool calledCopyCtor = data.trueSideCallsCopyCtor || data.falseSideCallsCopyCtor; | |
| 3505 | 2292 | if removedAnonymousSymbols || calledCopyCtor || resultType.isRef() { | |
| 3506 | 553 | anonymousSymbol = this.currentScope.symbolTable.insertAnonymous(resultType, baseNode); | |
| 3507 | 553 | } | |
| 3508 | |||
| 3509 | // Look up the copy ctor if at least one side needs it | ||
| 3510 | 2292 | if data.trueSideCallsCopyCtor || data.falseSideCallsCopyCtor { | |
| 3511 | 26 | data.calledCopyCtor = this.matchCopyCtor(trueTypeModified, baseNode); | |
| 3512 | 26 | } | |
| 3513 | |||
| 3514 | // Consider the dtor calls for the temporaries of the branches. A result of reference type may refer to a temporary of its | ||
| 3515 | // branch, so those must not be destructed. Not destructing them is the safe choice here, as it only leaks the temporary. | ||
| 3516 | 2292 | const bool resultIsRef = resultType.isRef(); | |
| 3517 | 2292 | if !node.isShortened { | |
| 3518 | 2290 | this.doTernaryBranchCleanup(node.trueExpr, trueScope, resultIsRef); | |
| 3519 | 2290 | } | |
| 3520 | 2292 | this.doTernaryBranchCleanup(node.falseExpr, falseScope, resultIsRef); | |
| 3521 | |||
| 3522 | 2292 | return Any(ExprResult{node.node.setEvaluatedSymbolType(resultType, this.manIdx), anonymousSymbol}); | |
| 3523 | } | ||
| 3524 | |||
| 3525 | // Visit a ternary branch in its expression scope | ||
| 3526 | 4586 | f<Pair<ExprResult, Scope*>> TypeChecker.visitTernaryBranch(IVisitable* branch) { | |
| 3527 | 4586 | ExprScopeHandle exprScopeHandle = ExprScopeHandle(&this.compilerPass, branch); | |
| 3528 | 4586 | const ExprResult branchResult = this.visitExpr(branch); | |
| 3529 | 9172 | return Pair<ExprResult, Scope*>(branchResult, exprScopeHandle.getExprScope()); | |
| 3530 | 4586 | } | |
| 3531 | |||
| 3532 | // Consider the dtor calls for the temporaries of a ternary branch | ||
| 3533 | 4582 | p TypeChecker.doTernaryBranchCleanup(IVisitable* branch, const Scope* branchScope, bool resultIsRef) { | |
| 3534 | 4582 | if branchScope == nil<const Scope*> { | |
| 3535 | 12 | return; | |
| 3536 | } | ||
| 3537 | 4570 | ExprScopeHandle exprScopeHandle = ExprScopeHandle(&this.compilerPass, branch); | |
| 3538 | 4570 | if resultIsRef { | |
| 3539 | 238 | Scope* exprScope = exprScopeHandle.getExprScope(); | |
| 3540 | 238 | exprScope.temporaryDtorsToCall.clear(); | |
| 3541 | 238 | } else { | |
| 3542 | 4332 | this.doExprScopeCleanup(branch); | |
| 3543 | 4332 | } | |
| 3544 | 4570 | } | |
| 3545 | |||
| 3546 | // Check if a ternary operand is a temporary | ||
| 3547 | 4584 | f<bool> isTemporaryTernaryOperand(const SymbolTableEntry* entry) { | |
| 3548 | 4584 | if entry == nil<const SymbolTableEntry*> || !entry.anonymous { | |
| 3549 | 3737 | return false; | |
| 3550 | } | ||
| 3551 | 847 | const QualType entryType = entry.getQualType(); | |
| 3552 | 847 | return !entryType.isRef(); | |
| 3553 | } | ||
| 3554 | |||
| 3555 | 3455 | public f<Any> TypeChecker.visitLogicalOrExpr(ASTLogicalOrExprNode* node) { | |
| 3556 | // Check if a logical or operator is applied | ||
| 3557 | 3455 | if node.operands.getSize() == 1l { | |
| 3558 | ✗ | return this.visit(node.operands.front()); | |
| 3559 | } | ||
| 3560 | 3455 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3561 | |||
| 3562 | // Visit leftmost operand | ||
| 3563 | 3455 | ExprResult currentOperand = this.visitExpr(node.operands.get(0)); | |
| 3564 | 3455 | if currentOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3565 | |||
| 3566 | // Loop through all remaining operands | ||
| 3567 | 7876 | for unsigned long i = 1l; i < node.operands.getSize(); i++ { | |
| 3568 | // The operand is only evaluated if the ones before did not short-circuit, so it gets its temporaries destructed on its own | ||
| 3569 | 4421 | const ExprResult rhsOperand = this.visitInExprScope(node.operands.get(i)); | |
| 3570 | 4421 | if rhsOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3571 | 4421 | currentOperand = ExprResult{this.opRuleManager.getLogicalOrResultType(baseNode, currentOperand, rhsOperand), nil<SymbolTableEntry*>}; | |
| 3572 | 4421 | } | |
| 3573 | |||
| 3574 | 3455 | node.node.setEvaluatedSymbolType(currentOperand.ty, this.manIdx); | |
| 3575 | 3455 | return Any(currentOperand); | |
| 3576 | } | ||
| 3577 | |||
| 3578 | 2004 | public f<Any> TypeChecker.visitLogicalAndExpr(ASTLogicalAndExprNode* node) { | |
| 3579 | // Check if a logical and operator is applied | ||
| 3580 | 2004 | if node.operands.getSize() == 1l { | |
| 3581 | ✗ | return this.visit(node.operands.front()); | |
| 3582 | } | ||
| 3583 | 2004 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3584 | |||
| 3585 | // Visit leftmost operand | ||
| 3586 | 2004 | ExprResult currentOperand = this.visitExpr(node.operands.get(0)); | |
| 3587 | 2004 | if currentOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3588 | |||
| 3589 | // Loop through all remaining operands | ||
| 3590 | 4261 | for unsigned long i = 1l; i < node.operands.getSize(); i++ { | |
| 3591 | // The operand is only evaluated if the ones before did not short-circuit, so it gets its temporaries destructed on its own | ||
| 3592 | 2257 | const ExprResult rhsOperand = this.visitInExprScope(node.operands.get(i)); | |
| 3593 | 2257 | if rhsOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3594 | 2257 | currentOperand = ExprResult{this.opRuleManager.getLogicalAndResultType(baseNode, currentOperand, rhsOperand), nil<SymbolTableEntry*>}; | |
| 3595 | 2257 | } | |
| 3596 | |||
| 3597 | 2004 | node.node.setEvaluatedSymbolType(currentOperand.ty, this.manIdx); | |
| 3598 | 2004 | return Any(currentOperand); | |
| 3599 | } | ||
| 3600 | |||
| 3601 | 438 | public f<Any> TypeChecker.visitBitwiseOrExpr(ASTBitwiseOrExprNode* node) { | |
| 3602 | // Check if a bitwise or operator is applied | ||
| 3603 | 438 | if node.operands.getSize() == 1l { | |
| 3604 | ✗ | return this.visit(node.operands.front()); | |
| 3605 | } | ||
| 3606 | 438 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3607 | |||
| 3608 | // Visit leftmost operand | ||
| 3609 | 438 | ExprResult currentOperand = this.visitExpr(node.operands.get(0)); | |
| 3610 | 438 | if currentOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3611 | |||
| 3612 | // Loop through all remaining operands | ||
| 3613 | 884 | for unsigned long i = 1l; i < node.operands.getSize(); i++ { | |
| 3614 | 446 | const ExprResult rhsOperand = this.visitExpr(node.operands.get(i)); | |
| 3615 | 446 | if rhsOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3616 | 446 | currentOperand = this.opRuleManager.getBitwiseOrResultType(baseNode, currentOperand, rhsOperand, i - 1l); | |
| 3617 | 446 | } | |
| 3618 | |||
| 3619 | 438 | node.node.setEvaluatedSymbolType(currentOperand.ty, this.manIdx); | |
| 3620 | 438 | return Any(currentOperand); | |
| 3621 | } | ||
| 3622 | |||
| 3623 | 85 | public f<Any> TypeChecker.visitBitwiseXorExpr(ASTBitwiseXorExprNode* node) { | |
| 3624 | // Check if a bitwise xor operator is applied | ||
| 3625 | 85 | if node.operands.getSize() == 1l { | |
| 3626 | ✗ | return this.visit(node.operands.front()); | |
| 3627 | } | ||
| 3628 | 85 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3629 | |||
| 3630 | // Visit leftmost operand | ||
| 3631 | 85 | ExprResult currentOperand = this.visitExpr(node.operands.get(0)); | |
| 3632 | 85 | if currentOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3633 | |||
| 3634 | // Loop through all remaining operands | ||
| 3635 | 176 | for unsigned long i = 1l; i < node.operands.getSize(); i++ { | |
| 3636 | 91 | const ExprResult rhsOperand = this.visitExpr(node.operands.get(i)); | |
| 3637 | 91 | if rhsOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3638 | 91 | currentOperand = this.opRuleManager.getBitwiseXorResultType(baseNode, currentOperand, rhsOperand, i - 1l); | |
| 3639 | 91 | } | |
| 3640 | |||
| 3641 | 85 | node.node.setEvaluatedSymbolType(currentOperand.ty, this.manIdx); | |
| 3642 | 85 | return Any(currentOperand); | |
| 3643 | } | ||
| 3644 | |||
| 3645 | 308 | public f<Any> TypeChecker.visitBitwiseAndExpr(ASTBitwiseAndExprNode* node) { | |
| 3646 | // Check if a bitwise and operator is applied | ||
| 3647 | 308 | if node.operands.getSize() == 1l { | |
| 3648 | ✗ | return this.visit(node.operands.front()); | |
| 3649 | } | ||
| 3650 | 308 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3651 | |||
| 3652 | // Visit leftmost operand | ||
| 3653 | 308 | ExprResult currentOperand = this.visitExpr(node.operands.get(0)); | |
| 3654 | 308 | if currentOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3655 | |||
| 3656 | // Loop through all remaining operands | ||
| 3657 | 622 | for unsigned long i = 1l; i < node.operands.getSize(); i++ { | |
| 3658 | 314 | const ExprResult rhsOperand = this.visitExpr(node.operands.get(i)); | |
| 3659 | 314 | if rhsOperand.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3660 | 314 | currentOperand = this.opRuleManager.getBitwiseAndResultType(baseNode, currentOperand, rhsOperand, i - 1l); | |
| 3661 | 314 | } | |
| 3662 | |||
| 3663 | 308 | node.node.setEvaluatedSymbolType(currentOperand.ty, this.manIdx); | |
| 3664 | 308 | return Any(currentOperand); | |
| 3665 | } | ||
| 3666 | |||
| 3667 | 28633 | public f<Any> TypeChecker.visitEqualityExpr(ASTEqualityExprNode* node) { | |
| 3668 | // Check if at least one equality operator is applied | ||
| 3669 | 28633 | if node.operands.getSize() == 1l { | |
| 3670 | ✗ | return this.visit(node.operands.front()); | |
| 3671 | } | ||
| 3672 | 28633 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3673 | |||
| 3674 | // Visit right side first, then left side | ||
| 3675 | 28633 | const ExprResult rhs = this.visitExpr(node.operands.get(1)); | |
| 3676 | 28633 | if rhs.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3677 | 28633 | const ExprResult lhs = this.visitExpr(node.operands.get(0)); | |
| 3678 | 28633 | if lhs.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3679 | |||
| 3680 | // Check if we need the string runtime to perform a string comparison | ||
| 3681 | 28633 | if lhs.ty.is(SuperType::TY_STRING) && rhs.ty.is(SuperType::TY_STRING) && !this.sourceFile.isStringRT() { | |
| 3682 | 545 | this.sourceFile.requestRuntimeModule(RuntimeModule::STRING_RT); | |
| 3683 | 545 | } | |
| 3684 | |||
| 3685 | // Check operator | ||
| 3686 | 28633 | ExprResult equalityResult = ExprResult{QualType(SuperType::TY_INVALID), nil<SymbolTableEntry*>}; | |
| 3687 | 28633 | if node.op == EqualityOp::OP_EQUAL { // Operator was equal | |
| 3688 | 23659 | equalityResult = this.opRuleManager.getEqualResultType(baseNode, lhs, rhs); | |
| 3689 | 28633 | } else if node.op == EqualityOp::OP_NOT_EQUAL { // Operator was not equal | |
| 3690 | 4974 | equalityResult = this.opRuleManager.getNotEqualResultType(baseNode, lhs, rhs); | |
| 3691 | 4974 | } else { | |
| 3692 | ✗ | panic(Error("EqualityExpr fall-through")); | |
| 3693 | 4974 | } | |
| 3694 | |||
| 3695 | 28633 | node.node.setEvaluatedSymbolType(equalityResult.ty, this.manIdx); | |
| 3696 | 28633 | return Any(equalityResult); | |
| 3697 | } | ||
| 3698 | |||
| 3699 | 17814 | public f<Any> TypeChecker.visitRelationalExpr(ASTRelationalExprNode* node) { | |
| 3700 | // Check if a relational operator is applied | ||
| 3701 | 17814 | if node.operands.getSize() == 1l { | |
| 3702 | ✗ | return this.visit(node.operands.front()); | |
| 3703 | } | ||
| 3704 | 17814 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3705 | |||
| 3706 | // Visit right side first, then left side | ||
| 3707 | 17814 | const ExprResult rhs = this.visitExpr(node.operands.get(1)); | |
| 3708 | 17814 | if rhs.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3709 | 17813 | const ExprResult lhs = this.visitExpr(node.operands.get(0)); | |
| 3710 | 17813 | if lhs.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3711 | |||
| 3712 | // Check operator | ||
| 3713 | 17813 | ExprResult relationalResult = ExprResult{QualType(SuperType::TY_INVALID), nil<SymbolTableEntry*>}; | |
| 3714 | 17813 | if node.op == RelationalOp::OP_LESS { // Operator was less | |
| 3715 | 7303 | relationalResult = this.opRuleManager.getLessResultType(baseNode, lhs, rhs); | |
| 3716 | 17813 | } else if node.op == RelationalOp::OP_GREATER { // Operator was greater | |
| 3717 | 3862 | relationalResult = this.opRuleManager.getGreaterResultType(baseNode, lhs, rhs); | |
| 3718 | 10510 | } else if node.op == RelationalOp::OP_LESS_EQUAL { // Operator was less equal | |
| 3719 | 2302 | relationalResult = this.opRuleManager.getLessEqualResultType(baseNode, lhs, rhs); | |
| 3720 | 6648 | } else if node.op == RelationalOp::OP_GREATER_EQUAL { // Operator was greater equal | |
| 3721 | 4346 | relationalResult = this.opRuleManager.getGreaterEqualResultType(baseNode, lhs, rhs); | |
| 3722 | 4346 | } else { | |
| 3723 | ✗ | panic(Error("RelationalExpr fall-through")); | |
| 3724 | 10510 | } | |
| 3725 | |||
| 3726 | 17813 | node.node.setEvaluatedSymbolType(relationalResult.ty, this.manIdx); | |
| 3727 | 17813 | return Any(relationalResult); | |
| 3728 | } | ||
| 3729 | |||
| 3730 | 757 | public f<Any> TypeChecker.visitShiftExpr(ASTShiftExprNode* node) { | |
| 3731 | // Check if at least one shift operator is applied | ||
| 3732 | 757 | if node.operands.getSize() == 1l { | |
| 3733 | ✗ | return this.visit(node.operands.front()); | |
| 3734 | } | ||
| 3735 | 757 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3736 | |||
| 3737 | // Visit leftmost operand | ||
| 3738 | 757 | ExprResult currentResult = this.visitExpr(node.operands.get(0)); | |
| 3739 | 757 | if currentResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3740 | |||
| 3741 | // Loop through remaining operands | ||
| 3742 | 1747 | for unsigned long i = 0l; i < node.opQueue.getSize(); i++ { | |
| 3743 | 990 | const ExprResult operandResult = this.visitExpr(node.operands.get(i + 1l)); | |
| 3744 | 990 | if operandResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3745 | |||
| 3746 | // Check operator | ||
| 3747 | 990 | Pair<ShiftOp, QualType>& queueFront = node.opQueue.front(); | |
| 3748 | 990 | const ShiftOp op = queueFront.getFirst(); | |
| 3749 | 990 | if op == ShiftOp::OP_SHIFT_LEFT { | |
| 3750 | 666 | currentResult = this.opRuleManager.getShiftLeftResultType(baseNode, currentResult, operandResult, i); | |
| 3751 | 990 | } else if op == ShiftOp::OP_SHIFT_RIGHT { | |
| 3752 | 324 | currentResult = this.opRuleManager.getShiftRightResultType(baseNode, currentResult, operandResult, i); | |
| 3753 | 324 | } else { | |
| 3754 | ✗ | panic(Error("ShiftExpr fall-through")); | |
| 3755 | 324 | } | |
| 3756 | |||
| 3757 | // Push the new item and pop the old one on the other side of the queue | ||
| 3758 | 990 | node.opQueue.push(Pair<ShiftOp, QualType>(op, currentResult.ty)); | |
| 3759 | 990 | node.opQueue.pop(); | |
| 3760 | 990 | } | |
| 3761 | |||
| 3762 | 757 | node.node.setEvaluatedSymbolType(currentResult.ty, this.manIdx); | |
| 3763 | 757 | return Any(currentResult); | |
| 3764 | } | ||
| 3765 | |||
| 3766 | 16204 | public f<Any> TypeChecker.visitAdditiveExpr(ASTAdditiveExprNode* node) { | |
| 3767 | // Check if at least one additive operator is applied | ||
| 3768 | 16204 | if node.operands.getSize() == 1l { | |
| 3769 | ✗ | return this.visit(node.operands.front()); | |
| 3770 | } | ||
| 3771 | 16204 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3772 | |||
| 3773 | // Visit leftmost operand | ||
| 3774 | 16204 | ExprResult currentResult = this.visitExpr(node.operands.get(0)); | |
| 3775 | 16204 | if currentResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3776 | |||
| 3777 | // Loop through remaining operands | ||
| 3778 | 34196 | for unsigned long i = 0l; i < node.opQueue.getSize(); i++ { | |
| 3779 | 17993 | const ExprResult operandResult = this.visitExpr(node.operands.get(i + 1l)); | |
| 3780 | 17993 | if operandResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3781 | |||
| 3782 | // Check operator | ||
| 3783 | 17993 | Pair<AdditiveOp, QualType>& queueFront = node.opQueue.front(); | |
| 3784 | 17993 | const AdditiveOp op = queueFront.getFirst(); | |
| 3785 | 17993 | if op == AdditiveOp::OP_PLUS { | |
| 3786 | 10918 | currentResult = this.opRuleManager.getPlusResultType(baseNode, currentResult, operandResult, i); | |
| 3787 | 17993 | } else if op == AdditiveOp::OP_MINUS { | |
| 3788 | 7075 | currentResult = this.opRuleManager.getMinusResultType(baseNode, currentResult, operandResult, i); | |
| 3789 | 7075 | } else { | |
| 3790 | ✗ | panic(Error("AdditiveExpr fall-through")); | |
| 3791 | 7075 | } | |
| 3792 | |||
| 3793 | // Push the new item and pop the old one on the other side of the queue | ||
| 3794 | 17993 | node.opQueue.push(Pair<AdditiveOp, QualType>(op, currentResult.ty)); | |
| 3795 | 17993 | node.opQueue.pop(); | |
| 3796 | 17993 | } | |
| 3797 | |||
| 3798 | 16203 | node.node.setEvaluatedSymbolType(currentResult.ty, this.manIdx); | |
| 3799 | 16203 | return Any(currentResult); | |
| 3800 | } | ||
| 3801 | |||
| 3802 | 2943 | public f<Any> TypeChecker.visitMultiplicativeExpr(ASTMultiplicativeExprNode* node) { | |
| 3803 | // Check if at least one multiplicative operator is applied | ||
| 3804 | 2943 | if node.operands.getSize() == 1l { | |
| 3805 | ✗ | return this.visit(node.operands.front()); | |
| 3806 | } | ||
| 3807 | 2943 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3808 | |||
| 3809 | // Visit leftmost operand | ||
| 3810 | 2943 | ExprResult currentResult = this.visitExpr(node.operands.get(0)); | |
| 3811 | 2943 | if currentResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3812 | // Loop through remaining operands | ||
| 3813 | 5984 | for unsigned long i = 0l; i < node.opQueue.getSize(); i++ { | |
| 3814 | 3041 | const ExprResult operandResult = this.visitExpr(node.operands.get(i + 1l)); | |
| 3815 | 3041 | if operandResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3816 | |||
| 3817 | // Check operator | ||
| 3818 | 3041 | Pair<MultiplicativeOp, QualType>& queueFront = node.opQueue.front(); | |
| 3819 | 3041 | const MultiplicativeOp op = queueFront.getFirst(); | |
| 3820 | 3041 | if op == MultiplicativeOp::OP_MUL { | |
| 3821 | 2482 | currentResult = this.opRuleManager.getMulResultType(baseNode, currentResult, operandResult, i); | |
| 3822 | 3041 | } else if op == MultiplicativeOp::OP_DIV { | |
| 3823 | 349 | currentResult = this.opRuleManager.getDivResultType(baseNode, currentResult, operandResult, i); | |
| 3824 | 559 | } else if op == MultiplicativeOp::OP_REM { | |
| 3825 | 210 | currentResult = this.opRuleManager.getRemResultType(baseNode, currentResult, operandResult); | |
| 3826 | 210 | } else { | |
| 3827 | ✗ | panic(Error("Multiplicative fall-through")); | |
| 3828 | 559 | } | |
| 3829 | |||
| 3830 | // Push the new item and pop the old one on the other side of the queue | ||
| 3831 | 3041 | node.opQueue.push(Pair<MultiplicativeOp, QualType>(op, currentResult.ty)); | |
| 3832 | 3041 | node.opQueue.pop(); | |
| 3833 | 3041 | } | |
| 3834 | |||
| 3835 | 2943 | node.node.setEvaluatedSymbolType(currentResult.ty, this.manIdx); | |
| 3836 | 2943 | return Any(currentResult); | |
| 3837 | } | ||
| 3838 | |||
| 3839 | 12175 | public f<Any> TypeChecker.visitCastExpr(ASTCastExprNode* node) { | |
| 3840 | // Check if cast is applied | ||
| 3841 | 12175 | if !node.isCast { | |
| 3842 | ✗ | return this.visit(node.prefixUnaryExpr); | |
| 3843 | } | ||
| 3844 | 12175 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3845 | |||
| 3846 | // Visit destination type | ||
| 3847 | 12175 | const QualType dstType = this.getDataType(node.dataType); | |
| 3848 | 12175 | if dstType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3849 | // Visit source type | ||
| 3850 | 12175 | const ExprResult src = this.visitExpr(node.assignExpr); | |
| 3851 | 12175 | if src.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3852 | |||
| 3853 | // Check for identity cast (casts that only change the signedness are not considered identity casts). Within a generic | ||
| 3854 | // manifestation, the cast may only be an identity cast for this particular substitution of the template types, so skip it | ||
| 3855 | 12175 | if src.ty == dstType && src.ty.qualifiers == dstType.qualifiers && this.typeMapping.isEmpty() { | |
| 3856 | 19 | this.addWarning(baseNode.codeLoc, CompilerWarningType::IDENTITY_CAST, String("You cast from a type to itself. Thus, this can be simplified.")); | |
| 3857 | 19 | } | |
| 3858 | |||
| 3859 | // Get result type | ||
| 3860 | 12175 | const QualType resultType = this.opRuleManager.getCastResultType(baseNode, dstType, src); | |
| 3861 | |||
| 3862 | 12175 | const bool typesMatch = dstType.matches(src.ty, false, true, true); | |
| 3863 | 12175 | const bool sameContainerType = src.ty.isSameContainerTypeAs(dstType); | |
| 3864 | 12175 | SymbolTableEntry* entry = typesMatch || sameContainerType ? src.entry : nil<SymbolTableEntry*>; | |
| 3865 | 12175 | return Any(ExprResult{node.node.setEvaluatedSymbolType(resultType, this.manIdx), entry}); | |
| 3866 | } | ||
| 3867 | |||
| 3868 | 7700 | public f<Any> TypeChecker.visitPrefixUnaryExpr(ASTPrefixUnaryExprNode* node) { | |
| 3869 | // If no operator is applied, simply visit the postfix unary expression | ||
| 3870 | 7700 | if node.op == PrefixUnaryOp::OP_NONE { | |
| 3871 | ✗ | return this.visit(node.postfixUnaryExpr); | |
| 3872 | } | ||
| 3873 | 7700 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3874 | |||
| 3875 | // Visit the right side | ||
| 3876 | 7700 | const ExprResult operand = this.visitExpr(node.prefixUnaryExpr); | |
| 3877 | 7700 | QualType operandType = operand.ty; | |
| 3878 | 7700 | SymbolTableEntry* operandEntry = operand.entry; | |
| 3879 | 7700 | if operandType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3880 | // Determine action, based on the given operator | ||
| 3881 | 7699 | switch node.op { | |
| 3882 | case PrefixUnaryOp::OP_MINUS: { | ||
| 3883 | 948 | operandType = this.opRuleManager.getPrefixMinusResultType(baseNode, operand); | |
| 3884 | 948 | } | |
| 3885 | case PrefixUnaryOp::OP_PLUS_PLUS: { | ||
| 3886 | 25 | operandType = this.opRuleManager.getPrefixPlusPlusResultType(baseNode, operand); | |
| 3887 | |||
| 3888 | 25 | if operandEntry != nil<SymbolTableEntry*> { | |
| 3889 | // In case the lhs is captured, notify the capture about the write access | ||
| 3890 | 18 | Capture* lhsCapture = this.currentScope.symbolTable.lookupCapture(operandEntry.name); | |
| 3891 | 18 | if lhsCapture != nil<Capture*> { | |
| 3892 | ✗ | lhsCapture.setAccessType(CaptureAccessType::READ_WRITE); | |
| 3893 | ✗ | } | |
| 3894 | |||
| 3895 | // Update the state of the variable | ||
| 3896 | 18 | operandEntry.updateState(LifecycleState::INITIALIZED); | |
| 3897 | 18 | } | |
| 3898 | 25 | } | |
| 3899 | case PrefixUnaryOp::OP_MINUS_MINUS: { | ||
| 3900 | 45 | operandType = this.opRuleManager.getPrefixMinusMinusResultType(baseNode, operand); | |
| 3901 | |||
| 3902 | 45 | if operandEntry != nil<SymbolTableEntry*> { | |
| 3903 | // In case the lhs is captured, notify the capture about the write access | ||
| 3904 | 37 | Capture* lhsCapture = this.currentScope.symbolTable.lookupCapture(operandEntry.name); | |
| 3905 | 37 | if lhsCapture != nil<Capture*> { | |
| 3906 | ✗ | lhsCapture.setAccessType(CaptureAccessType::READ_WRITE); | |
| 3907 | ✗ | } | |
| 3908 | |||
| 3909 | // Update the state of the variable | ||
| 3910 | 37 | operandEntry.updateState(LifecycleState::INITIALIZED); | |
| 3911 | 37 | } | |
| 3912 | 45 | } | |
| 3913 | case PrefixUnaryOp::OP_NOT: { | ||
| 3914 | 4072 | operandType = this.opRuleManager.getPrefixNotResultType(baseNode, operand); | |
| 3915 | 4072 | } | |
| 3916 | case PrefixUnaryOp::OP_BITWISE_NOT: { | ||
| 3917 | 33 | const ExprResult bitwiseNotResult = this.opRuleManager.getPrefixBitwiseNotResultType(baseNode, operand); | |
| 3918 | 33 | operandType = bitwiseNotResult.ty; | |
| 3919 | 33 | operandEntry = bitwiseNotResult.entry; | |
| 3920 | 33 | } | |
| 3921 | case PrefixUnaryOp::OP_DEREFERENCE: { | ||
| 3922 | 999 | operandType = this.opRuleManager.getPrefixMulResultType(baseNode, operand); | |
| 3923 | 999 | } | |
| 3924 | case PrefixUnaryOp::OP_ADDRESS_OF: { | ||
| 3925 | 1577 | operandType = this.opRuleManager.getPrefixBitwiseAndResultType(baseNode, operand); | |
| 3926 | 1577 | } | |
| 3927 | default: { | ||
| 3928 | ✗ | panic(Error("PrefixUnaryExpr fall-through")); | |
| 3929 | } | ||
| 3930 | } | ||
| 3931 | |||
| 3932 | 7699 | return Any(ExprResult{node.node.setEvaluatedSymbolType(operandType, this.manIdx), operandEntry}); | |
| 3933 | } | ||
| 3934 | |||
| 3935 | 106782 | public f<Any> TypeChecker.visitPostfixUnaryExpr(ASTPostfixUnaryExprNode* node) { | |
| 3936 | // If no operator is applied, simply visit the atomic expression | ||
| 3937 | 106782 | if node.op == PostfixUnaryOp::OP_NONE { | |
| 3938 | ✗ | return this.visit(node.atomicExpr); | |
| 3939 | } | ||
| 3940 | 106782 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 3941 | |||
| 3942 | // Visit left side | ||
| 3943 | 106782 | const ExprResult operand = this.visitExpr(node.postfixUnaryExpr); | |
| 3944 | 106782 | QualType operandType = operand.ty; | |
| 3945 | 106782 | SymbolTableEntry* operandEntry = operand.entry; | |
| 3946 | 106782 | if operandType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3947 | |||
| 3948 | 106771 | switch node.op { | |
| 3949 | case PostfixUnaryOp::OP_SUBSCRIPT: { | ||
| 3950 | // Visit index assignment | ||
| 3951 | 13177 | IVisitable* indexAssignExpr = node.subscriptIndexExpr; | |
| 3952 | 13177 | const ExprResult index = this.visitExpr(indexAssignExpr); | |
| 3953 | 13177 | if index.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 3954 | |||
| 3955 | // Check is there is an overloaded operator function available, if yes accept it | ||
| 3956 | 13177 | Vector<ExprResult> operatorOperands; | |
| 3957 | 13177 | operatorOperands.pushBack(operand); | |
| 3958 | 13177 | operatorOperands.pushBack(index); | |
| 3959 | 13177 | const ExprResult overloadResult = this.opRuleManager.isOperatorOverloadingFctAvailable(baseNode, OP_FCT_SUBSCRIPT, operatorOperands, 0l); | |
| 3960 | 13177 | if !overloadResult.ty.is(SuperType::TY_INVALID) { | |
| 3961 | 957 | operandType = overloadResult.ty; | |
| 3962 | 957 | } else { | |
| 3963 | 12220 | operandType = operandType.removeReferenceWrapper(); | |
| 3964 | |||
| 3965 | // Check if the index is of the right type | ||
| 3966 | 12220 | const dyn indexOptions = [SuperType::TY_INT, SuperType::TY_LONG]; | |
| 3967 | 12220 | if !index.ty.isOneOf(indexOptions, len(indexOptions)) { | |
| 3968 | 2 | return this.softErrorER(baseNode, SemanticErrorType::ARRAY_INDEX_NOT_INT_OR_LONG, String("Array index must be of type int or long")); | |
| 3969 | 1 | } | |
| 3970 | |||
| 3971 | // Check if we can apply the subscript operator on the lhs type | ||
| 3972 | 12219 | const dyn subscriptOptions = [SuperType::TY_ARRAY, SuperType::TY_PTR, SuperType::TY_STRING]; | |
| 3973 | 12219 | if !operandType.isOneOf(subscriptOptions, len(subscriptOptions)) { | |
| 3974 | ✗ | const String message = String("Can only apply subscript operator on array type, got ") + operandType.getName(true); | |
| 3975 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::OPERATOR_WRONG_DATA_TYPE, message); | |
| 3976 | ✗ | } | |
| 3977 | |||
| 3978 | // Check if we have an unsafe operation | ||
| 3979 | 12219 | if operandType.isPtr() && !this.currentScope.doesAllowUnsafeOperations() { | |
| 3980 | 2 | return this.softErrorER(baseNode, SemanticErrorType::UNSAFE_OPERATION_IN_SAFE_CONTEXT, String("The subscript operator on pointers is an unsafe operation. Use unsafe blocks if you know what you are doing.")); | |
| 3981 | 1 | } | |
| 3982 | |||
| 3983 | // In case of compile time index value and known array size, perform a compile time out-of-bounds check | ||
| 3984 | 12218 | ASTNode* indexNode = getASTNode(indexAssignExpr); | |
| 3985 | 12218 | if operandType.isArray() && cast<unsigned long>(operandType.getArraySize()) != ARRAY_SIZE_UNKNOWN && indexNode.hasCompileTimeValue(this.manIdx) { | |
| 3986 | 616 | const CompileTimeValue compileTimeValue = indexNode.getCompileTimeValue(this.manIdx); | |
| 3987 | 616 | const int constIndex = compileTimeValue.getIntValue(); | |
| 3988 | 616 | const unsigned int constSize = operandType.getArraySize(); | |
| 3989 | // Check if we are accessing out-of-bounds memory | ||
| 3990 | 616 | if constIndex >= cast<int>(constSize) { | |
| 3991 | 1 | const String idxStr = toString(constIndex); | |
| 3992 | 1 | const String sizeStr = toString(cast<long>(constSize)); | |
| 3993 | 1 | const String message = String("You are trying to access element with index ") + idxStr + " of an array with size " + sizeStr; | |
| 3994 | 2 | return this.softErrorER(baseNode, SemanticErrorType::ARRAY_INDEX_OUT_OF_BOUNDS, message); | |
| 3995 | 1 | } | |
| 3996 | 615 | } | |
| 3997 | |||
| 3998 | // Get item type | ||
| 3999 | 12217 | operandType = operandType.getContained(); | |
| 4000 | |||
| 4001 | // Remove heap qualifier | ||
| 4002 | 12217 | TypeQualifiers& qualifiers = operandType.getQualifiers(); | |
| 4003 | 12217 | qualifiers.isHeap = false; | |
| 4004 | 12217 | } | |
| 4005 | 13177 | } | |
| 4006 | case PostfixUnaryOp::OP_MEMBER_ACCESS: { | ||
| 4007 | 84193 | const String& fieldName = node.identifier; | |
| 4008 | |||
| 4009 | // Check if lhs is enum or strobj | ||
| 4010 | 84193 | const QualType lhsBaseTy = operandType.autoDereference(); | |
| 4011 | |||
| 4012 | // Union field access is handled separately: a union field is always a direct, non-composed lookup by name | ||
| 4013 | // (unlike struct fields, union fields never nest/compose), and reading/writing it requires a runtime tag | ||
| 4014 | // check/update in the IR generator, which relies on the field entry resolved here. | ||
| 4015 | 84193 | if lhsBaseTy.is(SuperType::TY_UNION) { | |
| 4016 | 66 | const String unionName = lhsBaseTy.getSubType(); | |
| 4017 | 66 | Scope* unionScope = lhsBaseTy.getBodyScope(); | |
| 4018 | |||
| 4019 | // If we only have the generic union scope, lookup the concrete manifestation scope | ||
| 4020 | 66 | if unionScope.isGenericScope { | |
| 4021 | ✗ | const Union* spiceUnion = lhsBaseTy.getUnion(baseNode); | |
| 4022 | ✗ | assert spiceUnion != nil<const Union*>; | |
| 4023 | ✗ | unionScope = spiceUnion.scope; | |
| 4024 | ✗ | } | |
| 4025 | 66 | assert !unionScope.isGenericScope; // At this point we always expect a substantiation scope | |
| 4026 | |||
| 4027 | // Get accessed field | ||
| 4028 | 66 | SymbolTableEntry* memberEntry = unionScope.symbolTable.lookupStrict(fieldName); | |
| 4029 | 66 | if memberEntry == nil<SymbolTableEntry*> { | |
| 4030 | 1 | const String message = String("Field '") + node.identifier + "' not found in union " + unionName; | |
| 4031 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 4032 | 1 | } | |
| 4033 | 65 | const QualType memberType = memberEntry.getQualType(); | |
| 4034 | |||
| 4035 | // Check for insufficient visibility | ||
| 4036 | 65 | const QualType memberBaseType = memberType.getBase(); | |
| 4037 | 65 | if unionScope.isImportedBy(this.rootScope) && !memberBaseType.isPublic() { | |
| 4038 | ✗ | const String message = String("Cannot access field '") + fieldName + "' due to its private visibility"; | |
| 4039 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, message); | |
| 4040 | ✗ | } | |
| 4041 | |||
| 4042 | // Set field to used | ||
| 4043 | 65 | memberEntry.used = true; | |
| 4044 | |||
| 4045 | // A read of an inactive field panics, which prints the stack trace, so the runtime that does that has to be loaded | ||
| 4046 | 65 | if this.cliOptions.printsStackTraceOnAbort() && !this.sourceFile.isRT(RuntimeModule::STACK_TRACE_RT) { | |
| 4047 | ✗ | this.sourceFile.requestRuntimeModule(RuntimeModule::STACK_TRACE_RT); | |
| 4048 | ✗ | } | |
| 4049 | |||
| 4050 | // Overwrite type and entry of left side with member type and entry | ||
| 4051 | 65 | operandType = memberType; | |
| 4052 | 65 | operandEntry = memberEntry; | |
| 4053 | 66 | } else { | |
| 4054 | 84127 | if !lhsBaseTy.is(SuperType::TY_STRUCT) { | |
| 4055 | 2 | const String message = String("Cannot apply member access operator on ") + operandType.getName(false); | |
| 4056 | 4 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_MEMBER_ACCESS, message); | |
| 4057 | 2 | } | |
| 4058 | |||
| 4059 | // Retrieve registry entry | ||
| 4060 | 84125 | const String structName = lhsBaseTy.getSubType(); | |
| 4061 | 84125 | Struct* spiceStruct = lhsBaseTy.getStruct(baseNode); | |
| 4062 | 84125 | assert spiceStruct != nil<Struct*>; | |
| 4063 | 84125 | Scope* structScope = spiceStruct.scope; | |
| 4064 | 84125 | assert !structScope.isGenericScope; // At this point we always expect a substantiation scope | |
| 4065 | |||
| 4066 | // Get accessed field | ||
| 4067 | 84125 | Vector<unsigned long> indexPath; | |
| 4068 | 84125 | SymbolTableEntry* memberEntry = spiceStruct.lookupInComposedFields(fieldName, indexPath); | |
| 4069 | 84125 | if memberEntry == nil<SymbolTableEntry*> { | |
| 4070 | 2 | const String message = String("Field '") + node.identifier + "' not found in struct " + structName; | |
| 4071 | 4 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 4072 | 2 | } | |
| 4073 | 84123 | const QualType memberType = memberEntry.getQualType(); | |
| 4074 | |||
| 4075 | // Check for insufficient visibility | ||
| 4076 | 84123 | const QualType memberBaseType = memberType.getBase(); | |
| 4077 | 84123 | if structScope.isImportedBy(this.rootScope) && !memberBaseType.isPublic() { | |
| 4078 | ✗ | const String message = String("Cannot access field '") + fieldName + "' due to its private visibility"; | |
| 4079 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, message); | |
| 4080 | ✗ | } | |
| 4081 | |||
| 4082 | // Set field to used | ||
| 4083 | 84123 | memberEntry.used = true; | |
| 4084 | |||
| 4085 | // Overwrite type and entry of left side with member type and entry | ||
| 4086 | 84123 | operandType = memberType; | |
| 4087 | 84123 | operandEntry = memberEntry; | |
| 4088 | |||
| 4089 | // A field of a const struct cannot be modified either. This does not apply to pointer and reference fields, | ||
| 4090 | // because their const would belong to the pointee or referent instead of the field itself | ||
| 4091 | 84123 | if lhsBaseTy.isConst() && !memberType.isPtr() && !memberType.isRef() { | |
| 4092 | 7782 | operandType.makeConst(); | |
| 4093 | 7782 | } | |
| 4094 | 84125 | } | |
| 4095 | 84188 | } | |
| 4096 | case PostfixUnaryOp::OP_PLUS_PLUS: { | ||
| 4097 | 8526 | const ExprResult plusPlusResult = this.opRuleManager.getPostfixPlusPlusResultType(baseNode, operand); | |
| 4098 | 8526 | operandType = plusPlusResult.ty; | |
| 4099 | |||
| 4100 | 8526 | if operandEntry != nil<SymbolTableEntry*> { | |
| 4101 | // In case the lhs is captured, notify the capture about the write access | ||
| 4102 | 8516 | Capture* lhsCapture = this.currentScope.symbolTable.lookupCapture(operandEntry.name); | |
| 4103 | 8516 | if lhsCapture != nil<Capture*> { | |
| 4104 | 10 | lhsCapture.setAccessType(CaptureAccessType::READ_WRITE); | |
| 4105 | 10 | } | |
| 4106 | |||
| 4107 | // Update the state of the variable | ||
| 4108 | 8516 | operandEntry.updateState(LifecycleState::INITIALIZED); | |
| 4109 | 8516 | } | |
| 4110 | 8526 | } | |
| 4111 | case PostfixUnaryOp::OP_MINUS_MINUS: { | ||
| 4112 | 779 | const ExprResult minusMinusResult = this.opRuleManager.getPostfixMinusMinusResultType(baseNode, operand); | |
| 4113 | 779 | operandType = minusMinusResult.ty; | |
| 4114 | |||
| 4115 | 779 | if operandEntry != nil<SymbolTableEntry*> { | |
| 4116 | // In case the lhs is captured, notify the capture about the write access | ||
| 4117 | 770 | Capture* lhsCapture = this.currentScope.symbolTable.lookupCapture(operandEntry.name); | |
| 4118 | 770 | if lhsCapture != nil<Capture*> { | |
| 4119 | ✗ | lhsCapture.setAccessType(CaptureAccessType::READ_WRITE); | |
| 4120 | ✗ | } | |
| 4121 | |||
| 4122 | // Update the state of the variable | ||
| 4123 | 770 | operandEntry.updateState(LifecycleState::INITIALIZED); | |
| 4124 | 770 | } | |
| 4125 | 779 | } | |
| 4126 | case PostfixUnaryOp::OP_ERR_PROPAGATION: { | ||
| 4127 | // Check if the operand is of type Result<T> | ||
| 4128 | 96 | const QualType resultType = operandType.removeReferenceWrapper(); | |
| 4129 | 96 | if !resultType.isResultObj() { | |
| 4130 | 1 | const String message = String("The error propagation operator '!' can only be applied to a value of type Result<T>, got ") + operandType.getName(false); | |
| 4131 | 2 | return this.softErrorER(baseNode, SemanticErrorType::ERR_PROPAGATION_INVALID_OPERAND, message); | |
| 4132 | 1 | } | |
| 4133 | |||
| 4134 | // Check if the enclosing function/procedure/lambda itself returns Result<U> | ||
| 4135 | 95 | const SymbolTableEntry* enclosingReturnVar = this.currentScope.lookupResultVar(); | |
| 4136 | 95 | QualType enclosingReturnType = QualType(SuperType::TY_DYN); | |
| 4137 | 95 | if enclosingReturnVar != nil<const SymbolTableEntry*> { | |
| 4138 | 94 | enclosingReturnType = enclosingReturnVar.getQualType(); | |
| 4139 | 94 | } | |
| 4140 | 95 | if enclosingReturnVar == nil<const SymbolTableEntry*> || !enclosingReturnType.isResultObj() { | |
| 4141 | 4 | return this.softErrorER(baseNode, SemanticErrorType::ERR_PROPAGATION_INVALID_CONTEXT, String("The error propagation operator '!' can only be used inside a function that itself returns Result<U>")); | |
| 4142 | 2 | } | |
| 4143 | |||
| 4144 | // Retrieve the substantiation scope of the operand's Result<T> | ||
| 4145 | 93 | Scope* resultScope = resultType.getBodyScope(); | |
| 4146 | 93 | if resultScope.isGenericScope { | |
| 4147 | ✗ | const Struct* resultStruct = resultType.getStruct(baseNode); | |
| 4148 | ✗ | assert resultStruct != nil<const Struct*>; | |
| 4149 | ✗ | resultScope = resultStruct.scope; | |
| 4150 | ✗ | } | |
| 4151 | 93 | assert !resultScope.isGenericScope; | |
| 4152 | |||
| 4153 | // Resolve Result<T>.isErr(), Result<T>.unwrap() and Result<T>.getErr() | ||
| 4154 | 93 | const ArgList noArgs; | |
| 4155 | 93 | const QualTypeList noTemplateTypeHints; | |
| 4156 | 93 | ErrPropagationData& errPropData = node.errPropData.get(this.manIdx); | |
| 4157 | 93 | errPropData.isErrFct = matchFunction(resultScope, String("isErr"), resultType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 4158 | 93 | errPropData.unwrapFct = matchFunction(resultScope, String("unwrap"), resultType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 4159 | 93 | errPropData.getErrFct = matchFunction(resultScope, String("getErr"), resultType, noArgs, noTemplateTypeHints, false, baseNode); | |
| 4160 | 93 | if errPropData.isErrFct == nil<const Function*> || errPropData.unwrapFct == nil<const Function*> || errPropData.getErrFct == nil<const Function*> { | |
| 4161 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::ERR_PROPAGATION_INVALID_CONTEXT, String("Failed to resolve the Result<T> helper methods required for the error propagation operator")); | |
| 4162 | ✗ | } | |
| 4163 | |||
| 4164 | // Resolve err<U>(const Error&), located next to Result in the same module | ||
| 4165 | 93 | const NameRegistryEntry* resultRegistryEntry = this.sourceFile.getNameRegistryEntry(String(RESULTOBJ_NAME)); | |
| 4166 | 93 | assert resultRegistryEntry != nil<const NameRegistryEntry*>; | |
| 4167 | 93 | Scope* resultStructScope = resultRegistryEntry.targetScope; | |
| 4168 | 93 | Scope* resultModuleScope = resultStructScope.parent; | |
| 4169 | 93 | const QualTypeList enclosingTemplateTypes = enclosingReturnType.getTemplateTypes(); | |
| 4170 | 93 | const Function* getErrFct = errPropData.getErrFct; | |
| 4171 | 93 | ArgList errArgs; | |
| 4172 | 93 | errArgs.pushBack(Pair<QualType, bool>(getErrFct.returnType, false)); | |
| 4173 | 93 | errPropData.ctorFct = matchFunction(resultModuleScope, String("err"), QualType(SuperType::TY_DYN), errArgs, enclosingTemplateTypes, false, baseNode); | |
| 4174 | 93 | if errPropData.ctorFct == nil<const Function*> { | |
| 4175 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::ERR_PROPAGATION_INVALID_CONTEXT, String("Failed to resolve 'err<U>(const Error&)' required for the error propagation operator")); | |
| 4176 | ✗ | } | |
| 4177 | |||
| 4178 | // The whole expression evaluates to the unwrapped payload | ||
| 4179 | 93 | const Function* unwrapFct = errPropData.unwrapFct; | |
| 4180 | 93 | operandType = unwrapFct.returnType; | |
| 4181 | 93 | operandEntry = nil<SymbolTableEntry*>; | |
| 4182 | 93 | } | |
| 4183 | default: { | ||
| 4184 | ✗ | panic(Error("PostfixUnaryExpr fall-through")); | |
| 4185 | } | ||
| 4186 | } | ||
| 4187 | |||
| 4188 | 106760 | if operandType.is(SuperType::TY_INVALID) { | |
| 4189 | ✗ | String varName = String(""); | |
| 4190 | ✗ | if operandEntry != nil<SymbolTableEntry*> { | |
| 4191 | ✗ | varName = operandEntry.name; | |
| 4192 | ✗ | } | |
| 4193 | ✗ | const String message = String("Variable '") + varName + "' was referenced before declared"; | |
| 4194 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 4195 | ✗ | } | |
| 4196 | |||
| 4197 | 106760 | return Any(ExprResult{node.node.setEvaluatedSymbolType(operandType, this.manIdx), operandEntry}); | |
| 4198 | } | ||
| 4199 | |||
| 4200 | 424415 | public f<Any> TypeChecker.visitAtomicExpr(ASTAtomicExprNode* node) { | |
| 4201 | // Check if constant | ||
| 4202 | 424415 | if node.constant != nil<ASTConstantNode*> { | |
| 4203 | 86013 | return this.visitConstant(node.constant); | |
| 4204 | } | ||
| 4205 | |||
| 4206 | // Check if value | ||
| 4207 | 338402 | if node.value != nil<IVisitable*> { | |
| 4208 | 92446 | return this.visit(node.value); | |
| 4209 | } | ||
| 4210 | |||
| 4211 | // Check for assign expression within parentheses | ||
| 4212 | 245956 | if node.assignExpr != nil<IVisitable*> { | |
| 4213 | 2028 | return this.visit(node.assignExpr); | |
| 4214 | } | ||
| 4215 | |||
| 4216 | // Identifier (local or global variable access) | ||
| 4217 | 243928 | assert !node.fqIdentifier.isEmpty(); | |
| 4218 | 243928 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 4219 | |||
| 4220 | 243928 | VarAccessData& accessData = node.data.get(this.manIdx); | |
| 4221 | 243928 | accessData.accessScope = this.currentScope; | |
| 4222 | |||
| 4223 | // Check if a local or global variable can be found by searching for the name | ||
| 4224 | 243928 | if node.identifierFragments.getSize() == 1l { | |
| 4225 | 241499 | accessData.entry = accessData.accessScope.lookup(node.identifierFragments.back()); | |
| 4226 | 241499 | } | |
| 4227 | |||
| 4228 | // If no local or global was found, search in the name registry | ||
| 4229 | 243928 | if accessData.entry == nil<SymbolTableEntry*> { | |
| 4230 | 2805 | const NameRegistryEntry* registryEntry = this.sourceFile.getNameRegistryEntry(node.fqIdentifier); | |
| 4231 | 2805 | if registryEntry == nil<const NameRegistryEntry*> { | |
| 4232 | 2 | if this.sourceFile.isAmbiguousName(node.fqIdentifier) { | |
| 4233 | 2 | return this.softErrorER(baseNode, SemanticErrorType::AMBIGUOUS_SYMBOL, this.sourceFile.getAmbiguousNameMessage(node.fqIdentifier)); | |
| 4234 | 1 | } | |
| 4235 | 1 | const String message = String("The variable '") + node.fqIdentifier + "' could not be found"; | |
| 4236 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 4237 | 1 | } | |
| 4238 | 2803 | accessData.entry = registryEntry.targetEntry; | |
| 4239 | 2803 | accessData.accessScope = registryEntry.targetScope; | |
| 4240 | 2803 | } | |
| 4241 | 243926 | assert accessData.entry != nil<SymbolTableEntry*>; | |
| 4242 | 243926 | SymbolTableEntry* entry = accessData.entry; | |
| 4243 | 243926 | entry.used = true; | |
| 4244 | 243926 | accessData.capture = accessData.accessScope.symbolTable.lookupCapture(entry.name); | |
| 4245 | |||
| 4246 | 243926 | const QualType varType = entry.getQualType(); | |
| 4247 | 243926 | if varType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4248 | 243910 | if varType.is(SuperType::TY_INVALID) { | |
| 4249 | 2 | const String message = String("Symbol '") + entry.name + "' was used before declared."; | |
| 4250 | 4 | return this.softErrorER(baseNode, SemanticErrorType::USED_BEFORE_DECLARED, message); | |
| 4251 | 2 | } | |
| 4252 | |||
| 4253 | 243908 | const dyn fctOptions = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 4254 | 243908 | if varType.isOneOf(fctOptions, len(fctOptions)) && entry.global { | |
| 4255 | // Check if overloaded function was referenced | ||
| 4256 | 76 | Vector<Function*>* manifestations = entry.declNode.getFctManifestations(entry.name); | |
| 4257 | 76 | if manifestations.getSize() > 1l { | |
| 4258 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_OVERLOADED_FCT, String("Overloaded functions / functions with optional params cannot be referenced")); | |
| 4259 | 1 | } | |
| 4260 | // Set referenced function to used | ||
| 4261 | 75 | Function* referencedFunction = manifestations.front(); | |
| 4262 | 75 | if !referencedFunction.templateTypes.isEmpty() { | |
| 4263 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_OVERLOADED_FCT, String("Generic functions cannot be referenced")); | |
| 4264 | 1 | } | |
| 4265 | 74 | referencedFunction.used = true; | |
| 4266 | 74 | referencedFunction.entry.used = true; | |
| 4267 | 74 | } | |
| 4268 | |||
| 4269 | // The base type should be an extended primitive | ||
| 4270 | 243906 | const QualType baseType = varType.getBase(); | |
| 4271 | 243906 | if !baseType.isExtendedPrimitive() && !baseType.is(SuperType::TY_DYN) { | |
| 4272 | 1 | const String message = String("A symbol of type ") + varType.getName(false) + " cannot be accessed here"; | |
| 4273 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_SYMBOL_ACCESS, message); | |
| 4274 | 1 | } | |
| 4275 | |||
| 4276 | // Check if we have seen a 'this.' prefix, because the generator needs that | ||
| 4277 | 243905 | const String& firstFragment = node.identifierFragments.front(); | |
| 4278 | 243905 | if entry.scope.scopeType == ScopeType::STRUCT && firstFragment != THIS_VARIABLE_NAME { | |
| 4279 | 1 | const String message = String("The symbol '") + node.fqIdentifier + "' could not be found. Missing 'this.' prefix?"; | |
| 4280 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 4281 | 1 | } | |
| 4282 | |||
| 4283 | // Ensure that the entry is public, if the symbol is imported. | ||
| 4284 | // An exception are enum items. There it is sufficient, that the enum itself is public. | ||
| 4285 | 243904 | if accessData.accessScope.isImportedBy(this.rootScope) && accessData.accessScope.scopeType != ScopeType::ENUM { | |
| 4286 | 332 | if !baseType.isPublic() { | |
| 4287 | 1 | const String message = String("Cannot access '") + entry.name + "' due to its private visibility"; | |
| 4288 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, message); | |
| 4289 | 1 | } | |
| 4290 | 331 | } | |
| 4291 | |||
| 4292 | // For enum item access, use access scope of the enum | ||
| 4293 | 243903 | if entry.scope.scopeType == ScopeType::ENUM { | |
| 4294 | 2419 | accessData.accessScope = entry.scope; | |
| 4295 | 2419 | } | |
| 4296 | |||
| 4297 | // For struct access, use access scope of the struct | ||
| 4298 | 243903 | if baseType.is(SuperType::TY_STRUCT) { | |
| 4299 | 96850 | const String structName = baseType.getSubType(); | |
| 4300 | 96850 | const NameRegistryEntry* nameRegistryEntry = this.sourceFile.getNameRegistryEntry(structName); | |
| 4301 | // The struct may only be reachable transitively (i.e. it is not present in this file's name registry under | ||
| 4302 | // its unqualified name). In that case, fall back to the struct's body scope, which is exactly what the | ||
| 4303 | // registry entry's targetScope would point to (see SymbolTableBuilder struct registration). This keeps | ||
| 4304 | // member access working across deep transitive imports instead of crashing. | ||
| 4305 | 96850 | if nameRegistryEntry != nil<const NameRegistryEntry*> { | |
| 4306 | 96845 | accessData.accessScope = nameRegistryEntry.targetScope; | |
| 4307 | 96845 | } else { | |
| 4308 | 5 | accessData.accessScope = baseType.getBodyScope(); | |
| 4309 | 5 | } | |
| 4310 | 96850 | assert accessData.accessScope != nil<Scope*>; | |
| 4311 | 96850 | } | |
| 4312 | |||
| 4313 | 243903 | return Any(ExprResult{node.node.setEvaluatedSymbolType(varType, this.manIdx), entry}); | |
| 4314 | } | ||
| 4315 | |||
| 4316 | // ============================================================ Values ============================================================ | ||
| 4317 | |||
| 4318 | 92446 | public f<Any> TypeChecker.visitValue(ASTValueNode* node) { | |
| 4319 | // Function call | ||
| 4320 | 92446 | if node.fctCall != nil<IVisitable*> { | |
| 4321 | 79415 | return this.visit(node.fctCall); | |
| 4322 | } | ||
| 4323 | |||
| 4324 | // Array initialization | ||
| 4325 | 13031 | if node.arrayInitialization != nil<IVisitable*> { | |
| 4326 | 184 | return this.visit(node.arrayInitialization); | |
| 4327 | } | ||
| 4328 | |||
| 4329 | // Struct instantiation | ||
| 4330 | 12847 | if node.structInstantiation != nil<IVisitable*> { | |
| 4331 | 1741 | return this.visit(node.structInstantiation); | |
| 4332 | } | ||
| 4333 | |||
| 4334 | // Lambda function | ||
| 4335 | 11106 | if node.lambdaFunc != nil<IVisitable*> { | |
| 4336 | 67 | return this.visit(node.lambdaFunc); | |
| 4337 | } | ||
| 4338 | |||
| 4339 | // Lambda procedure | ||
| 4340 | 11039 | if node.lambdaProc != nil<IVisitable*> { | |
| 4341 | 73 | return this.visit(node.lambdaProc); | |
| 4342 | } | ||
| 4343 | |||
| 4344 | // Lambda expression | ||
| 4345 | 10966 | if node.lambdaExpr != nil<IVisitable*> { | |
| 4346 | 4 | return this.visit(node.lambdaExpr); | |
| 4347 | } | ||
| 4348 | |||
| 4349 | // Typed nil | ||
| 4350 | 10962 | if node.isNil { | |
| 4351 | 10962 | const QualType nilType = this.getDataType(node.nilType); | |
| 4352 | 10962 | if nilType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4353 | 10962 | if nilType.is(SuperType::TY_DYN) { | |
| 4354 | 2 | return this.softErrorER(cast<ASTNode*>(node.nilType), SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Nil must have an explicit type")); | |
| 4355 | 1 | } | |
| 4356 | 10961 | return Any(ExprResult{node.node.setEvaluatedSymbolType(nilType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 4357 | } | ||
| 4358 | |||
| 4359 | − | panic(Error("Value fall-through")); // LCOV_EXCL_LINE | |
| 4360 | } | ||
| 4361 | |||
| 4362 | 96020 | public f<Any> TypeChecker.visitConstant(ASTConstantNode* node) { | |
| 4363 | 96020 | SuperType superType = SuperType::TY_INVALID; | |
| 4364 | 96020 | switch node.primitiveValueType { | |
| 4365 | case PrimitiveValueType::TYPE_DOUBLE: { | ||
| 4366 | 1391 | superType = SuperType::TY_DOUBLE; | |
| 4367 | 1391 | } | |
| 4368 | case PrimitiveValueType::TYPE_INT: { | ||
| 4369 | 15673 | superType = SuperType::TY_INT; | |
| 4370 | 15673 | } | |
| 4371 | case PrimitiveValueType::TYPE_SHORT: { | ||
| 4372 | 2312 | superType = SuperType::TY_SHORT; | |
| 4373 | 2312 | } | |
| 4374 | case PrimitiveValueType::TYPE_LONG: { | ||
| 4375 | 41286 | superType = SuperType::TY_LONG; | |
| 4376 | 41286 | } | |
| 4377 | case PrimitiveValueType::TYPE_CHAR: { | ||
| 4378 | 8948 | superType = SuperType::TY_CHAR; | |
| 4379 | 8948 | } | |
| 4380 | case PrimitiveValueType::TYPE_STRING: { | ||
| 4381 | 15665 | superType = SuperType::TY_STRING; | |
| 4382 | 15665 | } | |
| 4383 | case PrimitiveValueType::TYPE_BOOL: { | ||
| 4384 | 10745 | superType = SuperType::TY_BOOL; | |
| 4385 | 10745 | } | |
| 4386 | default: { | ||
| 4387 | ✗ | panic(Error("Constant fall-through")); | |
| 4388 | } | ||
| 4389 | } | ||
| 4390 | 96020 | QualType constantType = QualType(superType); | |
| 4391 | // Integer literals with an 'u' suffix are unsigned | ||
| 4392 | 96020 | if node.isUnsigned { | |
| 4393 | 3976 | constantType.makeUnsigned(); | |
| 4394 | 3976 | } | |
| 4395 | 96020 | return Any(ExprResult{node.node.setEvaluatedSymbolType(constantType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 4396 | } | ||
| 4397 | |||
| 4398 | 79415 | public f<Any> TypeChecker.visitFctCall(ASTFctCallNode* node) { | |
| 4399 | 79415 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 4400 | 79415 | FctCallData& data = node.data.get(this.manIdx); | |
| 4401 | |||
| 4402 | // Retrieve arg types | ||
| 4403 | 79415 | data.args.clear(); | |
| 4404 | // Scopes and names of the anonymous symbols of temporary args. The scope is nil if the arg is no such temporary | ||
| 4405 | 79415 | Vector<Scope*> argTemporaryScopes; | |
| 4406 | 79415 | Vector<String> argTemporaryNames; | |
| 4407 | 79415 | if node.hasArgs { | |
| 4408 | 59073 | ASTArgLstNode* argLst = node.argLst; | |
| 4409 | 59073 | data.args.reserve(argLst.args.getSize()); | |
| 4410 | 156787 | foreach IVisitable* arg : argLst.args { | |
| 4411 | // Visit argument | ||
| 4412 | 97739 | const ExprResult argResult = this.visitExpr(arg); | |
| 4413 | 97739 | if argResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4414 | 97714 | assert !argResult.ty.hasAnyGenericParts(); | |
| 4415 | // Save arg type to arg types list | ||
| 4416 | 97714 | data.args.pushBack(Pair<QualType, bool>(argResult.ty, argResult.isTemporary())); | |
| 4417 | 97714 | const bool isAnonymous = argResult.entry != nil<SymbolTableEntry*> && argResult.entry.anonymous; | |
| 4418 | 97714 | argTemporaryScopes.pushBack(isAnonymous ? argResult.entry.scope : nil<Scope*>); | |
| 4419 | 97714 | argTemporaryNames.pushBack(isAnonymous ? argResult.entry.name : String()); | |
| 4420 | 97714 | } | |
| 4421 | 59048 | } | |
| 4422 | |||
| 4423 | // Retrieve template types | ||
| 4424 | 79390 | data.templateTypes.clear(); | |
| 4425 | 79390 | if node.hasTemplateTypes { | |
| 4426 | 12779 | foreach ASTDataTypeNode* templateTypeNode : node.templateTypeLst.dataTypes { | |
| 4427 | 6927 | const QualType templateType = this.getDataType(templateTypeNode); | |
| 4428 | 6927 | assert !templateType.is(SuperType::TY_INVALID); | |
| 4429 | |||
| 4430 | // Abort if the type is unresolved | ||
| 4431 | 6927 | if templateType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4432 | |||
| 4433 | // Check if the given type is generic | ||
| 4434 | 6927 | if templateType.is(SuperType::TY_GENERIC) { | |
| 4435 | 2 | return this.softErrorER(cast<ASTNode*>(templateTypeNode), SemanticErrorType::EXPECTED_NON_GENERIC_TYPE, String("You must specify a concrete type here")); | |
| 4436 | 1 | } | |
| 4437 | |||
| 4438 | 6926 | data.templateTypes.pushBack(templateType); | |
| 4439 | 6926 | } | |
| 4440 | 5852 | } | |
| 4441 | |||
| 4442 | // Check if this is a builtin call | ||
| 4443 | 79389 | BuiltinFunctionInfo builtinFunctionInfo; | |
| 4444 | 79389 | if lookupBuiltinFunction(node.fqFunctionName, builtinFunctionInfo) { | |
| 4445 | 19816 | return this.visitBuiltinCall(node); | |
| 4446 | } | ||
| 4447 | |||
| 4448 | // Retrieve entry of the first fragment | ||
| 4449 | 69481 | const dyn structOrInterface = [SuperType::TY_STRUCT, SuperType::TY_INTERFACE]; | |
| 4450 | 69481 | const dyn fctOrProc = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 4451 | 69481 | const String& firstFrag = node.functionNameFragments.front(); | |
| 4452 | 69481 | SymbolTableEntry* firstFragEntry = this.currentScope.lookup(firstFrag); | |
| 4453 | 69481 | if firstFragEntry != nil<SymbolTableEntry*> { | |
| 4454 | // Check if we have seen a 'this.' prefix, because the generator needs that | ||
| 4455 | 43658 | if firstFragEntry.scope.scopeType == ScopeType::STRUCT && firstFrag != THIS_VARIABLE_NAME { | |
| 4456 | 1 | const String message = String("The symbol '") + firstFrag + "' could not be found. Missing 'this.' prefix?"; | |
| 4457 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_VARIABLE, message); | |
| 4458 | 1 | } | |
| 4459 | |||
| 4460 | 43657 | firstFragEntry.used = true; | |
| 4461 | // Decide of which type the function call is | ||
| 4462 | 43657 | const QualType firstFragType = firstFragEntry.getQualType(); | |
| 4463 | 43657 | const QualType baseType = firstFragType.getBase(); | |
| 4464 | 43657 | if baseType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4465 | 43652 | if baseType.isOneOf(structOrInterface, len(structOrInterface)) { | |
| 4466 | 31245 | if firstFragEntry.scope.scopeType == ScopeType::GLOBAL { | |
| 4467 | 2891 | data.callType = FctCallType::TYPE_CTOR; | |
| 4468 | 2891 | } else { | |
| 4469 | 28354 | data.callType = FctCallType::TYPE_METHOD; | |
| 4470 | 28354 | } | |
| 4471 | 43652 | } else if baseType.isOneOf(fctOrProc, len(fctOrProc)) && firstFragEntry.scope.scopeType != ScopeType::GLOBAL { | |
| 4472 | 172 | data.callType = FctCallType::TYPE_FCT_PTR; | |
| 4473 | 12407 | } | |
| 4474 | 43652 | } | |
| 4475 | |||
| 4476 | // Get struct name. Retrieve it from alias if required | ||
| 4477 | 69475 | Pair<SymbolTableEntry*, bool> aliasResolution = this.rootScope.symbolTable.lookupWithAliasResolution(node.fqFunctionName); | |
| 4478 | 69475 | SymbolTableEntry* structEntry = aliasResolution.getFirst(); | |
| 4479 | 69475 | const bool isAlias = aliasResolution.getSecond(); | |
| 4480 | 69475 | String fqFunctionName = node.fqFunctionName; | |
| 4481 | |||
| 4482 | // Get the concrete template types | ||
| 4483 | 69475 | if isAlias { | |
| 4484 | 3 | const QualType aliasedType = structEntry.getQualType(); | |
| 4485 | 3 | fqFunctionName = aliasedType.getSubType(); | |
| 4486 | // Retrieve concrete template types from type alias | ||
| 4487 | 3 | data.templateTypes = aliasedType.getTemplateTypes(); | |
| 4488 | // Check if the aliased type specified template types and the struct instantiation does | ||
| 4489 | 3 | if !data.templateTypes.isEmpty() && node.hasTemplateTypes { | |
| 4490 | 2 | return this.softErrorER(cast<ASTNode*>(node.templateTypeLst), SemanticErrorType::ALIAS_WITH_TEMPLATE_LIST, String("The aliased type already has a template list")); | |
| 4491 | 1 | } | |
| 4492 | 3 | } | |
| 4493 | |||
| 4494 | // Check if this is a method call or a normal function call | ||
| 4495 | 69474 | if data.isMethodCall() { | |
| 4496 | // This is a method call | ||
| 4497 | 28354 | data.thisType = firstFragEntry.getQualType(); | |
| 4498 | 28354 | const QualType thisBaseType = data.thisType.getBase(); | |
| 4499 | 28354 | Scope* structBodyScope = thisBaseType.getBodyScope(); | |
| 4500 | 28354 | assert structBodyScope != nil<Scope*>; | |
| 4501 | 28354 | if !this.visitMethodCall(node, structBodyScope) { // Check if soft errors occurred | |
| 4502 | 4 | return Any(ExprResult{node.node.setEvaluatedSymbolType(QualType(SuperType::TY_UNRESOLVED), this.manIdx), nil<SymbolTableEntry*>}); | |
| 4503 | } | ||
| 4504 | 28352 | assert data.calleeParentScope != nil<Scope*>; | |
| 4505 | 69472 | } else if data.isFctPtrCall() { | |
| 4506 | // This is a function pointer call | ||
| 4507 | 172 | const QualType firstFragType = firstFragEntry.getQualType(); | |
| 4508 | 172 | const QualType functionType = firstFragType.getBase(); | |
| 4509 | 172 | assert functionType.isOneOf(fctOrProc, len(fctOrProc)); | |
| 4510 | 172 | if !this.visitFctPtrCall(node, functionType) { // Check if soft errors occurred | |
| 4511 | ✗ | return Any(ExprResult{node.node.setEvaluatedSymbolType(QualType(SuperType::TY_UNRESOLVED), this.manIdx), nil<SymbolTableEntry*>}); | |
| 4512 | } | ||
| 4513 | 172 | } else { | |
| 4514 | // This is an ordinary function call | ||
| 4515 | 40948 | assert data.isOrdinaryCall() || data.isCtorCall(); | |
| 4516 | 40948 | if !this.visitOrdinaryFctCall(node, fqFunctionName) { // Check if soft errors occurred | |
| 4517 | 10 | return Any(ExprResult{node.node.setEvaluatedSymbolType(QualType(SuperType::TY_UNRESOLVED), this.manIdx), nil<SymbolTableEntry*>}); | |
| 4518 | } | ||
| 4519 | 40943 | assert data.calleeParentScope != nil<Scope*>; | |
| 4520 | |||
| 4521 | // If the call is no ordinary call, it must be a constructor, which takes a struct as this type. | ||
| 4522 | 40943 | assert data.isOrdinaryCall() || data.thisType.is(SuperType::TY_STRUCT); | |
| 4523 | 41115 | } | |
| 4524 | |||
| 4525 | 69467 | const Function* callee = data.callee; | |
| 4526 | 69467 | if !data.isFctPtrCall() { | |
| 4527 | // Check if we were able to find a function | ||
| 4528 | 69295 | if callee == nil<const Function*> { | |
| 4529 | // Build error message | ||
| 4530 | 21 | String functionName = node.functionNameFragments.back(); | |
| 4531 | 21 | if data.isCtorCall() { | |
| 4532 | 2 | functionName = String(CTOR_FUNCTION_NAME); | |
| 4533 | 2 | } | |
| 4534 | 21 | ParamList errArgTypes; | |
| 4535 | 21 | errArgTypes.reserve(data.args.getSize()); | |
| 4536 | 40 | foreach const Arg& arg : data.args { | |
| 4537 | // Copy the arg type into a local first, instead of initializing a struct literal field with a reference to | ||
| 4538 | // a type with an overloaded assign operator | ||
| 4539 | 19 | const QualType argType = arg.getFirst(); | |
| 4540 | 19 | errArgTypes.pushBack(Param{argType, false}); | |
| 4541 | 19 | } | |
| 4542 | 21 | const QualTypeList noTemplateTypes; | |
| 4543 | 21 | const String signature = getFunctionSignature(functionName, data.thisType, QualType(SuperType::TY_DYN), errArgTypes, noTemplateTypes, false); | |
| 4544 | // Throw error | ||
| 4545 | 42 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_FUNCTION, String("Function/procedure '") + signature + "' could not be found"); | |
| 4546 | 21 | } | |
| 4547 | |||
| 4548 | // Check if we need to request a re-visit, because the function body was not type-checked yet | ||
| 4549 | 69274 | requestRevisitIfRequired(callee); | |
| 4550 | |||
| 4551 | // Get function entry from function object | ||
| 4552 | 69274 | SymbolTableEntry* functionEntry = callee.entry; | |
| 4553 | |||
| 4554 | // Check if the called function has sufficient visibility | ||
| 4555 | 69274 | data.isImported = data.calleeParentScope.isImportedBy(this.rootScope); | |
| 4556 | 69274 | const QualType functionEntryType = functionEntry.getQualType(); | |
| 4557 | 69274 | if data.isImported && !functionEntryType.isPublic() { | |
| 4558 | 2 | const String signature = callee.getSignature(); | |
| 4559 | 2 | if functionEntryType.is(SuperType::TY_FUNCTION) { | |
| 4560 | 4 | return this.softErrorER(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, String("Function '") + signature + "' has insufficient visibility"); | |
| 4561 | 2 | } else { | |
| 4562 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, String("Procedure '") + signature + "' has insufficient visibility"); | |
| 4563 | ✗ | } | |
| 4564 | 2 | } | |
| 4565 | |||
| 4566 | // A virtual call can only return the interface part of the struct, that the implementation of the method returns. | ||
| 4567 | // All other fields of the struct would be lost, so calls through an interface must not return an interface by value | ||
| 4568 | 69272 | if data.thisType.is(SuperType::TY_INTERFACE) && callee.returnType.is(SuperType::TY_INTERFACE) { | |
| 4569 | 4 | const String message = String("The method '") + callee.name + "' returns the interface '" + callee.returnType.getName(false, true) + "' by value, so it cannot be called through the interface '" + data.thisType.getName(false, true) + "'. Call it on the struct, that implements the interface, instead"; | |
| 4570 | 8 | return this.softErrorER(baseNode, SemanticErrorType::INTERFACE_RETURNED_BY_VALUE, message); | |
| 4571 | 4 | } | |
| 4572 | |||
| 4573 | // Mark direct calls to err<T>(...) as an error-return-trace origin site when tracing is enabled for this file. | ||
| 4574 | // err<T>'s own internal delegation to Result<T>'s error ctor is a distinct ctor call and is never matched here, | ||
| 4575 | // so this only ever fires for the user's own call site, not for that nested one. | ||
| 4576 | 69268 | if this.sourceFile.errorReturnTracing && callee.bodyScope != nil<Scope*> && callee.name == "err" { | |
| 4577 | 5 | Scope* calleeBodyScope = callee.bodyScope; | |
| 4578 | 5 | SourceFile* calleeSourceFile = calleeBodyScope.sourceFile; | |
| 4579 | 5 | if calleeSourceFile.isResultRT() { | |
| 4580 | 5 | node.isErrorTraceOrigin = true; | |
| 4581 | 5 | } | |
| 4582 | 5 | } | |
| 4583 | 69268 | } | |
| 4584 | |||
| 4585 | // Generate arg infos | ||
| 4586 | 69440 | if node.hasArgs { | |
| 4587 | 52477 | QualTypeList paramTypes; | |
| 4588 | 52477 | if data.isFctPtrCall() { | |
| 4589 | 120 | const QualType firstFragType = firstFragEntry.getQualType(); | |
| 4590 | 120 | const QualType functionType = firstFragType.getBase(); | |
| 4591 | 120 | paramTypes = functionType.getFunctionParamTypes(); | |
| 4592 | 120 | } else { | |
| 4593 | 52357 | assert callee != nil<const Function*>; | |
| 4594 | 52357 | paramTypes = callee.getParamTypes(); | |
| 4595 | 52357 | } | |
| 4596 | |||
| 4597 | 52477 | ASTArgLstNode* argLst = node.argLst; | |
| 4598 | 52477 | Vector<ArgInfo>& argInfos = argLst.argInfos.get(this.manIdx); | |
| 4599 | 52477 | argInfos.clear(); | |
| 4600 | 140845 | for unsigned long argIdx = 0l; argIdx < data.args.getSize(); argIdx++ { | |
| 4601 | 88368 | const QualType& expectedType = paramTypes.get(argIdx); | |
| 4602 | 88368 | const Arg& arg = data.args.get(argIdx); | |
| 4603 | 88368 | const QualType& actualType = arg.getFirst(); | |
| 4604 | |||
| 4605 | // By-value params of non-trivially copyable types get a copy of the argument, also if it is a reference | ||
| 4606 | 88368 | Function* copyCtor = nil<Function*>; | |
| 4607 | 88368 | const QualType actualValueType = actualType.removeReferenceWrapper(); | |
| 4608 | 88368 | if expectedType.isOwnedByValueParam(baseNode) && actualValueType.is(SuperType::TY_STRUCT) { | |
| 4609 | // The called function destructs its by-value params. External functions do not, so the caller has to destruct | ||
| 4610 | // the arg instead. | ||
| 4611 | 61 | const bool isExtCallee = callee != nil<const Function*> && callee.declNode.kind == ASTNodeKind::EXT_DECL; | |
| 4612 | 61 | Scope* argTemporaryScope = argTemporaryScopes.get(argIdx); | |
| 4613 | 61 | if !isExtCallee && !actualType.isRef() && argTemporaryScope != nil<Scope*> { | |
| 4614 | // Move the temporary into the param instead of copying it: the called function takes it over, so the | ||
| 4615 | // caller must not destruct it anymore | ||
| 4616 | 18 | argTemporaryScope.symbolTable.deleteAnonymous(argTemporaryNames.get(argIdx)); | |
| 4617 | 18 | } else { | |
| 4618 | 43 | copyCtor = this.matchCopyCtor(actualValueType, baseNode); | |
| 4619 | // Insert an anonymous symbol to track the dtor call of the copy for external functions | ||
| 4620 | 43 | if isExtCallee { | |
| 4621 | ✗ | ASTNode* argNode = getASTNode(argLst.args.get(argIdx)); | |
| 4622 | ✗ | this.currentScope.symbolTable.insertAnonymous(actualValueType, argNode, ULONG_MAX_VALUE); | |
| 4623 | ✗ | } | |
| 4624 | 43 | } | |
| 4625 | 61 | } | |
| 4626 | |||
| 4627 | 88368 | argInfos.pushBack(ArgInfo{copyCtor}); | |
| 4628 | 88368 | } | |
| 4629 | 52477 | assert argInfos.getSize() == argLst.args.getSize(); | |
| 4630 | 52477 | } | |
| 4631 | |||
| 4632 | // Retrieve return type | ||
| 4633 | 69440 | bool isFct = false; | |
| 4634 | 69440 | QualType returnType = QualType(SuperType::TY_DYN); | |
| 4635 | 69440 | if data.isFctPtrCall() { | |
| 4636 | 172 | const QualType firstFragType = firstFragEntry.getQualType(); | |
| 4637 | 172 | const QualType functionType = firstFragType.getBase(); | |
| 4638 | 172 | isFct = functionType.is(SuperType::TY_FUNCTION); | |
| 4639 | 172 | if isFct { | |
| 4640 | 73 | returnType = functionType.getFunctionReturnType(); | |
| 4641 | 73 | } else { | |
| 4642 | 99 | returnType = QualType(SuperType::TY_BOOL); | |
| 4643 | 99 | } | |
| 4644 | 172 | } else { | |
| 4645 | 69268 | isFct = callee.isFunction(); | |
| 4646 | 69268 | if data.isCtorCall() { | |
| 4647 | 9158 | returnType = data.thisType; | |
| 4648 | 69268 | } else if callee.isProcedure() { | |
| 4649 | 16771 | returnType = QualType(SuperType::TY_DYN); | |
| 4650 | 16771 | } else { | |
| 4651 | 43339 | returnType = callee.returnType; | |
| 4652 | 60110 | } | |
| 4653 | 69268 | } | |
| 4654 | |||
| 4655 | 69440 | const QualType returnBaseType = returnType.getBase(); | |
| 4656 | |||
| 4657 | // Make sure this source file knows about the return type | ||
| 4658 | 69440 | if returnBaseType.is(SuperType::TY_STRUCT) { | |
| 4659 | 16741 | returnType = this.mapImportedScopeTypeToLocalType(returnBaseType.getBodyScope(), returnType); | |
| 4660 | 16741 | } | |
| 4661 | |||
| 4662 | // Add anonymous symbol to keep track of dtor call, if non-trivially destructible | ||
| 4663 | 69440 | SymbolTableEntry* anonymousSymbol = nil<SymbolTableEntry*>; | |
| 4664 | 69440 | if returnType.is(SuperType::TY_STRUCT) && !returnType.isTriviallyDestructible(baseNode) { | |
| 4665 | 8072 | anonymousSymbol = this.currentScope.symbolTable.insertAnonymous(returnType, baseNode); | |
| 4666 | 8072 | } | |
| 4667 | |||
| 4668 | // Remove public qualifier to not have public local variables | ||
| 4669 | 69440 | TypeQualifiers& returnTypeQualifiers = returnType.getQualifiers(); | |
| 4670 | 69440 | returnTypeQualifiers.isPublic = false; | |
| 4671 | |||
| 4672 | // Check if the return value gets discarded | ||
| 4673 | 69440 | if isFct && !node.hasReturnValueReceiver() { | |
| 4674 | // Discarding a Result<T> is always a hard error when explicit error handling is enabled for this module, | ||
| 4675 | // regardless of ignoreUnusedReturnValue (which only ever weakens the generic warning below). | ||
| 4676 | 5333 | const QualType unwrappedReturnType = returnType.removeReferenceWrapper(); | |
| 4677 | 5333 | if this.sourceFile.explicitErrorHandling && unwrappedReturnType.isResultObj() { | |
| 4678 | 2 | const String message = String("A Result<T> returned from a call must be handled: bind it to a variable, propagate it with the '!' operator, handle it with '.unwrapOr(...)'/'.unwrapOrElse(...)', or explicitly discard it by binding it to a variable with a name starting with '_'"); | |
| 4679 | 4 | return this.softErrorER(baseNode, SemanticErrorType::DISCARDED_RESULT_VALUE, message); | |
| 4680 | 2 | } | |
| 4681 | |||
| 4682 | // Check if we want to ignore the discarded return value | ||
| 4683 | 5331 | bool ignoreUnusedReturnValue = false; | |
| 4684 | 5331 | if !data.isFctPtrCall() { | |
| 4685 | 5327 | assert callee != nil<const Function*>; | |
| 4686 | 5327 | ASTNode* calleeDeclNode = callee.declNode; | |
| 4687 | 5327 | if calleeDeclNode.kind == ASTNodeKind::FCT_DEF { | |
| 4688 | 391 | const ASTFctDefNode* fctDef = downcast<ASTFctDefNode>(calleeDeclNode); | |
| 4689 | 391 | if fctDef.node.attrs != nil<IVisitable*> { | |
| 4690 | 156 | const ASTTopLevelDefAttrNode* attrs = downcast<ASTTopLevelDefAttrNode>(fctDef.node.attrs); | |
| 4691 | 156 | ASTAttrLstNode* attrLst = attrs.attrLst; | |
| 4692 | 156 | ignoreUnusedReturnValue = attrLst.hasAttr(String(ATTR_IGNORE_UNUSED_RETURN_VALUE)); | |
| 4693 | 156 | } | |
| 4694 | 391 | } | |
| 4695 | 5327 | } | |
| 4696 | |||
| 4697 | 5331 | if !ignoreUnusedReturnValue { | |
| 4698 | 5175 | this.addWarning(baseNode.codeLoc, CompilerWarningType::UNUSED_RETURN_VALUE, String("The return value of the function call is unused")); | |
| 4699 | 5175 | } | |
| 4700 | 5331 | } | |
| 4701 | |||
| 4702 | 138876 | return Any(ExprResult{node.node.setEvaluatedSymbolType(returnType, this.manIdx), anonymousSymbol}); | |
| 4703 | 79415 | } | |
| 4704 | |||
| 4705 | 40948 | f<bool> TypeChecker.visitOrdinaryFctCall(ASTFctCallNode* node, const String& origFqFunctionName) { | |
| 4706 | 40948 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 4707 | 40948 | String fqFunctionName = origFqFunctionName; | |
| 4708 | 40948 | FctCallData& data = node.data.get(this.manIdx); | |
| 4709 | |||
| 4710 | // Check if this is a well-known ctor/fct call | ||
| 4711 | 40948 | if node.functionNameFragments.getSize() == 1l { | |
| 4712 | 40741 | this.ensureLoadedRuntimeForTypeName(fqFunctionName); | |
| 4713 | 40741 | this.ensureLoadedRuntimeForFunctionName(fqFunctionName); | |
| 4714 | 40741 | } | |
| 4715 | |||
| 4716 | // Check if the type is generic (possible in case of ctor call) | ||
| 4717 | 40948 | const GenericType* genericType = this.rootScope.lookupGenericTypeStrict(fqFunctionName); | |
| 4718 | 40948 | if genericType != nil<const GenericType*> && this.typeMapping.contains(fqFunctionName) { | |
| 4719 | 10 | const QualType& replacementType = this.typeMapping.get(fqFunctionName); | |
| 4720 | 10 | if replacementType.is(SuperType::TY_STRUCT) { | |
| 4721 | 10 | fqFunctionName = replacementType.getSubType(); | |
| 4722 | 10 | } | |
| 4723 | 10 | } | |
| 4724 | |||
| 4725 | // Check if the exported name registry contains that function name | ||
| 4726 | 40948 | const NameRegistryEntry* functionRegistryEntry = this.sourceFile.getNameRegistryEntry(fqFunctionName); | |
| 4727 | 40948 | if functionRegistryEntry == nil<const NameRegistryEntry*> { | |
| 4728 | 4 | if this.sourceFile.isAmbiguousName(fqFunctionName) { | |
| 4729 | 1 | this.softError(baseNode, SemanticErrorType::AMBIGUOUS_SYMBOL, this.sourceFile.getAmbiguousNameMessage(fqFunctionName)); | |
| 4730 | 1 | return false; | |
| 4731 | 1 | } | |
| 4732 | 3 | const String message = String("Function/procedure/struct '") + node.functionNameFragments.back() + "' could not be found"; | |
| 4733 | 3 | this.softError(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_FUNCTION, message); | |
| 4734 | 3 | return false; | |
| 4735 | 3 | } | |
| 4736 | 40944 | const SymbolTableEntry* functionEntry = functionRegistryEntry.targetEntry; | |
| 4737 | 40944 | data.calleeParentScope = functionRegistryEntry.targetScope; | |
| 4738 | |||
| 4739 | // Check if the target symbol is a struct -> this must be a constructor call | ||
| 4740 | 40944 | String functionName = node.functionNameFragments.back(); | |
| 4741 | 40944 | if functionEntry != nil<const SymbolTableEntry*> { | |
| 4742 | 40944 | const QualType structEntryType = functionEntry.getQualType(); | |
| 4743 | 40944 | if structEntryType.is(SuperType::TY_STRUCT) { | |
| 4744 | 9161 | data.callType = FctCallType::TYPE_CTOR; | |
| 4745 | 9161 | functionName = String(CTOR_FUNCTION_NAME); | |
| 4746 | |||
| 4747 | // Substantiate potentially generic this struct | ||
| 4748 | 9161 | const Struct* thisStruct = structEntryType.getStruct(baseNode, data.templateTypes); | |
| 4749 | 9161 | if thisStruct == nil<const Struct*> { | |
| 4750 | 1 | const SymbolTableEntry* structEntry = functionRegistryEntry.targetEntry; | |
| 4751 | 1 | const String signature = getStructSignature(structEntry.name, data.templateTypes); | |
| 4752 | 1 | this.softError(baseNode, SemanticErrorType::UNKNOWN_DATATYPE, String("Could not find struct candidate for struct '") + signature + "'. Do the template types match?"); | |
| 4753 | 1 | return false; | |
| 4754 | 1 | } | |
| 4755 | |||
| 4756 | // Set the 'this' type of the function to the struct type | ||
| 4757 | 9160 | data.thisType = structEntryType.getWithBodyScope(thisStruct.scope); | |
| 4758 | 9160 | data.calleeParentScope = thisStruct.scope; | |
| 4759 | 9161 | } | |
| 4760 | 40943 | } | |
| 4761 | |||
| 4762 | // Attach the concrete template types to the 'this' type | ||
| 4763 | 40943 | if !data.thisType.is(SuperType::TY_DYN) && !data.templateTypes.isEmpty() { | |
| 4764 | 2100 | data.thisType = data.thisType.getWithTemplateTypes(data.templateTypes); | |
| 4765 | 2100 | } | |
| 4766 | |||
| 4767 | // Map local arg types to imported types | ||
| 4768 | 109295 | for unsigned long i = 0l; i < data.args.getSize(); i++ { | |
| 4769 | 68352 | Arg& arg = data.args.get(i); | |
| 4770 | 68352 | QualType& argType = arg.getFirst(); | |
| 4771 | 68352 | argType = this.mapLocalTypeToImportedScopeType(data.calleeParentScope, argType); | |
| 4772 | 68352 | } | |
| 4773 | |||
| 4774 | // Map local template types to imported types | ||
| 4775 | 45712 | for unsigned long i = 0l; i < data.templateTypes.getSize(); i++ { | |
| 4776 | 4769 | QualType& templateType = data.templateTypes.get(i); | |
| 4777 | 4769 | templateType = this.mapLocalTypeToImportedScopeType(data.calleeParentScope, templateType); | |
| 4778 | 4769 | } | |
| 4779 | |||
| 4780 | // Retrieve function object | ||
| 4781 | 40943 | Scope* matchScope = data.calleeParentScope; | |
| 4782 | 40943 | data.callee = matchFunction(matchScope, functionName, data.thisType, data.args, data.templateTypes, false, baseNode); | |
| 4783 | |||
| 4784 | 40943 | return true; | |
| 4785 | 40948 | } | |
| 4786 | |||
| 4787 | 172 | f<bool> TypeChecker.visitFctPtrCall(ASTFctCallNode* node, const QualType& functionType) { | |
| 4788 | 172 | const FctCallData& data = node.data.get(this.manIdx); | |
| 4789 | |||
| 4790 | // Check if the given argument types match the type | ||
| 4791 | 172 | const QualTypeList expectedArgTypes = functionType.getFunctionParamTypes(); | |
| 4792 | 172 | if data.args.getSize() != expectedArgTypes.getSize() { | |
| 4793 | ✗ | this.softError(cast<ASTNode*>(node), SemanticErrorType::REFERENCED_UNDEFINED_FUNCTION, String("Expected and actual number of arguments do not match")); | |
| 4794 | ✗ | return false; | |
| 4795 | ✗ | } | |
| 4796 | |||
| 4797 | // Never resolve a generic type here, which an empty list of candidate template types does | ||
| 4798 | 172 | const Vector<GenericType> noCandidateTemplateTypes; | |
| 4799 | |||
| 4800 | 311 | for unsigned long i = 0l; i < data.args.getSize(); i++ { | |
| 4801 | 139 | const Arg& arg = data.args.get(i); | |
| 4802 | 139 | const QualType& actualType = arg.getFirst(); | |
| 4803 | 139 | const QualType& expectedType = expectedArgTypes.get(i); | |
| 4804 | 139 | TypeMapping tm; | |
| 4805 | 139 | if !matchRequestedToCandidateType(expectedType, actualType, tm, noCandidateTemplateTypes, false, false) { | |
| 4806 | ✗ | const String message = String("Expected ") + expectedType.getName(false) + " but got " + actualType.getName(false); | |
| 4807 | ✗ | this.softError(getASTNode(node.argLst.args.get(i)), SemanticErrorType::REFERENCED_UNDEFINED_FUNCTION, message); | |
| 4808 | ✗ | return false; | |
| 4809 | ✗ | } | |
| 4810 | 139 | } | |
| 4811 | 172 | return true; | |
| 4812 | 172 | } | |
| 4813 | |||
| 4814 | 28354 | f<bool> TypeChecker.visitMethodCall(ASTFctCallNode* node, Scope* structScope) { | |
| 4815 | 28354 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 4816 | 28354 | FctCallData& data = node.data.get(this.manIdx); | |
| 4817 | 28354 | Scope* currentStructScope = structScope; | |
| 4818 | |||
| 4819 | // Traverse through structs - the first fragment is already looked up and the last one is the method name | ||
| 4820 | 32101 | for unsigned long i = 1l; i < node.functionNameFragments.getSize() - 1l; i++ { | |
| 4821 | 3749 | const String& identifier = node.functionNameFragments.get(i); | |
| 4822 | |||
| 4823 | // Retrieve field entry, also looking through composed fields | ||
| 4824 | 3749 | const QualType currentStructType = data.thisType.getBase(); | |
| 4825 | 3749 | Struct* spiceStruct = currentStructType.getStruct(baseNode); | |
| 4826 | 3749 | assert spiceStruct != nil<Struct*>; | |
| 4827 | 3749 | Vector<unsigned long> indexPath; | |
| 4828 | 3749 | SymbolTableEntry* fieldEntry = spiceStruct.lookupInComposedFields(identifier, indexPath); | |
| 4829 | 3749 | if fieldEntry == nil<SymbolTableEntry*> { | |
| 4830 | 1 | const QualType thisBaseType = data.thisType.getBase(); | |
| 4831 | 1 | const String errorMsg = String("The type '") + thisBaseType.getName(false, true) + "' does not have a member with the name '" + identifier + "'"; | |
| 4832 | 1 | this.softError(baseNode, SemanticErrorType::ACCESS_TO_NON_EXISTING_MEMBER, errorMsg); | |
| 4833 | 1 | return false; | |
| 4834 | 1 | } | |
| 4835 | 3748 | const QualType fieldType = fieldEntry.getQualType(); | |
| 4836 | 3748 | const QualType fieldBaseType = fieldType.getBase(); | |
| 4837 | 3748 | const dyn structOrInterface = [SuperType::TY_STRUCT, SuperType::TY_INTERFACE]; | |
| 4838 | 3748 | if !fieldBaseType.isOneOf(structOrInterface, len(structOrInterface)) { | |
| 4839 | 1 | const String message = String("Cannot call a method on '") + identifier + "', since it is no struct or interface"; | |
| 4840 | 1 | this.softError(baseNode, SemanticErrorType::INVALID_MEMBER_ACCESS, message); | |
| 4841 | 1 | return false; | |
| 4842 | 1 | } | |
| 4843 | 3747 | fieldEntry.used = true; | |
| 4844 | |||
| 4845 | // Get struct type and scope | ||
| 4846 | 3747 | data.thisType = fieldType; | |
| 4847 | 3747 | currentStructScope = fieldBaseType.getBodyScope(); | |
| 4848 | 3747 | assert currentStructScope != nil<Scope*>; | |
| 4849 | 3749 | } | |
| 4850 | |||
| 4851 | 28352 | if data.thisType.is(SuperType::TY_INTERFACE) { | |
| 4852 | ✗ | this.softError(baseNode, SemanticErrorType::INVALID_MEMBER_ACCESS, String("Cannot call a method on an interface")); | |
| 4853 | ✗ | return false; | |
| 4854 | ✗ | } | |
| 4855 | |||
| 4856 | // Map local arg types to imported types | ||
| 4857 | 28352 | data.calleeParentScope = currentStructScope; | |
| 4858 | 28352 | Scope* matchScope = currentStructScope; | |
| 4859 | 48248 | for unsigned long i = 0l; i < data.args.getSize(); i++ { | |
| 4860 | 19896 | Arg& arg = data.args.get(i); | |
| 4861 | 19896 | QualType& argType = arg.getFirst(); | |
| 4862 | 19896 | argType = this.mapLocalTypeToImportedScopeType(data.calleeParentScope, argType); | |
| 4863 | 19896 | } | |
| 4864 | |||
| 4865 | // Map local template types to imported types | ||
| 4866 | 28381 | for unsigned long i = 0l; i < data.templateTypes.getSize(); i++ { | |
| 4867 | 29 | QualType& templateType = data.templateTypes.get(i); | |
| 4868 | 29 | templateType = this.mapLocalTypeToImportedScopeType(data.calleeParentScope, templateType); | |
| 4869 | 29 | } | |
| 4870 | |||
| 4871 | // 'this' type | ||
| 4872 | 28352 | data.thisType = data.thisType.autoDereference(); | |
| 4873 | 28352 | data.thisType = this.mapLocalTypeToImportedScopeType(data.calleeParentScope, data.thisType); | |
| 4874 | |||
| 4875 | // Retrieve function object | ||
| 4876 | 28352 | const String& functionName = node.functionNameFragments.back(); | |
| 4877 | 28352 | data.callee = matchFunction(matchScope, functionName, data.thisType, data.args, data.templateTypes, false, baseNode); | |
| 4878 | |||
| 4879 | 28352 | return true; | |
| 4880 | } | ||
| 4881 | |||
| 4882 | 184 | public f<Any> TypeChecker.visitArrayInitialization(ASTArrayInitializationNode* node) { | |
| 4883 | 184 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 4884 | 184 | if node.itemLst == nil<ASTArgLstNode*> || node.itemLst.args.isEmpty() { | |
| 4885 | 2 | return this.softErrorER(baseNode, SemanticErrorType::ARRAY_SIZE_INVALID, String("Array initializers must at least contain one value")); | |
| 4886 | 1 | } | |
| 4887 | 183 | node.actualSize = node.itemLst.args.getSize(); | |
| 4888 | |||
| 4889 | 183 | QualType actualItemType = QualType(SuperType::TY_DYN); | |
| 4890 | // Check if all values have the same type | ||
| 4891 | 1637 | foreach IVisitable* arg : node.itemLst.args { | |
| 4892 | 1455 | const ExprResult itemResult = this.visitExpr(arg); | |
| 4893 | 1455 | const QualType itemType = itemResult.ty; | |
| 4894 | 1455 | if itemType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4895 | 1455 | if actualItemType.is(SuperType::TY_DYN) { // Perform type inference | |
| 4896 | 183 | actualItemType = itemType; | |
| 4897 | 1455 | } else if itemType != actualItemType { // Check if types are matching | |
| 4898 | 1 | const String message = String("All provided values have to be of the same data type. You provided ") + actualItemType.getName(false) + " and " + itemType.getName(false); | |
| 4899 | 2 | return this.softErrorER(getASTNode(arg), SemanticErrorType::ARRAY_ITEM_TYPE_NOT_MATCHING, message); | |
| 4900 | 1272 | } | |
| 4901 | 1454 | } | |
| 4902 | 182 | assert !actualItemType.is(SuperType::TY_DYN); | |
| 4903 | |||
| 4904 | 182 | const QualType arrayType = actualItemType.toArr(baseNode, cast<unsigned int>(node.actualSize), true); | |
| 4905 | 182 | return Any(ExprResult{node.node.setEvaluatedSymbolType(arrayType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 4906 | } | ||
| 4907 | |||
| 4908 | 1741 | public f<Any> TypeChecker.visitStructInstantiation(ASTStructInstantiationNode* node) { | |
| 4909 | 1741 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 4910 | |||
| 4911 | // Retrieve struct name | ||
| 4912 | 1741 | Pair<SymbolTableEntry*, bool> aliasResolution = this.rootScope.symbolTable.lookupWithAliasResolution(node.fqStructName); | |
| 4913 | 1741 | SymbolTableEntry* aliasedEntry = aliasResolution.getFirst(); | |
| 4914 | 1741 | const bool isAlias = aliasResolution.getSecond(); | |
| 4915 | 1741 | String structName = node.fqStructName; | |
| 4916 | 1741 | if isAlias { | |
| 4917 | 3 | const QualType aliasedType = aliasedEntry.getQualType(); | |
| 4918 | 3 | structName = aliasedType.getSubType(); | |
| 4919 | 3 | } | |
| 4920 | |||
| 4921 | // Check if the struct type is generic | ||
| 4922 | 1741 | const GenericType* genericType = this.rootScope.lookupGenericTypeStrict(structName); | |
| 4923 | 1741 | if genericType != nil<const GenericType*> && this.typeMapping.contains(structName) { | |
| 4924 | ✗ | const QualType& replacementType = this.typeMapping.get(structName); | |
| 4925 | ✗ | if replacementType.is(SuperType::TY_STRUCT) { | |
| 4926 | ✗ | structName = replacementType.getSubType(); | |
| 4927 | ✗ | } | |
| 4928 | ✗ | } | |
| 4929 | |||
| 4930 | // Retrieve struct | ||
| 4931 | 1741 | const NameRegistryEntry* registryEntry = this.sourceFile.getNameRegistryEntry(structName); | |
| 4932 | 1741 | if registryEntry == nil<const NameRegistryEntry*> { | |
| 4933 | 3 | if this.sourceFile.isAmbiguousName(structName) { | |
| 4934 | 2 | return this.softErrorER(baseNode, SemanticErrorType::AMBIGUOUS_SYMBOL, this.sourceFile.getAmbiguousNameMessage(structName)); | |
| 4935 | 1 | } | |
| 4936 | 4 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_STRUCT, String("Cannot find struct '") + structName + "'"); | |
| 4937 | 2 | } | |
| 4938 | 1738 | assert registryEntry.targetEntry != nil<SymbolTableEntry*> && registryEntry.targetScope != nil<Scope*>; | |
| 4939 | 1738 | SymbolTableEntry* structEntry = registryEntry.targetEntry; | |
| 4940 | |||
| 4941 | // Check visibility | ||
| 4942 | 1738 | const QualType structEntryType = structEntry.getQualType(); | |
| 4943 | 1738 | if !structEntryType.isPublic() && structEntry.scope.isImportedBy(this.currentScope) { | |
| 4944 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INSUFFICIENT_VISIBILITY, String("Struct '") + structName + "' has insufficient visibility"); | |
| 4945 | 1 | } | |
| 4946 | |||
| 4947 | // Get struct type | ||
| 4948 | 1737 | QualType structType = structEntryType; | |
| 4949 | |||
| 4950 | // Get the concrete template types | ||
| 4951 | 1737 | QualTypeList concreteTemplateTypes; | |
| 4952 | 1737 | if isAlias { | |
| 4953 | // Retrieve concrete template types from type alias | ||
| 4954 | 3 | const QualType aliasedType = aliasedEntry.getQualType(); | |
| 4955 | 3 | concreteTemplateTypes = aliasedType.getTemplateTypes(); | |
| 4956 | // Check if the aliased type specified template types and the struct instantiation does | ||
| 4957 | 3 | if !concreteTemplateTypes.isEmpty() && node.templateTypeLst != nil<ASTTypeLstNode*> { | |
| 4958 | 2 | return this.softErrorER(cast<ASTNode*>(node.templateTypeLst), SemanticErrorType::ALIAS_WITH_TEMPLATE_LIST, String("The aliased type already has a template list")); | |
| 4959 | 1 | } | |
| 4960 | 3 | } | |
| 4961 | |||
| 4962 | 1736 | if node.templateTypeLst != nil<ASTTypeLstNode*> { | |
| 4963 | 32 | concreteTemplateTypes.reserve(node.templateTypeLst.dataTypes.getSize()); | |
| 4964 | 68 | foreach ASTDataTypeNode* dataType : node.templateTypeLst.dataTypes { | |
| 4965 | 37 | const QualType concreteType = this.getDataType(dataType); | |
| 4966 | 37 | if concreteType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 4967 | // Check if generic type | ||
| 4968 | 37 | if concreteType.is(SuperType::TY_GENERIC) { | |
| 4969 | 2 | return this.softErrorER(cast<ASTNode*>(dataType), SemanticErrorType::EXPECTED_NON_GENERIC_TYPE, String("Struct instantiations may only take concrete template types")); | |
| 4970 | 1 | } | |
| 4971 | 36 | concreteTemplateTypes.pushBack(concreteType); | |
| 4972 | 36 | } | |
| 4973 | 31 | } | |
| 4974 | |||
| 4975 | // Get the struct instance | ||
| 4976 | 1735 | Struct* spiceStruct = structType.getStructAndAdjustType(baseNode, concreteTemplateTypes); | |
| 4977 | 1735 | Struct*& instantiatedStruct = node.instantiatedStructs.get(this.manIdx); | |
| 4978 | 1735 | instantiatedStruct = spiceStruct; | |
| 4979 | 1735 | if spiceStruct == nil<Struct*> { | |
| 4980 | 1 | const String message = String("Struct '") + getStructSignature(structName, concreteTemplateTypes) + "' could not be found"; | |
| 4981 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCED_UNDEFINED_STRUCT, message); | |
| 4982 | 1 | } | |
| 4983 | |||
| 4984 | // Struct instantiation for an inheriting struct is forbidden, because the vtable needs to be initialized and this is done in | ||
| 4985 | // the ctor of the struct, which is never called in case of struct instantiation | ||
| 4986 | 1734 | if !spiceStruct.interfaceTypes.isEmpty() { | |
| 4987 | 2 | return this.softErrorER(baseNode, SemanticErrorType::INVALID_STRUCT_INSTANTIATION, String("Struct instantiations for inheriting structs are forbidden")); | |
| 4988 | 1 | } | |
| 4989 | |||
| 4990 | // Check if the number of fields matches | ||
| 4991 | 1733 | if node.fieldLst != nil<ASTArgLstNode*> { // Check if any fields are passed. Empty braces are also allowed | |
| 4992 | 1684 | ASTArgLstNode* fieldLst = node.fieldLst; | |
| 4993 | 1684 | if spiceStruct.fieldTypes.getSize() != fieldLst.args.getSize() { | |
| 4994 | 4 | return this.softErrorER(cast<ASTNode*>(fieldLst), SemanticErrorType::NUMBER_OF_FIELDS_NOT_MATCHING, String("You've passed too less/many field values. Pass either none or all of them")); | |
| 4995 | 2 | } | |
| 4996 | |||
| 4997 | // Check if the field types are matching | ||
| 4998 | 1682 | const unsigned long fieldCount = spiceStruct.fieldTypes.getSize(); | |
| 4999 | 1682 | Scope* structScope = spiceStruct.scope; | |
| 5000 | 1682 | const unsigned long explicitFieldsStartIdx = structScope.getFieldCount() - fieldCount; | |
| 5001 | // Per-manifestation: a struct literal inside a generic function is re-type-checked once per manifestation of | ||
| 5002 | // that function, and the struct itself may resolve to a different concrete type each time (e.g. HashEntry<K, | ||
| 5003 | // V> inside HashTable<K, V>.upsert()), so the copy-ctor set from one manifestation must not leak into another. | ||
| 5004 | 1682 | Vector<Function*>& fieldCopyCtors = node.fieldCopyCtors.get(this.manIdx); | |
| 5005 | 1682 | fieldCopyCtors.clear(); | |
| 5006 | 5802 | for unsigned long i = 0l; i < fieldLst.args.getSize(); i++ { | |
| 5007 | // Get actual type | ||
| 5008 | 4120 | IVisitable* assignExpr = fieldLst.args.get(i); | |
| 5009 | 4120 | ASTNode* assignExprNode = getASTNode(assignExpr); | |
| 5010 | 4120 | ExprResult fieldResult = this.visitExpr(assignExpr); | |
| 5011 | 4120 | if fieldResult.ty.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 5012 | // Get expected type | ||
| 5013 | 4120 | SymbolTableEntry* expectedField = structScope.lookupField(cast<unsigned int>(explicitFieldsStartIdx + i)); | |
| 5014 | 4120 | assert expectedField != nil<SymbolTableEntry*>; | |
| 5015 | // Copy the field type into a local first, instead of initializing a struct literal field with a reference to a | ||
| 5016 | // type with an overloaded assign operator | ||
| 5017 | 4120 | const QualType expectedFieldType = expectedField.getQualType(); | |
| 5018 | 4120 | const ExprResult expected = ExprResult{expectedFieldType, expectedField}; | |
| 5019 | 4120 | const bool rhsIsImmediate = assignExprNode.hasCompileTimeValue(this.manIdx); | |
| 5020 | |||
| 5021 | // Capture anonymous info up front: getFieldAssignResultType may delete the anonymous entry (temp stealing | ||
| 5022 | // in performStructAssign), turning fieldResult.entry into a dangling pointer. | ||
| 5023 | 4120 | const bool rhsIsAnonymous = fieldResult.entry != nil<SymbolTableEntry*> && fieldResult.entry.anonymous; | |
| 5024 | 4120 | String rhsEntryName = String(""); | |
| 5025 | 4120 | if rhsIsAnonymous { | |
| 5026 | 13 | rhsEntryName = fieldResult.entry.name; | |
| 5027 | 13 | } | |
| 5028 | |||
| 5029 | // Check if actual type matches expected type. A non-anonymous, non-trivially-copyable rhs (e.g. a plain | ||
| 5030 | // local variable) needs its value deep-copied into the field: the struct instantiation does not | ||
| 5031 | // consume/move it, so the caller keeps its own copy alive and destructs it independently. | ||
| 5032 | 4120 | Pair<QualType, Function*> fieldAssignResult = this.opRuleManager.getFieldAssignResultType(assignExprNode, expected, fieldResult, rhsIsImmediate, true); | |
| 5033 | 4120 | fieldCopyCtors.pushBack(fieldAssignResult.getSecond()); | |
| 5034 | |||
| 5035 | // If there is an anonymous entry attached (e.g. for struct instantiation), delete it. | ||
| 5036 | // Safe to call even if performStructAssign already deleted it: erasing by key is a no-op when absent. | ||
| 5037 | 4120 | if rhsIsAnonymous { | |
| 5038 | 13 | this.currentScope.symbolTable.deleteAnonymous(rhsEntryName); | |
| 5039 | 13 | fieldResult.entry = nil<SymbolTableEntry*>; | |
| 5040 | 13 | } | |
| 5041 | 4120 | } | |
| 5042 | 1682 | assert fieldCopyCtors.getSize() == fieldLst.args.getSize(); | |
| 5043 | 1682 | } else { | |
| 5044 | 49 | bool hasReferenceField = false; | |
| 5045 | 149 | foreach const QualType& fieldType : spiceStruct.fieldTypes { | |
| 5046 | 100 | if fieldType.isRef() { | |
| 5047 | 1 | hasReferenceField = true; | |
| 5048 | 1 | } | |
| 5049 | 100 | } | |
| 5050 | 49 | if hasReferenceField { | |
| 5051 | 2 | return this.softErrorER(baseNode, SemanticErrorType::REFERENCE_WITHOUT_INITIALIZER, String("The struct takes at least one reference field. You need to instantiate it with all fields.")); | |
| 5052 | 1 | } | |
| 5053 | 48 | } | |
| 5054 | |||
| 5055 | // Update type of struct entry | ||
| 5056 | 1730 | structEntry.updateType(structType, true); | |
| 5057 | |||
| 5058 | // Add anonymous symbol to keep track of dtor call, if non-trivially destructible | ||
| 5059 | 1730 | SymbolTableEntry* anonymousEntry = nil<SymbolTableEntry*>; | |
| 5060 | 1730 | if !structType.isTriviallyDestructible(baseNode) { | |
| 5061 | 65 | anonymousEntry = this.currentScope.symbolTable.insertAnonymous(structType, baseNode); | |
| 5062 | 65 | } | |
| 5063 | |||
| 5064 | // Remove public qualifier to not have public local variables | ||
| 5065 | 1730 | TypeQualifiers& structTypeQualifiers = structType.getQualifiers(); | |
| 5066 | 1730 | structTypeQualifiers.isPublic = false; | |
| 5067 | |||
| 5068 | 3460 | return Any(ExprResult{node.node.setEvaluatedSymbolType(structType, this.manIdx), anonymousEntry}); | |
| 5069 | 1741 | } | |
| 5070 | |||
| 5071 | 67 | public f<Any> TypeChecker.visitLambdaFunc(ASTLambdaFuncNode* node) { | |
| 5072 | 67 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5073 | |||
| 5074 | // Check if all control paths in the lambda body return | ||
| 5075 | 67 | bool returnsOnAllControlPaths = true; | |
| 5076 | 67 | if !baseNode.returnsOnAllControlPaths(&returnsOnAllControlPaths, this.manIdx) { | |
| 5077 | 2 | return this.softErrorER(baseNode, SemanticErrorType::MISSING_RETURN_STMT, String("Not all control paths of this lambda function have a return statement")); | |
| 5078 | 1 | } | |
| 5079 | |||
| 5080 | // Change to function scope | ||
| 5081 | 66 | Scope* bodyScope = this.currentScope.getChildScope(node.getScopeId()); | |
| 5082 | 66 | ScopeHandle scopeHandle = ScopeHandle(&this.compilerPass, bodyScope, ScopeType::LAMBDA_BODY); | |
| 5083 | |||
| 5084 | // Visit return type | ||
| 5085 | 66 | const QualType returnType = this.getDataType(node.returnType); | |
| 5086 | 66 | if returnType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 5087 | 66 | if returnType.is(SuperType::TY_DYN) { | |
| 5088 | 2 | return this.softErrorER(baseNode, SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Dyn return types are not allowed")); | |
| 5089 | 1 | } | |
| 5090 | |||
| 5091 | // Set the type of the result variable | ||
| 5092 | 65 | SymbolTableEntry* resultVarEntry = this.currentScope.lookupStrict(String(RETURN_VARIABLE_NAME)); | |
| 5093 | 65 | assert resultVarEntry != nil<SymbolTableEntry*>; | |
| 5094 | 65 | resultVarEntry.updateType(returnType, true); | |
| 5095 | 65 | resultVarEntry.used = true; | |
| 5096 | |||
| 5097 | // Visit parameters | ||
| 5098 | 65 | QualTypeList paramTypes; | |
| 5099 | 65 | ParamList paramList; | |
| 5100 | 65 | if node.hasParams { | |
| 5101 | 58 | this.visitLambdaParams(baseNode, node.paramLst, paramTypes, paramList); | |
| 5102 | 58 | } | |
| 5103 | |||
| 5104 | // Visit lambda body | ||
| 5105 | 65 | this.visit(node.body); | |
| 5106 | |||
| 5107 | // Leave function body scope | ||
| 5108 | 65 | scopeHandle.leaveScopeEarly(); | |
| 5109 | |||
| 5110 | // Prepare type of function | ||
| 5111 | 65 | const QualType functionTypeBase = QualType(SuperType::TY_FUNCTION); | |
| 5112 | 65 | const QualType functionTypeWithSignature = functionTypeBase.getWithFunctionParamAndReturnTypes(returnType, paramTypes); | |
| 5113 | 65 | const QualType functionType = functionTypeWithSignature.getWithLambdaCaptures(!bodyScope.symbolTable.captures.isEmpty()); | |
| 5114 | |||
| 5115 | // Create function object | ||
| 5116 | 65 | this.createLambdaManifestation(baseNode, bodyScope, returnType, paramList); | |
| 5117 | |||
| 5118 | // Check special requirements if this is an async lambda | ||
| 5119 | 65 | this.checkAsyncLambdaCaptureRules(baseNode, node.lambdaAttr); | |
| 5120 | |||
| 5121 | 130 | return Any(ExprResult{node.node.node.setEvaluatedSymbolType(functionType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 5122 | 66 | } | |
| 5123 | |||
| 5124 | 73 | public f<Any> TypeChecker.visitLambdaProc(ASTLambdaProcNode* node) { | |
| 5125 | 73 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5126 | |||
| 5127 | // Mark unreachable statements | ||
| 5128 | 73 | bool doSetPredecessorsUnreachable = true; | |
| 5129 | 73 | const bool _ = baseNode.returnsOnAllControlPaths(&doSetPredecessorsUnreachable, this.manIdx); | |
| 5130 | |||
| 5131 | // Change to function scope | ||
| 5132 | 73 | Scope* bodyScope = this.currentScope.getChildScope(node.getScopeId()); | |
| 5133 | 73 | ScopeHandle scopeHandle = ScopeHandle(&this.compilerPass, bodyScope, ScopeType::LAMBDA_BODY); | |
| 5134 | |||
| 5135 | // Visit parameters | ||
| 5136 | 73 | QualTypeList paramTypes; | |
| 5137 | 73 | ParamList paramList; | |
| 5138 | 73 | if node.hasParams { | |
| 5139 | 32 | this.visitLambdaParams(baseNode, node.paramLst, paramTypes, paramList); | |
| 5140 | 32 | } | |
| 5141 | |||
| 5142 | // Visit lambda body | ||
| 5143 | 73 | this.visit(node.body); | |
| 5144 | |||
| 5145 | // Leave function body scope | ||
| 5146 | 73 | scopeHandle.leaveScopeEarly(); | |
| 5147 | |||
| 5148 | // Prepare type of function | ||
| 5149 | 73 | const QualType functionTypeBase = QualType(SuperType::TY_PROCEDURE); | |
| 5150 | 73 | const QualType functionTypeWithSignature = functionTypeBase.getWithFunctionParamAndReturnTypes(QualType(SuperType::TY_DYN), paramTypes); | |
| 5151 | 73 | const QualType functionType = functionTypeWithSignature.getWithLambdaCaptures(!bodyScope.symbolTable.captures.isEmpty()); | |
| 5152 | |||
| 5153 | // Create function object | ||
| 5154 | 73 | this.createLambdaManifestation(baseNode, bodyScope, QualType(SuperType::TY_DYN), paramList); | |
| 5155 | |||
| 5156 | // Check special requirements if this is an async lambda | ||
| 5157 | 73 | this.checkAsyncLambdaCaptureRules(baseNode, node.lambdaAttr); | |
| 5158 | |||
| 5159 | 146 | return Any(ExprResult{node.node.node.setEvaluatedSymbolType(functionType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 5160 | 73 | } | |
| 5161 | |||
| 5162 | 4 | public f<Any> TypeChecker.visitLambdaExpr(ASTLambdaExprNode* node) { | |
| 5163 | 4 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5164 | |||
| 5165 | // Change to function scope | ||
| 5166 | 4 | Scope* bodyScope = this.currentScope.getChildScope(node.getScopeId()); | |
| 5167 | 4 | ScopeHandle scopeHandle = ScopeHandle(&this.compilerPass, bodyScope, ScopeType::LAMBDA_BODY); | |
| 5168 | |||
| 5169 | // Visit parameters | ||
| 5170 | 4 | QualTypeList paramTypes; | |
| 5171 | 4 | ParamList paramList; | |
| 5172 | 4 | if node.hasParams { | |
| 5173 | 4 | this.visitLambdaParams(baseNode, node.paramLst, paramTypes, paramList); | |
| 5174 | 4 | } | |
| 5175 | |||
| 5176 | // Visit lambda expression | ||
| 5177 | 4 | const ExprResult lambdaExprResult = this.visitExpr(node.lambdaExpr); | |
| 5178 | 4 | const QualType returnType = lambdaExprResult.ty; | |
| 5179 | 4 | if returnType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 5180 | 4 | if returnType.is(SuperType::TY_DYN) { | |
| 5181 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Dyn return types are not allowed")); | |
| 5182 | ✗ | } | |
| 5183 | |||
| 5184 | // Leave function body scope | ||
| 5185 | 4 | scopeHandle.leaveScopeEarly(); | |
| 5186 | |||
| 5187 | // Prepare type of function | ||
| 5188 | 4 | const SuperType superType = returnType.is(SuperType::TY_DYN) ? SuperType::TY_PROCEDURE : SuperType::TY_FUNCTION; | |
| 5189 | 4 | const QualType functionTypeBase = QualType(superType); | |
| 5190 | 4 | const QualType functionTypeWithSignature = functionTypeBase.getWithFunctionParamAndReturnTypes(returnType, paramTypes); | |
| 5191 | 4 | const QualType functionType = functionTypeWithSignature.getWithLambdaCaptures(!bodyScope.symbolTable.captures.isEmpty()); | |
| 5192 | |||
| 5193 | // Create function object | ||
| 5194 | 4 | this.createLambdaManifestation(baseNode, bodyScope, returnType, paramList); | |
| 5195 | |||
| 5196 | 8 | return Any(ExprResult{node.node.node.setEvaluatedSymbolType(functionType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 5197 | 4 | } | |
| 5198 | |||
| 5199 | /** | ||
| 5200 | * Visit the param list of a lambda and collect the param types (shared part of visitLambdaFunc/Proc/Expr) | ||
| 5201 | * | ||
| 5202 | * @param node Lambda node | ||
| 5203 | * @param paramLst Param list node | ||
| 5204 | * @param paramTypes Param types | ||
| 5205 | * @param paramList Params | ||
| 5206 | */ | ||
| 5207 | 94 | p TypeChecker.visitLambdaParams(ASTNode* node, IVisitable* paramLst, QualTypeList& paramTypes, ParamList& paramList) { | |
| 5208 | // Visit param list to retrieve the params | ||
| 5209 | 94 | Any paramLstResult = this.visit(paramLst); | |
| 5210 | 94 | const ParamList visitedParams = paramLstResult.get<ParamList>(); | |
| 5211 | 218 | foreach const Param& param : visitedParams { | |
| 5212 | 124 | if param.isOptional { | |
| 5213 | 1 | this.softError(node, SemanticErrorType::LAMBDA_WITH_OPTIONAL_PARAMS, String("Lambdas cannot have optional parameters")); | |
| 5214 | 1 | } | |
| 5215 | |||
| 5216 | 124 | paramTypes.pushBack(param.qualType); | |
| 5217 | 124 | paramList.pushBack(Param{param.qualType, param.isOptional}); | |
| 5218 | 124 | } | |
| 5219 | 94 | } | |
| 5220 | |||
| 5221 | /** | ||
| 5222 | * Create the function object of a lambda for the current manifestation (shared part of visitLambdaFunc/Proc/Expr) | ||
| 5223 | * | ||
| 5224 | * @param node Lambda node | ||
| 5225 | * @param bodyScope Body scope of the lambda | ||
| 5226 | * @param returnType Return type of the lambda | ||
| 5227 | * @param paramList Params of the lambda | ||
| 5228 | */ | ||
| 5229 | 142 | p TypeChecker.createLambdaManifestation(ASTNode* node, Scope* bodyScope, const QualType& returnType, const ParamList& paramList) { | |
| 5230 | 142 | const String fctName = String("lambda.") + node.codeLoc.toPrettyLineAndColumn(); | |
| 5231 | 142 | ASTLambdaBaseNode* lambdaBaseNode = asLambdaBaseNode(node); | |
| 5232 | 142 | Function& manifestation = lambdaBaseNode.manifestations.get(this.manIdx); | |
| 5233 | 142 | const Vector<GenericType> noTemplateTypes; | |
| 5234 | 142 | manifestation = Function(fctName, nil<SymbolTableEntry*>, QualType(SuperType::TY_DYN), returnType, paramList, noTemplateTypes, node); | |
| 5235 | 142 | manifestation.bodyScope = bodyScope; | |
| 5236 | 142 | manifestation.mangleSuffix = String(".") + toString(cast<long>(this.manIdx)); | |
| 5237 | 142 | } | |
| 5238 | |||
| 5239 | // ====================================================== Builtin functions ======================================================= | ||
| 5240 | |||
| 5241 | /** | ||
| 5242 | * Build the error message for a mismatching number of template types or arguments of a builtin call | ||
| 5243 | * | ||
| 5244 | * @param min Min count | ||
| 5245 | * @param max Max count | ||
| 5246 | * @param actual Actual count | ||
| 5247 | * @param suffix Suffix to describe what is counted | ||
| 5248 | * @return Error message | ||
| 5249 | */ | ||
| 5250 | 27 | f<String> buildBuiltinCountErrorMessage(unsigned int min, unsigned int max, unsigned long actual, string suffix) { | |
| 5251 | 27 | String expectedStr; | |
| 5252 | // The counts are widened to unsigned long, because the int overload of toString would print large counts as negative | ||
| 5253 | 27 | if min == max { | |
| 5254 | 15 | expectedStr = min == 0u ? String("no") : toString(cast<unsigned long>(min)); | |
| 5255 | 15 | } else { | |
| 5256 | 12 | expectedStr = "between " + toString(cast<unsigned long>(min)) + " and " + toString(cast<unsigned long>(max)); | |
| 5257 | 12 | } | |
| 5258 | 54 | return "This builtin expects " + expectedStr + " " + suffix + ", but got " + toString(actual); | |
| 5259 | 27 | } | |
| 5260 | |||
| 5261 | 9908 | f<Any> TypeChecker.visitBuiltinCall(ASTFctCallNode* node) { | |
| 5262 | 9908 | BuiltinFunctionInfo info; | |
| 5263 | 9908 | const bool isBuiltin = lookupBuiltinFunction(node.fqFunctionName, info); | |
| 5264 | 9908 | assert isBuiltin; // Builtin function not implemented | |
| 5265 | 9908 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5266 | |||
| 5267 | // Do basic checks of template types and args, based on the builtin function info | ||
| 5268 | 9908 | const unsigned long numTemplateTypes = node.hasTemplateTypes ? node.templateTypeLst.dataTypes.getSize() : 0l; | |
| 5269 | 9908 | if numTemplateTypes < cast<unsigned long>(info.minTemplateTypes) || numTemplateTypes > cast<unsigned long>(info.maxTemplateTypes) { | |
| 5270 | 12 | const String msg = buildBuiltinCountErrorMessage(info.minTemplateTypes, info.maxTemplateTypes, numTemplateTypes, "template type(s)"); | |
| 5271 | 24 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_TEMPLATE_TYPE_COUNT_MISMATCH, msg); | |
| 5272 | 12 | } | |
| 5273 | |||
| 5274 | 9896 | const unsigned long numArgs = node.hasArgs ? node.argLst.args.getSize() : 0l; | |
| 5275 | 9896 | if numArgs < cast<unsigned long>(info.minArgTypes) || numArgs > cast<unsigned long>(info.maxArgTypes) { | |
| 5276 | 15 | const String msg = buildBuiltinCountErrorMessage(info.minArgTypes, info.maxArgTypes, numArgs, "argument(s)"); | |
| 5277 | 30 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_ARG_COUNT_MISMATCH, msg); | |
| 5278 | 15 | } | |
| 5279 | |||
| 5280 | 9881 | if info.allTemplateTypesOrAllArgTypes { | |
| 5281 | 1017 | if numTemplateTypes > 0l && numArgs > 0l { | |
| 5282 | 8 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_SIGNATURE_MISMATCH, String("This builtin expects either template types or arguments, but got both")); | |
| 5283 | 4 | } | |
| 5284 | 1013 | if numTemplateTypes == 0l && numArgs == 0l { | |
| 5285 | 8 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_SIGNATURE_MISMATCH, String("This builtin expects either template types or arguments, but got none")); | |
| 5286 | 4 | } | |
| 5287 | 1009 | } | |
| 5288 | |||
| 5289 | // Call to TypeChecker delegate to execute further checks. This is a switch, since Spice has no method pointers | ||
| 5290 | 9873 | const String& name = node.fqFunctionName; | |
| 5291 | 9873 | if name == BUILTIN_FCT_NAME_PRINTF { return this.visitBuiltinPrintfCall(node); } | |
| 5292 | 7466 | if name == BUILTIN_FCT_NAME_SIZEOF { return this.visitBuiltinSizeOfCall(node); } | |
| 5293 | 6550 | if name == BUILTIN_FCT_NAME_ALIGNOF { return this.visitBuiltinAlignOfCall(node); } | |
| 5294 | 6515 | if name == BUILTIN_FCT_NAME_OFFSETOF { return this.visitBuiltinOffsetOfCall(node); } | |
| 5295 | 6475 | if name == BUILTIN_FCT_NAME_TYPEID { return this.visitBuiltinTypeIdCall(node); } | |
| 5296 | 6440 | if name == BUILTIN_FCT_NAME_TYPENAME { return this.visitBuiltinTypeNameCall(node); } | |
| 5297 | 6417 | if name == BUILTIN_FCT_NAME_LEN { return this.visitBuiltinLenCall(node); } | |
| 5298 | 6267 | if name == BUILTIN_FCT_NAME_PANIC { return this.visitBuiltinPanicCall(node); } | |
| 5299 | 3234 | if name == BUILTIN_FCT_NAME_SYSCALL { return this.visitBuiltinSyscallCall(node); } | |
| 5300 | 3226 | if name == BUILTIN_FCT_NAME_IS_SAME { return this.visitBuiltinIsSameCall(node); } | |
| 5301 | 3208 | if name == BUILTIN_FCT_NAME_IMPLEMENTS_INTERFACE { return this.visitBuiltinImplementsInterfaceCall(node); } | |
| 5302 | 3205 | if name == BUILTIN_FCT_NAME_GET_BUILD_VAR { return this.visitBuiltinGetBuildVarCall(node); } | |
| 5303 | 3179 | if name == BUILTIN_FCT_NAME_IS_TRIVIALLY_CONSTRUCTIBLE { return this.visitBuiltinIsTriviallyConstructible(node); } | |
| 5304 | 3171 | if name == BUILTIN_FCT_NAME_IS_TRIVIALLY_COPYABLE { return this.visitBuiltinIsTriviallyCopyable(node); } | |
| 5305 | 3157 | if name == BUILTIN_FCT_NAME_IS_TRIVIALLY_DESTRUCTIBLE { return this.visitBuiltinIsTriviallyDestructible(node); } | |
| 5306 | 2819 | if name == BUILTIN_FCT_NAME_IS_HEAP { return this.visitBuiltinIsHeap(node); } | |
| 5307 | 2811 | if name == BUILTIN_FCT_NAME_NEW { return this.visitBuiltinNewCall(node); } | |
| 5308 | 2570 | if name == BUILTIN_FCT_NAME_PLACEMENT_NEW { return this.visitBuiltinPlacementNewCall(node); } | |
| 5309 | 2040 | if name == BUILTIN_FCT_NAME_SOURCE_FILE { return this.visitBuiltinSourceFileCall(node); } | |
| 5310 | 2037 | if name == BUILTIN_FCT_NAME_SOURCE_LINE { return this.visitBuiltinSourceLineCall(node); } | |
| 5311 | 2031 | if name == BUILTIN_FCT_NAME_SOURCE_COLUMN { return this.visitBuiltinSourceColumnCall(node); } | |
| 5312 | 2025 | if name == BUILTIN_FCT_NAME_ERR_TRACE_BUFFER { return this.visitBuiltinErrTraceBufferCall(node); } | |
| 5313 | 1029 | if name == BUILTIN_FCT_NAME_STDERR { return this.visitBuiltinStdErrCall(node); } | |
| 5314 | ✗ | if name == BUILTIN_FCT_NAME_FRAME_ADDRESS { return this.visitBuiltinFrameAddressCall(node); } | |
| 5315 | ✗ | return Any(); | |
| 5316 | } | ||
| 5317 | |||
| 5318 | // Evaluated symbol type of an argument of a function call | ||
| 5319 | 7895 | f<QualType> TypeChecker.getArgType(ASTFctCallNode* node, unsigned long argIdx) { | |
| 5320 | 7895 | ASTExprNode* argNode = asExprNode(getASTNode(node.argLst.args.get(argIdx))); | |
| 5321 | 7895 | return argNode.getEvaluatedSymbolType(this.manIdx); | |
| 5322 | } | ||
| 5323 | |||
| 5324 | // Evaluated symbol type of the template type or (if there is no template type) the argument of a builtin call like sizeof | ||
| 5325 | 1009 | f<QualType> TypeChecker.getTemplateTypeOrArgType(ASTFctCallNode* node) { | |
| 5326 | 1009 | if node.hasTemplateTypes { // Of type | |
| 5327 | 893 | ASTDataTypeNode* dataTypeNode = node.templateTypeLst.dataTypes.front(); | |
| 5328 | 893 | return dataTypeNode.node.getEvaluatedSymbolType(this.manIdx); | |
| 5329 | } | ||
| 5330 | 116 | return this.getArgType(node, 0l); // Of value | |
| 5331 | } | ||
| 5332 | |||
| 5333 | // Evaluated symbol type of the template type with the given index of a function call | ||
| 5334 | 1207 | f<QualType> TypeChecker.getTemplateType(ASTFctCallNode* node, unsigned long templateTypeIdx) { | |
| 5335 | 1207 | ASTDataTypeNode* dataTypeNode = node.templateTypeLst.dataTypes.get(templateTypeIdx); | |
| 5336 | 1207 | return dataTypeNode.node.getEvaluatedSymbolType(this.manIdx); | |
| 5337 | } | ||
| 5338 | |||
| 5339 | // Set the evaluated symbol type of a builtin call and return it as expression result | ||
| 5340 | 9837 | f<Any> TypeChecker.builtinResult(ASTFctCallNode* node, const QualType& qualType) { | |
| 5341 | 9837 | return Any(ExprResult{node.node.setEvaluatedSymbolType(qualType, this.manIdx), nil<SymbolTableEntry*>}); | |
| 5342 | } | ||
| 5343 | |||
| 5344 | // Set the compile time value of a function call for the current manifestation | ||
| 5345 | 1495 | p TypeChecker.setCompileTimeValue(ASTFctCallNode* node, const CompileTimeValue& value) { | |
| 5346 | 1495 | FctCallData& data = node.data.get(this.manIdx); | |
| 5347 | 1495 | data.setCompileTimeValue(value); | |
| 5348 | 1495 | } | |
| 5349 | |||
| 5350 | // Whether the given char is a printf flag | ||
| 5351 | 2119 | f<bool> isPrintfFlag(char c) { | |
| 5352 | 2119 | return c == '-' || c == '+' || c == ' ' || c == '#' || c == '0'; | |
| 5353 | } | ||
| 5354 | |||
| 5355 | // Whether the given char is a decimal digit | ||
| 5356 | 2145 | f<bool> isDecimalDigit(char c) { | |
| 5357 | 2145 | return c >= '0' && c <= '9'; | |
| 5358 | } | ||
| 5359 | |||
| 5360 | 2407 | f<Any> TypeChecker.visitBuiltinPrintfCall(ASTFctCallNode* node) { | |
| 5361 | 2407 | assert node.fqFunctionName == BUILTIN_FCT_NAME_PRINTF; | |
| 5362 | 2407 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5363 | |||
| 5364 | // Retrieve templated string | ||
| 5365 | 2407 | assert node.hasArgs; | |
| 5366 | 2407 | ASTNode* firstArg = getASTNode(node.argLst.args.front()); | |
| 5367 | 2407 | const QualType firstArgType = this.getArgType(node, 0l); | |
| 5368 | 2407 | assert firstArgType.is(SuperType::TY_STRING) && firstArg.hasCompileTimeValue(this.manIdx); | |
| 5369 | 2407 | const CompileTimeValue firstArgValue = firstArg.getCompileTimeValue(this.manIdx); | |
| 5370 | 2407 | const unsigned long stringOffset = firstArgValue.getStringValueOffset(); | |
| 5371 | 2407 | String templatedString = String(this.resourceManager.getCompileTimeStringValue(stringOffset)); | |
| 5372 | 2407 | const long templatedStringSize = cast<long>(templatedString.getLength()); | |
| 5373 | 2407 | const unsigned long argCount = node.argLst.args.getSize(); | |
| 5374 | |||
| 5375 | // Check if assignment types match placeholder types | ||
| 5376 | 2407 | unsigned long placeholderCount = 0l; | |
| 5377 | 2407 | long index = templatedString.find('%'); | |
| 5378 | 4504 | while index != -1l && index != templatedStringSize - 1l { | |
| 5379 | // Handle escaped percent signs (%%), which do not consume an argument | ||
| 5380 | 2106 | if templatedString[cast<unsigned long>(index + 1l)] == '%' { | |
| 5381 | ✗ | index = templatedString.find('%', cast<unsigned long>(index + 2l)); | |
| 5382 | ✗ | continue; | |
| 5383 | } | ||
| 5384 | |||
| 5385 | // Determine the position of the conversion specifier by skipping the optional | ||
| 5386 | // flags, field width and precision (e.g. the "04" in "%04d"). These are forwarded | ||
| 5387 | // verbatim to the underlying C printf, so we only need to skip over them here. | ||
| 5388 | 2106 | long specifierIndex = index + 1l; | |
| 5389 | 2119 | while specifierIndex < templatedStringSize && isPrintfFlag(templatedString[cast<unsigned long>(specifierIndex)]) { | |
| 5390 | 13 | specifierIndex++; // Skip flags | |
| 5391 | 13 | } | |
| 5392 | 2117 | while specifierIndex < templatedStringSize && isDecimalDigit(templatedString[cast<unsigned long>(specifierIndex)]) { | |
| 5393 | 11 | specifierIndex++; // Skip field width | |
| 5394 | 11 | } | |
| 5395 | 2106 | if specifierIndex < templatedStringSize && templatedString[cast<unsigned long>(specifierIndex)] == '.' { | |
| 5396 | 10 | specifierIndex++; // Skip precision dot | |
| 5397 | 20 | while specifierIndex < templatedStringSize && isDecimalDigit(templatedString[cast<unsigned long>(specifierIndex)]) { | |
| 5398 | 10 | specifierIndex++; // Skip precision | |
| 5399 | 10 | } | |
| 5400 | 10 | } | |
| 5401 | 2106 | if specifierIndex >= templatedStringSize { | |
| 5402 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::PRINTF_TYPE_ERROR, String("The placeholder string contains an invalid placeholder")); | |
| 5403 | ✗ | } | |
| 5404 | |||
| 5405 | // Check if there is another assignExpr | ||
| 5406 | 2106 | if argCount - 1l <= placeholderCount { | |
| 5407 | 2 | return this.softErrorER(baseNode, SemanticErrorType::PRINTF_ARG_COUNT_ERROR, String("The placeholder string contains more placeholders than arguments")); | |
| 5408 | 1 | } | |
| 5409 | |||
| 5410 | // Get next assignment | ||
| 5411 | 2105 | const ASTNode* assignment = getASTNode(node.argLst.args.get(placeholderCount + 1l)); | |
| 5412 | // Visit assignment | ||
| 5413 | 2105 | QualType argType = this.getArgType(node, placeholderCount + 1l); | |
| 5414 | 2105 | if argType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 5415 | 2105 | argType = argType.removeReferenceWrapper(); | |
| 5416 | |||
| 5417 | 2105 | const char specifier = templatedString[cast<unsigned long>(specifierIndex)]; | |
| 5418 | 2105 | switch specifier { | |
| 5419 | case 'c': { | ||
| 5420 | 66 | if !argType.is(SuperType::TY_CHAR) { | |
| 5421 | 2 | return this.softErrorER(assignment, SemanticErrorType::PRINTF_TYPE_ERROR, "The placeholder string expects char, but got " + argType.getName(false)); | |
| 5422 | 1 | } | |
| 5423 | 65 | placeholderCount++; | |
| 5424 | 65 | } | |
| 5425 | case 'd', 'i', 'l', 'o', 'u', 'x', 'X': { | ||
| 5426 | 1381 | const dyn intLikeTypes = [SuperType::TY_INT, SuperType::TY_SHORT, SuperType::TY_LONG, SuperType::TY_BYTE, SuperType::TY_BOOL]; | |
| 5427 | 1381 | if !argType.isOneOf(intLikeTypes, len(intLikeTypes)) { | |
| 5428 | 4 | return this.softErrorER(assignment, SemanticErrorType::PRINTF_TYPE_ERROR, "The placeholder string expects int, short, long, byte or bool, but got " + argType.getName(false)); | |
| 5429 | 2 | } | |
| 5430 | 1379 | placeholderCount++; | |
| 5431 | 1379 | } | |
| 5432 | case 'a', 'A', 'f', 'F', 'e', 'E', 'g', 'G': { | ||
| 5433 | 147 | if !argType.is(SuperType::TY_DOUBLE) { | |
| 5434 | 2 | return this.softErrorER(assignment, SemanticErrorType::PRINTF_TYPE_ERROR, "The placeholder string expects double, but got " + argType.getName(false)); | |
| 5435 | 1 | } | |
| 5436 | 146 | placeholderCount++; | |
| 5437 | 146 | } | |
| 5438 | case 's': { | ||
| 5439 | 500 | if !argType.is(SuperType::TY_STRING) && !argType.isStringObj() && !argType.isPtrTo(SuperType::TY_CHAR) && !argType.isArrayOf(SuperType::TY_CHAR) { | |
| 5440 | 2 | return this.softErrorER(assignment, SemanticErrorType::PRINTF_TYPE_ERROR, "The placeholder string expects string, String, char* or char[], but got " + argType.getName(false)); | |
| 5441 | 1 | } | |
| 5442 | 499 | placeholderCount++; | |
| 5443 | 499 | } | |
| 5444 | case 'p': { | ||
| 5445 | 9 | if !argType.isPtr() && !argType.isArray() && !argType.is(SuperType::TY_STRING) { | |
| 5446 | 2 | return this.softErrorER(assignment, SemanticErrorType::PRINTF_TYPE_ERROR, "The placeholder string expects pointer, array or string, but got " + argType.getName(false)); | |
| 5447 | 1 | } | |
| 5448 | 8 | placeholderCount++; | |
| 5449 | 8 | } | |
| 5450 | default: { | ||
| 5451 | 4 | return this.softErrorER(baseNode, SemanticErrorType::PRINTF_TYPE_ERROR, String("The placeholder string contains an invalid placeholder")); | |
| 5452 | 2 | } | |
| 5453 | } | ||
| 5454 | 2097 | index = templatedString.find('%', cast<unsigned long>(specifierIndex + 1l)); // We can also skip the conversion specifier | |
| 5455 | 2097 | } | |
| 5456 | |||
| 5457 | // Check if the number of placeholders matches the number of args | ||
| 5458 | 2398 | if placeholderCount < argCount - 1l { | |
| 5459 | 2 | return this.softErrorER(baseNode, SemanticErrorType::PRINTF_ARG_COUNT_ERROR, String("The placeholder string contains less placeholders than arguments")); | |
| 5460 | 1 | } | |
| 5461 | |||
| 5462 | 4794 | return this.builtinResult(node, QualType(SuperType::TY_INT)); | |
| 5463 | 2407 | } | |
| 5464 | |||
| 5465 | 916 | f<Any> TypeChecker.visitBuiltinSizeOfCall(ASTFctCallNode* node) { | |
| 5466 | 916 | assert node.fqFunctionName == BUILTIN_FCT_NAME_SIZEOF; | |
| 5467 | |||
| 5468 | // Directly set compile time value here, so that compile time ifs can be evaluated. | ||
| 5469 | 916 | const QualType qualType = this.getTemplateTypeOrArgType(node); | |
| 5470 | |||
| 5471 | 916 | const dyn dynOrUnresolved = [SuperType::TY_UNRESOLVED, SuperType::TY_DYN]; | |
| 5472 | 916 | if qualType.isOneOf(dynOrUnresolved, len(dynOrUnresolved)) { | |
| 5473 | 4 | return this.softErrorER(cast<ASTNode*>(node), SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Cannot use sizeof on a dyn or unresolved type")); | |
| 5474 | 2 | } | |
| 5475 | |||
| 5476 | 914 | const Type* rawType = qualType.getType(); | |
| 5477 | 914 | CompileTimeValue compileTimeValue; | |
| 5478 | 914 | compileTimeValue.setLongValue(cast<long>(rawType.getTypeAllocSize(this.sourceFile))); | |
| 5479 | 914 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5480 | |||
| 5481 | 914 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5482 | } | ||
| 5483 | |||
| 5484 | 35 | f<Any> TypeChecker.visitBuiltinAlignOfCall(ASTFctCallNode* node) { | |
| 5485 | 35 | assert node.fqFunctionName == BUILTIN_FCT_NAME_ALIGNOF; | |
| 5486 | |||
| 5487 | // Directly set compile time value here, so that compile time ifs can be evaluated. | ||
| 5488 | 35 | const QualType qualType = this.getTemplateTypeOrArgType(node); | |
| 5489 | |||
| 5490 | 35 | const dyn dynOrUnresolved = [SuperType::TY_UNRESOLVED, SuperType::TY_DYN]; | |
| 5491 | 35 | if qualType.isOneOf(dynOrUnresolved, len(dynOrUnresolved)) { | |
| 5492 | 4 | return this.softErrorER(cast<ASTNode*>(node), SemanticErrorType::UNEXPECTED_DYN_TYPE, String("Cannot use alignof on a dyn or unresolved type")); | |
| 5493 | 2 | } | |
| 5494 | |||
| 5495 | 33 | const Type* rawType = qualType.getType(); | |
| 5496 | 33 | CompileTimeValue compileTimeValue; | |
| 5497 | 33 | compileTimeValue.setLongValue(cast<long>(rawType.getABITypeAlign(this.sourceFile))); | |
| 5498 | 33 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5499 | |||
| 5500 | 33 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5501 | } | ||
| 5502 | |||
| 5503 | 40 | f<Any> TypeChecker.visitBuiltinOffsetOfCall(ASTFctCallNode* node) { | |
| 5504 | 40 | assert node.fqFunctionName == BUILTIN_FCT_NAME_OFFSETOF; | |
| 5505 | 40 | assert node.hasArgs && node.argLst.args.getSize() == 2l; | |
| 5506 | |||
| 5507 | // The first argument denotes the struct to compute the offset within | ||
| 5508 | 40 | const ASTNode* structArg = getASTNode(node.argLst.args.get(0l)); | |
| 5509 | 40 | QualType structType = this.getArgType(node, 0l); | |
| 5510 | 40 | structType = structType.removeReferenceWrapper(); | |
| 5511 | 40 | structType = structType.autoDereference(); | |
| 5512 | 40 | if !structType.is(SuperType::TY_STRUCT) { | |
| 5513 | 2 | return this.softErrorER(structArg, SemanticErrorType::EXPECTED_STRUCT_TYPE, String("The offsetof builtin expects a struct as its first argument")); | |
| 5514 | 1 | } | |
| 5515 | |||
| 5516 | // The second argument must be a (possibly chained) member access, denoting the field to compute the offset of | ||
| 5517 | 39 | ASTNode* memberArg = getASTNode(node.argLst.args.get(1l)); | |
| 5518 | 39 | ASTPostfixUnaryExprNode* leafAccess = nil<ASTPostfixUnaryExprNode*>; | |
| 5519 | 39 | ASTNode* cur = memberArg; | |
| 5520 | 40 | while cur != nil<ASTNode*> { | |
| 5521 | 39 | if cur.kind == ASTNodeKind::POSTFIX_UNARY_EXPR { | |
| 5522 | 38 | leafAccess = downcastMut<ASTPostfixUnaryExprNode>(cur); | |
| 5523 | 38 | break; | |
| 5524 | } | ||
| 5525 | 1 | const Vector<ASTNode*>& children = cur.getChildren(); | |
| 5526 | 1 | cur = children.getSize() == 1l ? children.front() : nil<ASTNode*>; | |
| 5527 | 1 | } | |
| 5528 | 39 | if leafAccess == nil<ASTPostfixUnaryExprNode*> || leafAccess.op != PostfixUnaryOp::OP_MEMBER_ACCESS { | |
| 5529 | 2 | return this.softErrorER(memberArg, SemanticErrorType::INVALID_MEMBER_ACCESS, String("The offsetof builtin expects a struct member access as its second argument")); | |
| 5530 | 1 | } | |
| 5531 | |||
| 5532 | // Collect the chain of member accesses (from leaf to base). Only plain member accesses are supported. | ||
| 5533 | 38 | Vector<ASTPostfixUnaryExprNode*> accessChain; | |
| 5534 | 38 | ASTPostfixUnaryExprNode* access = leafAccess; | |
| 5535 | 84 | while access != nil<ASTPostfixUnaryExprNode*> { | |
| 5536 | 47 | if access.op != PostfixUnaryOp::OP_MEMBER_ACCESS { | |
| 5537 | 2 | return this.softErrorER(memberArg, SemanticErrorType::INVALID_MEMBER_ACCESS, String("The offsetof builtin only supports plain member accesses")); | |
| 5538 | 1 | } | |
| 5539 | 46 | accessChain.pushBack(access); | |
| 5540 | // Continue with the operand of the member access, if it is a postfix unary expression itself | ||
| 5541 | 46 | access = nil<ASTPostfixUnaryExprNode*>; | |
| 5542 | 46 | IVisitable* operand = accessChain.back().postfixUnaryExpr; | |
| 5543 | 46 | if operand != nil<IVisitable*> && getASTNode(operand).kind == ASTNodeKind::POSTFIX_UNARY_EXPR { | |
| 5544 | 9 | access = downcast<ASTPostfixUnaryExprNode>(operand); | |
| 5545 | 9 | } | |
| 5546 | 46 | } | |
| 5547 | |||
| 5548 | // Ensure the base of the member access chain is the struct that was passed as first argument | ||
| 5549 | 37 | QualType chainBaseType = this.getOperandType(accessChain.back()); | |
| 5550 | 37 | chainBaseType = chainBaseType.removeReferenceWrapper(); | |
| 5551 | 37 | chainBaseType = chainBaseType.autoDereference(); | |
| 5552 | 37 | if !chainBaseType.matches(structType, false, true, false) { | |
| 5553 | 2 | return this.softErrorER(memberArg, SemanticErrorType::INVALID_MEMBER_ACCESS, String("The member access must be rooted in the struct passed as first argument")); | |
| 5554 | 1 | } | |
| 5555 | |||
| 5556 | // Accumulate the byte offset by walking each access in the chain through its struct layout | ||
| 5557 | 36 | long offset = 0l; | |
| 5558 | 80 | foreach ASTPostfixUnaryExprNode* chainAccess : accessChain { | |
| 5559 | 44 | QualType baseType = this.getOperandType(chainAccess); | |
| 5560 | 44 | baseType = baseType.removeReferenceWrapper(); | |
| 5561 | 44 | baseType = baseType.autoDereference(); | |
| 5562 | 44 | assert baseType.is(SuperType::TY_STRUCT); | |
| 5563 | |||
| 5564 | // Resolve the struct manifestation | ||
| 5565 | 44 | Struct* spiceStruct = baseType.getStruct(cast<ASTNode*>(node)); | |
| 5566 | 44 | assert spiceStruct != nil<Struct*> && !spiceStruct.scope.isGenericScope; | |
| 5567 | |||
| 5568 | // Look up the accessed field to retrieve its index path within the LLVM struct type | ||
| 5569 | 44 | Vector<unsigned long> indexPath; | |
| 5570 | 44 | const SymbolTableEntry* fieldEntry = spiceStruct.lookupInComposedFields(chainAccess.identifier, indexPath); | |
| 5571 | 44 | if fieldEntry == nil<SymbolTableEntry*> { | |
| 5572 | ✗ | return this.softErrorER(memberArg, SemanticErrorType::REFERENCED_UNDEFINED_FIELD, "Field '" + chainAccess.identifier + "' not found in struct " + baseType.getSubType()); | |
| 5573 | ✗ | } | |
| 5574 | |||
| 5575 | // Walk the index path through the struct layout to accumulate the byte offset of the field | ||
| 5576 | 44 | const Type* baseRawType = baseType.getType(); | |
| 5577 | 44 | offset += cast<long>(baseRawType.getFieldOffset(this.sourceFile, indexPath)); | |
| 5578 | 44 | } | |
| 5579 | 36 | CompileTimeValue compileTimeValue; | |
| 5580 | 36 | compileTimeValue.setLongValue(offset); | |
| 5581 | 36 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5582 | |||
| 5583 | 72 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5584 | 38 | } | |
| 5585 | |||
| 5586 | // Evaluated symbol type of the operand of a postfix unary expression | ||
| 5587 | 81 | f<QualType> TypeChecker.getOperandType(ASTPostfixUnaryExprNode* node) { | |
| 5588 | 81 | ASTExprNode* operandNode = asExprNode(getASTNode(node.postfixUnaryExpr)); | |
| 5589 | 81 | return operandNode.getEvaluatedSymbolType(this.manIdx); | |
| 5590 | } | ||
| 5591 | |||
| 5592 | 35 | f<Any> TypeChecker.visitBuiltinTypeIdCall(ASTFctCallNode* node) { | |
| 5593 | 35 | assert node.fqFunctionName == BUILTIN_FCT_NAME_TYPEID; | |
| 5594 | |||
| 5595 | // Directly set compile time value here, so that compile time ifs can be evaluated. | ||
| 5596 | 35 | const QualType qualType = this.getTemplateTypeOrArgType(node); | |
| 5597 | 35 | const Hash typeId = this.resourceManager.typeRegistry.getTypeHash(*qualType.getType()); | |
| 5598 | 35 | CompileTimeValue compileTimeValue; | |
| 5599 | 35 | compileTimeValue.setLongValue(cast<long>(typeId)); | |
| 5600 | 35 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5601 | |||
| 5602 | 35 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5603 | } | ||
| 5604 | |||
| 5605 | 23 | f<Any> TypeChecker.visitBuiltinTypeNameCall(ASTFctCallNode* node) { | |
| 5606 | 23 | assert node.fqFunctionName == BUILTIN_FCT_NAME_TYPENAME; | |
| 5607 | |||
| 5608 | // Directly set compile time value here, so that compile time ifs can be evaluated. | ||
| 5609 | 23 | const QualType qualType = this.getTemplateTypeOrArgType(node); | |
| 5610 | 23 | CompileTimeValue compileTimeValue; | |
| 5611 | 23 | compileTimeValue.setStringValueOffset(this.resourceManager.compileTimeStringValues.getSize()); | |
| 5612 | 23 | this.resourceManager.compileTimeStringValues.pushBack(qualType.getName()); | |
| 5613 | 23 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5614 | |||
| 5615 | 46 | return this.builtinResult(node, QualType(SuperType::TY_STRING)); | |
| 5616 | 23 | } | |
| 5617 | |||
| 5618 | 150 | f<Any> TypeChecker.visitBuiltinLenCall(ASTFctCallNode* node) { | |
| 5619 | 150 | assert node.fqFunctionName == BUILTIN_FCT_NAME_LEN; | |
| 5620 | |||
| 5621 | // Directly set compile time value here, so that compile time ifs can be evaluated. | ||
| 5622 | 150 | QualType argType = this.getArgType(node, 0l); | |
| 5623 | 150 | argType = argType.removeReferenceWrapper(); | |
| 5624 | |||
| 5625 | // Check if arg is of type array | ||
| 5626 | 150 | if !argType.isArray() && !argType.is(SuperType::TY_STRING) { | |
| 5627 | 2 | return this.softErrorER(getASTNode(node.argLst.args.front()), SemanticErrorType::EXPECTED_ARRAY_TYPE, String("The len builtin can only work on arrays or strings")); | |
| 5628 | 1 | } | |
| 5629 | |||
| 5630 | 149 | if argType.is(SuperType::TY_ARRAY) { | |
| 5631 | 50 | CompileTimeValue compileTimeValue; | |
| 5632 | 50 | compileTimeValue.setLongValue(cast<long>(argType.getArraySize())); | |
| 5633 | 50 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5634 | 50 | } else { | |
| 5635 | // If we want to use the len builtin on a string, we need to import the string runtime module | ||
| 5636 | 99 | if !this.sourceFile.isStringRT() { | |
| 5637 | 98 | this.sourceFile.requestRuntimeModule(RuntimeModule::STRING_RT); | |
| 5638 | 98 | } | |
| 5639 | 99 | } | |
| 5640 | |||
| 5641 | 149 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5642 | } | ||
| 5643 | |||
| 5644 | 3033 | f<Any> TypeChecker.visitBuiltinPanicCall(ASTFctCallNode* node) { | |
| 5645 | 3033 | assert node.fqFunctionName == BUILTIN_FCT_NAME_PANIC; | |
| 5646 | |||
| 5647 | 3033 | assert node.hasArgs; | |
| 5648 | 3033 | const ASTNode* assignExpr = getASTNode(node.argLst.args.front()); | |
| 5649 | 3033 | QualType argType = this.getArgType(node, 0l); | |
| 5650 | 3033 | if argType.is(SuperType::TY_UNRESOLVED) { return unresolvedER(); } | |
| 5651 | 3033 | argType = argType.removeReferenceWrapper(); | |
| 5652 | |||
| 5653 | // Check if arg is of type array | ||
| 5654 | 3033 | if !argType.isErrorObj() { | |
| 5655 | 2 | return this.softErrorER(assignExpr, SemanticErrorType::EXPECTED_ERROR_TYPE, String("The panic builtin can only work with errors")); | |
| 5656 | 1 | } | |
| 5657 | |||
| 5658 | // The panic prints the stack trace, so the runtime that does that has to be loaded | ||
| 5659 | 3032 | if this.cliOptions.printsStackTraceOnAbort() && !this.sourceFile.isRT(RuntimeModule::STACK_TRACE_RT) { | |
| 5660 | 11 | this.sourceFile.requestRuntimeModule(RuntimeModule::STACK_TRACE_RT); | |
| 5661 | 11 | } | |
| 5662 | |||
| 5663 | 3032 | return this.builtinResult(node, QualType(SuperType::TY_DYN)); | |
| 5664 | } | ||
| 5665 | |||
| 5666 | 8 | f<Any> TypeChecker.visitBuiltinSyscallCall(ASTFctCallNode* node) { | |
| 5667 | 8 | assert node.fqFunctionName == BUILTIN_FCT_NAME_SYSCALL; | |
| 5668 | |||
| 5669 | // Check if the syscall number if of type short | ||
| 5670 | 8 | ASTNode* sysCallNumberExpr = getASTNode(node.argLst.args.front()); | |
| 5671 | 8 | const QualType sysCallNumberType = this.getArgType(node, 0l); | |
| 5672 | 8 | if !sysCallNumberType.is(SuperType::TY_SHORT) { | |
| 5673 | 2 | return this.softErrorER(sysCallNumberExpr, SemanticErrorType::INVALID_SYSCALL_NUMBER_TYPE, String("Syscall number must be of type short")); | |
| 5674 | 1 | } | |
| 5675 | |||
| 5676 | // Check if the syscall number is out of range | ||
| 5677 | // According to https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/ | ||
| 5678 | 7 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5679 | 7 | if sysCallNumberExpr.hasCompileTimeValue(this.manIdx) { | |
| 5680 | 2 | const CompileTimeValue sysCallNumberValue = sysCallNumberExpr.getCompileTimeValue(this.manIdx); | |
| 5681 | 2 | const short sysCallNumber = sysCallNumberValue.getShortValue(); | |
| 5682 | 2 | if sysCallNumber < 0s || sysCallNumber > 439s { | |
| 5683 | 4 | return this.softErrorER(baseNode, SemanticErrorType::SYSCALL_NUMBER_OUT_OF_RANGE, String("Only syscall numbers between 0 and 439 are supported")); | |
| 5684 | 2 | } | |
| 5685 | ✗ | } | |
| 5686 | |||
| 5687 | 5 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5688 | } | ||
| 5689 | |||
| 5690 | 18 | f<Any> TypeChecker.visitBuiltinIsSameCall(ASTFctCallNode* node) { | |
| 5691 | 18 | assert node.fqFunctionName == BUILTIN_FCT_NAME_IS_SAME; | |
| 5692 | |||
| 5693 | // Directly set compile time value here, so that compile time ifs can be evaluated. | ||
| 5694 | 18 | CompileTimeValue compileTimeValue; | |
| 5695 | 18 | compileTimeValue.setBoolValue(true); | |
| 5696 | 18 | const QualType firstType = this.getTemplateType(node, 0l); | |
| 5697 | 24 | for unsigned long i = 1l; i < node.templateTypeLst.dataTypes.getSize(); i++ { | |
| 5698 | 18 | const QualType qualType = this.getTemplateType(node, i); | |
| 5699 | 18 | if !qualType.matches(firstType, false, true, false) { | |
| 5700 | 12 | compileTimeValue.setBoolValue(false); | |
| 5701 | 12 | break; | |
| 5702 | } | ||
| 5703 | 6 | } | |
| 5704 | 18 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5705 | |||
| 5706 | 18 | return this.builtinResult(node, QualType(SuperType::TY_BOOL)); | |
| 5707 | } | ||
| 5708 | |||
| 5709 | 3 | f<Any> TypeChecker.visitBuiltinImplementsInterfaceCall(ASTFctCallNode* node) { | |
| 5710 | 3 | assert node.fqFunctionName == BUILTIN_FCT_NAME_IMPLEMENTS_INTERFACE; | |
| 5711 | |||
| 5712 | 3 | const QualType interfaceType = this.getTemplateType(node, 0l); | |
| 5713 | 3 | const QualType structType = this.getTemplateType(node, node.templateTypeLst.dataTypes.getSize() - 1l); | |
| 5714 | 3 | CompileTimeValue compileTimeValue; | |
| 5715 | 3 | compileTimeValue.setBoolValue(interfaceType.is(SuperType::TY_INTERFACE) && structType.is(SuperType::TY_STRUCT) && structType.doesImplement(interfaceType, cast<ASTNode*>(node))); | |
| 5716 | 3 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5717 | |||
| 5718 | 3 | return this.builtinResult(node, QualType(SuperType::TY_BOOL)); | |
| 5719 | } | ||
| 5720 | |||
| 5721 | /** | ||
| 5722 | * Parse the given string to an int with the semantics of std::stoi: Leading whitespace and trailing garbage are ignored, but | ||
| 5723 | * at least one digit has to be present and the value has to fit into an int. | ||
| 5724 | * | ||
| 5725 | * @param input Input string | ||
| 5726 | * @param output Parsed int value | ||
| 5727 | * @return Successfully parsed or not | ||
| 5728 | */ | ||
| 5729 | 5 | f<bool> parseIntLikeStoi(const String& input, int& output) { | |
| 5730 | 5 | const unsigned long length = input.getLength(); | |
| 5731 | 5 | unsigned long idx = 0l; | |
| 5732 | 5 | char c = '\0'; | |
| 5733 | // Skip leading whitespace | ||
| 5734 | 5 | while idx < length { | |
| 5735 | 5 | c = input[idx]; | |
| 5736 | 5 | if c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '\f' && c != '\v' { break; } | |
| 5737 | ✗ | idx++; | |
| 5738 | ✗ | } | |
| 5739 | // Sign | ||
| 5740 | 5 | bool isNegative = false; | |
| 5741 | 5 | if idx < length { | |
| 5742 | 5 | c = input[idx]; | |
| 5743 | 5 | if c == '+' || c == '-' { | |
| 5744 | ✗ | isNegative = c == '-'; | |
| 5745 | ✗ | idx++; | |
| 5746 | ✗ | } | |
| 5747 | 5 | } | |
| 5748 | // Digits | ||
| 5749 | 5 | long value = 0l; | |
| 5750 | 5 | unsigned long digitCount = 0l; | |
| 5751 | 11 | while idx < length { | |
| 5752 | 8 | c = input[idx]; | |
| 5753 | 8 | if !isDecimalDigit(c) { break; } | |
| 5754 | 6 | value = value * 10l + cast<long>(toInt(c) - toInt('0')); | |
| 5755 | 6 | if value > 2147483648l { return false; } // Out of range | |
| 5756 | 6 | digitCount++; | |
| 5757 | 6 | idx++; | |
| 5758 | 6 | } | |
| 5759 | 5 | if digitCount == 0l { return false; } // Invalid argument | |
| 5760 | 3 | if isNegative { value = -value; } | |
| 5761 | 3 | if value > 2147483647l { return false; } // Out of range | |
| 5762 | 3 | output = cast<int>(value); | |
| 5763 | 3 | return true; | |
| 5764 | } | ||
| 5765 | |||
| 5766 | 26 | f<Any> TypeChecker.visitBuiltinGetBuildVarCall(ASTFctCallNode* node) { | |
| 5767 | 26 | assert node.fqFunctionName == BUILTIN_FCT_NAME_GET_BUILD_VAR; | |
| 5768 | 26 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5769 | |||
| 5770 | 26 | const ASTNode* requestedTypeNode = cast<ASTNode*>(node.templateTypeLst.dataTypes.front()); | |
| 5771 | 26 | const QualType requestedType = this.getTemplateType(node, 0l); | |
| 5772 | 26 | const dyn buildVarTypes = [SuperType::TY_STRING, SuperType::TY_INT, SuperType::TY_BOOL]; | |
| 5773 | 26 | if !requestedType.isOneOf(buildVarTypes, len(buildVarTypes)) { | |
| 5774 | 2 | return this.softErrorER(requestedTypeNode, SemanticErrorType::BUILTIN_SIGNATURE_MISMATCH, String("Build var type must be string, int or bool")); | |
| 5775 | 1 | } | |
| 5776 | |||
| 5777 | 25 | ASTNode* varNameNode = getASTNode(node.argLst.args.get(0l)); | |
| 5778 | 25 | const QualType varNameType = this.getArgType(node, 0l); | |
| 5779 | 25 | if !varNameType.is(SuperType::TY_STRING) { | |
| 5780 | 2 | return this.softErrorER(varNameNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, String("Build var name must be a string")); | |
| 5781 | 1 | } | |
| 5782 | 24 | if !varNameNode.hasCompileTimeValue(this.manIdx) { | |
| 5783 | 2 | return this.softErrorER(varNameNode, SemanticErrorType::EXPECTED_COMPILE_TIME_VALUE, String("Var name must be known at compile time")); | |
| 5784 | 1 | } | |
| 5785 | |||
| 5786 | 23 | const bool hasDefaultValue = node.argLst.args.getSize() == 2l; | |
| 5787 | 23 | CompileTimeValue defaultValue; | |
| 5788 | 23 | if hasDefaultValue { | |
| 5789 | 11 | ASTNode* defaultValueNode = getASTNode(node.argLst.args.get(1l)); | |
| 5790 | 11 | const QualType defaultValueType = this.getArgType(node, 1l); | |
| 5791 | 11 | if !defaultValueType.matches(requestedType, false, true, true) { | |
| 5792 | 2 | return this.softErrorER(defaultValueNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, String("Default value type must be the same as the requested type")); | |
| 5793 | 1 | } | |
| 5794 | 10 | if !defaultValueNode.hasCompileTimeValue(this.manIdx) { | |
| 5795 | 2 | return this.softErrorER(defaultValueNode, SemanticErrorType::EXPECTED_COMPILE_TIME_VALUE, String("Default value must be known at compile time")); | |
| 5796 | 1 | } | |
| 5797 | 9 | defaultValue = defaultValueNode.getCompileTimeValue(this.manIdx); | |
| 5798 | 9 | } | |
| 5799 | |||
| 5800 | 21 | FctCallData& data = node.data.get(this.manIdx); | |
| 5801 | 21 | const CompileTimeValue varNameValue = varNameNode.getCompileTimeValue(this.manIdx); | |
| 5802 | 21 | const unsigned long stringValueOffset = varNameValue.getStringValueOffset(); | |
| 5803 | 21 | const String varName = String(this.resourceManager.getCompileTimeStringValue(stringValueOffset)); | |
| 5804 | 21 | if this.cliOptions.buildVars.contains(varName) { | |
| 5805 | 17 | const String buildVarValue = String(this.cliOptions.buildVars.get(varName)); | |
| 5806 | 17 | CompileTimeValue compileTimeValue; | |
| 5807 | 17 | if requestedType.is(SuperType::TY_STRING) { | |
| 5808 | 9 | compileTimeValue.setStringValueOffset(this.resourceManager.compileTimeStringValues.getSize()); | |
| 5809 | 9 | this.resourceManager.compileTimeStringValues.pushBack(buildVarValue); | |
| 5810 | 17 | } else if requestedType.is(SuperType::TY_INT) { | |
| 5811 | // Invalid values are rejected | ||
| 5812 | 5 | int value = 0; | |
| 5813 | 5 | if !parseIntLikeStoi(buildVarValue, value) { | |
| 5814 | 4 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, String("Error while parsing the provided value to the requested type")); | |
| 5815 | 2 | } | |
| 5816 | 3 | compileTimeValue.setIntValue(value); | |
| 5817 | 6 | } else if requestedType.is(SuperType::TY_BOOL) { | |
| 5818 | 3 | compileTimeValue.setBoolValue(buildVarValue == "true"); | |
| 5819 | 6 | } | |
| 5820 | 15 | data.setCompileTimeValue(compileTimeValue); | |
| 5821 | 17 | } else { | |
| 5822 | 4 | if !hasDefaultValue { | |
| 5823 | 2 | return this.softErrorER(varNameNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, String("Build var with this name was not provided")); | |
| 5824 | 1 | } | |
| 5825 | 3 | data.setCompileTimeValue(defaultValue); | |
| 5826 | 3 | } | |
| 5827 | |||
| 5828 | 36 | return this.builtinResult(node, requestedType); | |
| 5829 | 21 | } | |
| 5830 | |||
| 5831 | 8 | f<Any> TypeChecker.visitBuiltinIsTriviallyConstructible(ASTFctCallNode* node) { | |
| 5832 | 8 | assert node.fqFunctionName == BUILTIN_FCT_NAME_IS_TRIVIALLY_CONSTRUCTIBLE; | |
| 5833 | |||
| 5834 | 8 | const QualType qualType = this.getTemplateType(node, 0l); | |
| 5835 | 8 | CompileTimeValue compileTimeValue; | |
| 5836 | 8 | compileTimeValue.setBoolValue(qualType.isTriviallyConstructible(cast<ASTNode*>(node))); | |
| 5837 | 8 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5838 | |||
| 5839 | 8 | return this.builtinResult(node, QualType(SuperType::TY_BOOL)); | |
| 5840 | } | ||
| 5841 | |||
| 5842 | 14 | f<Any> TypeChecker.visitBuiltinIsTriviallyCopyable(ASTFctCallNode* node) { | |
| 5843 | 14 | assert node.fqFunctionName == BUILTIN_FCT_NAME_IS_TRIVIALLY_COPYABLE; | |
| 5844 | |||
| 5845 | 14 | const QualType qualType = this.getTemplateType(node, 0l); | |
| 5846 | 14 | CompileTimeValue compileTimeValue; | |
| 5847 | 14 | compileTimeValue.setBoolValue(qualType.isTriviallyCopyable(cast<ASTNode*>(node))); | |
| 5848 | 14 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5849 | |||
| 5850 | 14 | return this.builtinResult(node, QualType(SuperType::TY_BOOL)); | |
| 5851 | } | ||
| 5852 | |||
| 5853 | 338 | f<Any> TypeChecker.visitBuiltinIsTriviallyDestructible(ASTFctCallNode* node) { | |
| 5854 | 338 | assert node.fqFunctionName == BUILTIN_FCT_NAME_IS_TRIVIALLY_DESTRUCTIBLE; | |
| 5855 | |||
| 5856 | 338 | const QualType qualType = this.getTemplateType(node, 0l); | |
| 5857 | 338 | CompileTimeValue compileTimeValue; | |
| 5858 | 338 | compileTimeValue.setBoolValue(qualType.isTriviallyDestructible(cast<ASTNode*>(node))); | |
| 5859 | 338 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5860 | |||
| 5861 | 338 | return this.builtinResult(node, QualType(SuperType::TY_BOOL)); | |
| 5862 | } | ||
| 5863 | |||
| 5864 | 8 | f<Any> TypeChecker.visitBuiltinIsHeap(ASTFctCallNode* node) { | |
| 5865 | 8 | assert node.fqFunctionName == BUILTIN_FCT_NAME_IS_HEAP; | |
| 5866 | |||
| 5867 | 8 | const QualType qualType = this.getTemplateType(node, 0l); | |
| 5868 | 8 | CompileTimeValue compileTimeValue; | |
| 5869 | 8 | compileTimeValue.setBoolValue(qualType.isHeap()); | |
| 5870 | 8 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5871 | |||
| 5872 | 8 | return this.builtinResult(node, QualType(SuperType::TY_BOOL)); | |
| 5873 | } | ||
| 5874 | |||
| 5875 | /** | ||
| 5876 | * Check the constructor of a struct for __new and __placement_new. Shared part of visitBuiltinNewCall and | ||
| 5877 | * visitBuiltinPlacementNewCall. | ||
| 5878 | * | ||
| 5879 | * @param node Builtin call node | ||
| 5880 | * @param templateType Struct type to construct | ||
| 5881 | * @param ctorArgs Constructor arguments | ||
| 5882 | * @return Whether the check was successful. If not, a soft error was recorded | ||
| 5883 | */ | ||
| 5884 | 534 | f<bool> TypeChecker.checkBuiltinNewCtor(ASTFctCallNode* node, const QualType& templateType, const ArgList& ctorArgs) { | |
| 5885 | 534 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5886 | 534 | FctCallData& data = node.data.get(this.manIdx); | |
| 5887 | 534 | Scope* bodyScope = templateType.getBodyScope(); | |
| 5888 | 534 | const QualTypeList noTemplateTypeHints; | |
| 5889 | 534 | Function* ctor = matchFunction(bodyScope, String(CTOR_FUNCTION_NAME), templateType, ctorArgs, noTemplateTypeHints, false, baseNode); | |
| 5890 | 534 | if ctor == nil<Function*> { | |
| 5891 | 26 | const bool copyCtorCall = this.isCopyCtorCall(ctorArgs, templateType); | |
| 5892 | 26 | if !copyCtorCall && !templateType.isTriviallyConstructible(baseNode) { | |
| 5893 | 2 | this.softError(baseNode, SemanticErrorType::NO_MATCHING_CTOR_FOUND, "No matching constructor found for type '" + templateType.getName(false) + "'"); | |
| 5894 | 2 | return false; | |
| 5895 | 2 | } | |
| 5896 | 24 | if copyCtorCall && !templateType.isTriviallyCopyable(baseNode) { | |
| 5897 | ✗ | this.softError(baseNode, SemanticErrorType::NO_MATCHING_CTOR_FOUND, "No matching copy constructor found for type '" + templateType.getName(false) + "'"); | |
| 5898 | ✗ | return false; | |
| 5899 | ✗ | } | |
| 5900 | 24 | } | |
| 5901 | 532 | if ctor != nil<Function*> { | |
| 5902 | 508 | this.matchCopyCtorsForOwnedParams(ctor, baseNode); | |
| 5903 | 508 | } | |
| 5904 | 532 | data.callee = ctor; | |
| 5905 | 532 | return true; | |
| 5906 | 534 | } | |
| 5907 | |||
| 5908 | 241 | f<Any> TypeChecker.visitBuiltinNewCall(ASTFctCallNode* node) { | |
| 5909 | 241 | assert node.fqFunctionName == BUILTIN_FCT_NAME_NEW; | |
| 5910 | 241 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5911 | |||
| 5912 | 241 | FctCallData& data = node.data.get(this.manIdx); | |
| 5913 | 241 | const QualType templateType = this.getTemplateType(node, 0l); | |
| 5914 | |||
| 5915 | 241 | if templateType.is(SuperType::TY_STRUCT) { | |
| 5916 | 231 | if !this.checkBuiltinNewCtor(node, templateType, data.args) { return unresolvedER(); } | |
| 5917 | 240 | } else if data.args.getSize() > 1l { | |
| 5918 | 1 | const String msg = "Expected no or 1 argument for __new on primitive type, got " + toString(data.args.getSize()); | |
| 5919 | 2 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, msg); | |
| 5920 | 10 | } else if data.args.getSize() == 1l { | |
| 5921 | 4 | const Pair<QualType, bool>& arg = data.args.front(); | |
| 5922 | 4 | QualType argType = arg.getFirst(); | |
| 5923 | 4 | argType = argType.removeReferenceWrapper(); | |
| 5924 | 4 | argType = argType.toNonConst(); | |
| 5925 | 4 | if !templateType.matches(argType, false, false, true) { | |
| 5926 | ✗ | const String msg = "Argument type '" + argType.getName() + "' does not match template type '" + templateType.getName() + "'"; | |
| 5927 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, msg); | |
| 5928 | ✗ | } | |
| 5929 | 9 | } | |
| 5930 | |||
| 5931 | 239 | QualType returnType = templateType.toPtr(baseNode); | |
| 5932 | 239 | returnType.makeHeap(); | |
| 5933 | 239 | return this.builtinResult(node, returnType); | |
| 5934 | } | ||
| 5935 | |||
| 5936 | 530 | f<Any> TypeChecker.visitBuiltinPlacementNewCall(ASTFctCallNode* node) { | |
| 5937 | 530 | assert node.fqFunctionName == BUILTIN_FCT_NAME_PLACEMENT_NEW; | |
| 5938 | 530 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 5939 | |||
| 5940 | 530 | FctCallData& data = node.data.get(this.manIdx); | |
| 5941 | 530 | const QualType templateType = this.getTemplateType(node, 0l); | |
| 5942 | |||
| 5943 | // Validate first arg is a byte pointer or a heap pointer to the template type | ||
| 5944 | 530 | const Pair<QualType, bool>& ptrArg = data.args.front(); | |
| 5945 | 530 | QualType ptrArgType = ptrArg.getFirst(); | |
| 5946 | 530 | ptrArgType = ptrArgType.removeReferenceWrapper(); | |
| 5947 | 530 | const bool isBytePtr = ptrArgType.isPtrTo(SuperType::TY_BYTE); | |
| 5948 | 530 | const bool isStructPtr = ptrArgType.isPtr() && ptrArgType.getContained() == templateType; | |
| 5949 | 530 | if !isBytePtr && !isStructPtr { | |
| 5950 | 2 | return this.softErrorER(getASTNode(node.argLst.args.front()), SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, String("__placement_new expects a 'byte*' or 'T*' as its first argument")); | |
| 5951 | 1 | } | |
| 5952 | |||
| 5953 | 529 | if templateType.is(SuperType::TY_STRUCT) { | |
| 5954 | 303 | ArgList ctorArgs; | |
| 5955 | 608 | for unsigned long i = 1l; i < data.args.getSize(); i++ { | |
| 5956 | 305 | ctorArgs.pushBack(data.args.get(i)); | |
| 5957 | 305 | } | |
| 5958 | 303 | if !this.checkBuiltinNewCtor(node, templateType, ctorArgs) { return unresolvedER(); } | |
| 5959 | 529 | } else if data.args.getSize() > 2l { | |
| 5960 | 1 | const String msg = "Expected 1 or 2 arguments for __placement_new, got " + toString(data.args.getSize()); | |
| 5961 | 2 | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, msg); | |
| 5962 | 226 | } else if data.args.getSize() == 2l { | |
| 5963 | 225 | const Pair<QualType, bool>& arg = data.args.back(); | |
| 5964 | 225 | QualType argType = arg.getFirst(); | |
| 5965 | 225 | argType = argType.removeReferenceWrapper(); | |
| 5966 | 225 | argType = argType.toNonConst(); | |
| 5967 | 225 | if !templateType.matches(argType, false, false, true) { | |
| 5968 | ✗ | const String msg = "Argument type '" + argType.getName() + "' does not match template type '" + templateType.getName() + "'"; | |
| 5969 | ✗ | return this.softErrorER(baseNode, SemanticErrorType::BUILTIN_ARG_TYPE_MISMATCH, msg); | |
| 5970 | ✗ | } | |
| 5971 | 225 | } | |
| 5972 | |||
| 5973 | 527 | return this.builtinResult(node, templateType.toPtr(baseNode)); | |
| 5974 | } | ||
| 5975 | |||
| 5976 | 3 | f<Any> TypeChecker.visitBuiltinSourceFileCall(ASTFctCallNode* node) { | |
| 5977 | 3 | assert node.fqFunctionName == BUILTIN_FCT_NAME_SOURCE_FILE; | |
| 5978 | |||
| 5979 | 3 | CompileTimeValue compileTimeValue; | |
| 5980 | 3 | compileTimeValue.setStringValueOffset(this.resourceManager.compileTimeStringValues.getSize()); | |
| 5981 | 3 | this.resourceManager.compileTimeStringValues.pushBack(node.codeLoc.toPrettyFilePath()); | |
| 5982 | 3 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5983 | |||
| 5984 | 6 | return this.builtinResult(node, QualType(SuperType::TY_STRING)); | |
| 5985 | 3 | } | |
| 5986 | |||
| 5987 | 6 | f<Any> TypeChecker.visitBuiltinSourceLineCall(ASTFctCallNode* node) { | |
| 5988 | 6 | assert node.fqFunctionName == BUILTIN_FCT_NAME_SOURCE_LINE; | |
| 5989 | |||
| 5990 | 6 | CompileTimeValue compileTimeValue; | |
| 5991 | 6 | compileTimeValue.setLongValue(cast<long>(node.codeLoc.line)); | |
| 5992 | 6 | this.setCompileTimeValue(node, compileTimeValue); | |
| 5993 | |||
| 5994 | 6 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 5995 | } | ||
| 5996 | |||
| 5997 | 6 | f<Any> TypeChecker.visitBuiltinSourceColumnCall(ASTFctCallNode* node) { | |
| 5998 | 6 | assert node.fqFunctionName == BUILTIN_FCT_NAME_SOURCE_COLUMN; | |
| 5999 | |||
| 6000 | 6 | CompileTimeValue compileTimeValue; | |
| 6001 | 6 | compileTimeValue.setLongValue(cast<long>(node.codeLoc.col)); | |
| 6002 | 6 | this.setCompileTimeValue(node, compileTimeValue); | |
| 6003 | |||
| 6004 | 6 | return this.builtinResult(node, QualType(SuperType::TY_LONG)); | |
| 6005 | } | ||
| 6006 | |||
| 6007 | /** | ||
| 6008 | * Resolves to a reference to the calling thread's ErrorTraceBuffer (std/runtime/error_trace_rt.spice). Only ever | ||
| 6009 | * called from within that same file, where the struct is always in scope by name. | ||
| 6010 | */ | ||
| 6011 | 996 | f<Any> TypeChecker.visitBuiltinErrTraceBufferCall(ASTFctCallNode* node) { | |
| 6012 | 996 | assert node.fqFunctionName == BUILTIN_FCT_NAME_ERR_TRACE_BUFFER; | |
| 6013 | |||
| 6014 | 996 | const SymbolTableEntry* structEntry = this.rootScope.lookupStrict(String("ErrorTraceBuffer")); | |
| 6015 | 996 | assert structEntry != nil<SymbolTableEntry*>; | |
| 6016 | 996 | const QualType structType = structEntry.getQualType(); | |
| 6017 | 996 | const QualType bufferType = structType.toRef(cast<ASTNode*>(node)); | |
| 6018 | |||
| 6019 | 1992 | return this.builtinResult(node, bufferType); | |
| 6020 | 996 | } | |
| 6021 | |||
| 6022 | /** | ||
| 6023 | * Resolves to a pointer to the process's stderr stream. Spice source has no syntax for declaring an external | ||
| 6024 | * *variable* (only functions/procedures via 'ext'), so this exists as a builtin that reaches the real libc | ||
| 6025 | * symbol directly in IR generation, the same way panic()'s own codegen already did before this builtin existed. | ||
| 6026 | */ | ||
| 6027 | 1029 | f<Any> TypeChecker.visitBuiltinStdErrCall(ASTFctCallNode* node) { | |
| 6028 | 1029 | assert node.fqFunctionName == BUILTIN_FCT_NAME_STDERR; | |
| 6029 | |||
| 6030 | 1029 | const QualType byteType = QualType(SuperType::TY_BYTE); | |
| 6031 | 1029 | return this.builtinResult(node, byteType.toPtr(cast<ASTNode*>(node))); | |
| 6032 | } | ||
| 6033 | |||
| 6034 | /** | ||
| 6035 | * Resolves to the frame address (saved frame-pointer slot) of the calling function's own stack frame, i.e. the | ||
| 6036 | * same value as C's `__builtin_frame_address(0)`. IRGenerator keeps frame pointers for every function it emits, so | ||
| 6037 | * the value is always a real frame address - but see the IR generator side of this builtin for why that alone does | ||
| 6038 | * not make the surrounding frame chain walkable on every target. | ||
| 6039 | */ | ||
| 6040 | ✗ | f<Any> TypeChecker.visitBuiltinFrameAddressCall(ASTFctCallNode* node) { | |
| 6041 | ✗ | assert node.fqFunctionName == BUILTIN_FCT_NAME_FRAME_ADDRESS; | |
| 6042 | |||
| 6043 | ✗ | const QualType byteType = QualType(SuperType::TY_BYTE); | |
| 6044 | ✗ | return this.builtinResult(node, byteType.toPtr(cast<ASTNode*>(node))); | |
| 6045 | } | ||
| 6046 | |||
| 6047 | // ===================================================== Implicit functions ====================================================== | ||
| 6048 | |||
| 6049 | /** | ||
| 6050 | * Check whether the given struct and all of its by-value struct fields are manifested, transitively, and have already | ||
| 6051 | * decided their own implicit default members. While a circular import is still being prepared, a by-value struct field | ||
| 6052 | * may (transitively) reference a struct that is not manifested yet; generating implicit special members for such a | ||
| 6053 | * struct recurses through isTriviallyConstructible and would dereference a null struct lookup. A genuine infinite-size | ||
| 6054 | * containment cycle among these structs is reported separately by the infinite-size check during struct preparation, | ||
| 6055 | * so a containment cycle here is treated as manifested. | ||
| 6056 | * | ||
| 6057 | * A field struct can also be manifested (the Struct object exists) but not decided yet: a generic struct like | ||
| 6058 | * Result<T> can be substantiated for T = Url while checking a function body during Url's own file's one-shot sweep | ||
| 6059 | * (TypeChecker.visitEntry), before that sweep has reached Url's struct declaration and decided that Url needs a | ||
| 6060 | * dtor/copy ctor. Deciding Result<Url> right then would see Url as trivial (no dtor found yet) and permanently skip | ||
| 6061 | * generating Result<Url>'s dtor, since createImplicitDefaultMembers only ever runs once per manifestation. Requiring | ||
| 6062 | * the field struct to have decided its own members first defers Result<Url> the same way as the circular-import case | ||
| 6063 | * above, to be picked up again by the fallback sweep in visitStructDefCheck once Url has decided. | ||
| 6064 | */ | ||
| 6065 | 7382 | f<bool> structFullyManifested(const Struct* spiceStruct, const ASTNode* node, Vector<const Scope*>& visited) { | |
| 6066 | 24934 | foreach const QualType& fieldType : spiceStruct.fieldTypes { | |
| 6067 | 17704 | if !fieldType.is(SuperType::TY_STRUCT) { | |
| 6068 | 15448 | continue; | |
| 6069 | } | ||
| 6070 | 2256 | const Struct* fieldStruct = fieldType.getStruct(node); | |
| 6071 | 2256 | if fieldStruct == nil<const Struct*> || !fieldStruct.structBase.implicitDefaultMembersDecided { | |
| 6072 | 152 | return false; | |
| 6073 | } | ||
| 6074 | // Recurse into each field struct once. A containment cycle (already visited) is fine here - it is reported | ||
| 6075 | // separately as an infinite-size error. Leaf structs never touch the list, so the common case stays allocation-free. | ||
| 6076 | 2104 | bool alreadyVisited = false; | |
| 6077 | 4351 | foreach const Scope* visitedScope : visited { | |
| 6078 | 2247 | if visitedScope == fieldStruct.structBase.scope { | |
| 6079 | 228 | alreadyVisited = true; | |
| 6080 | 228 | } | |
| 6081 | 2247 | } | |
| 6082 | 2104 | if !alreadyVisited { | |
| 6083 | 1876 | visited.pushBack(fieldStruct.structBase.scope); | |
| 6084 | 1876 | if !structFullyManifested(fieldStruct, node, visited) { | |
| 6085 | ✗ | return false; | |
| 6086 | } | ||
| 6087 | 1876 | } | |
| 6088 | 2104 | } | |
| 6089 | 7230 | return true; | |
| 6090 | } | ||
| 6091 | |||
| 6092 | /** | ||
| 6093 | * Decide which compiler-generated default members (ctor, copy ctor, move ctor, dtor) the given struct manifestation | ||
| 6094 | * needs and create them. | ||
| 6095 | * | ||
| 6096 | * This decision depends on the concrete field types, so it has to be taken once per manifestation and not once per | ||
| 6097 | * struct declaration: whether e.g. HashEntry<K, V> needs a dtor is unanswerable while K and V are still generic, but | ||
| 6098 | * well-defined for HashEntry<int, String>. It is therefore taken as soon as a manifestation comes into existence - | ||
| 6099 | * either by the one-shot sweep over the manifestations that already exist when a source file is prepared | ||
| 6100 | * (TypeChecker.visitEntry), or, for manifestations that are substantiated later on, right at the point where | ||
| 6101 | * matchStruct creates them. Deciding any later would be too late for all the reactive lookups that ask a | ||
| 6102 | * struct for its default members (matchFunction/lookupFunction, doScopeCleanup, ...), since a function body is | ||
| 6103 | * type-checked exactly once and would not pick up a dtor that appears afterward. | ||
| 6104 | * | ||
| 6105 | * The default move ctor is decided about only when the caller asks for it. A manifestation that is substantiated after | ||
| 6106 | * its file was prepared does not get one, which keeps it at the behavior it had before this decision was taken per | ||
| 6107 | * manifestation at all. Synthesizing one would require the move ctor of each struct-typed field to be resolvable for | ||
| 6108 | * the concrete manifestation, which the raw manifestation scan behind it (findMoveCtor) cannot do for | ||
| 6109 | * a generic field type - it returns the generic preset that the manifestation scope inherited. Without a move ctor, | ||
| 6110 | * moving such a struct falls back to copying it, which is correct, just less efficient. | ||
| 6111 | * | ||
| 6112 | * @param spiceStruct Struct manifestation to decide the default members for | ||
| 6113 | * @param node AST node for error messages | ||
| 6114 | * @param withMoveCtor Also decide about the default move ctor | ||
| 6115 | */ | ||
| 6116 | 13433 | public p createImplicitDefaultMembers(Struct& spiceStruct, const ASTNode* node, bool withMoveCtor = true) { | |
| 6117 | // Take the decision only once per manifestation | ||
| 6118 | 13433 | if spiceStruct.structBase.implicitDefaultMembersDecided { | |
| 6119 | 8078 | return; | |
| 6120 | } | ||
| 6121 | |||
| 6122 | 5355 | SourceFile* structSourceFile = spiceStruct.structBase.scope.sourceFile; | |
| 6123 | 5355 | GlobalResourceManager& resourceManager = structSourceFile.resourceManager; | |
| 6124 | |||
| 6125 | // Skip while a by-value struct field is (transitively) not manifested yet, or has not decided its own implicit | ||
| 6126 | // default members yet (circular import still in progress, or a sibling struct's one-shot sweep has not reached it | ||
| 6127 | // yet - see structFullyManifested). Relying solely on the fallback sweep in visitStructDefCheck to pick this back up | ||
| 6128 | // can be too late: that sweep only runs during the type-checker POST pass, by which point some other function body | ||
| 6129 | // elsewhere may already have looked up (and missed) this manifestation's dtor for good, since a function body is | ||
| 6130 | // type-checked exactly once. Queue the manifestation instead, so it gets retried the moment something else's | ||
| 6131 | // decision completes (see drainPendingImplicitDefaultMemberDecisions below) - typically still within the same PRE | ||
| 6132 | // pass that first substantiated it. | ||
| 6133 | 5355 | Vector<const Scope*> visitedScopes; | |
| 6134 | 5355 | if !structFullyManifested(&spiceStruct, node, visitedScopes) { | |
| 6135 | 36 | Vector<Pair<Struct*, bool>>& pending = resourceManager.pendingStructDefaultMemberDecisions; | |
| 6136 | 36 | bool found = false; | |
| 6137 | 43 | foreach Pair<Struct*, bool>& entry : pending { | |
| 6138 | 7 | if entry.getFirst() == &spiceStruct { | |
| 6139 | // Upgrade to deciding the move ctor too if any deferred caller asked for it | ||
| 6140 | ✗ | bool& pendingWithMoveCtor = entry.getSecond(); | |
| 6141 | ✗ | pendingWithMoveCtor = pendingWithMoveCtor || withMoveCtor; | |
| 6142 | ✗ | found = true; | |
| 6143 | ✗ | } | |
| 6144 | 7 | } | |
| 6145 | 36 | if !found { | |
| 6146 | 36 | pending.pushBack(Pair<Struct*, bool>(&spiceStruct, withMoveCtor)); | |
| 6147 | 36 | } | |
| 6148 | 36 | return; | |
| 6149 | } | ||
| 6150 | |||
| 6151 | 5319 | decideDefaultMembers(spiceStruct, withMoveCtor); | |
| 6152 | |||
| 6153 | // This struct just became decided - drain the pending list so it can pick up anything that was blocked on it, directly or | ||
| 6154 | // transitively (a chain of Result<Outer<Inner>>-shaped dependencies). | ||
| 6155 | 5319 | drainPendingImplicitDefaultMemberDecisions(resourceManager); | |
| 6156 | 5355 | } | |
| 6157 | |||
| 6158 | /** | ||
| 6159 | * Set a struct manifestation's default members as decided and create whichever of them (ctor, copy ctor, move ctor, | ||
| 6160 | * dtor) it actually needs. Only called once structFullyManifested has confirmed the struct is ready. | ||
| 6161 | * | ||
| 6162 | * @param spiceStruct Struct manifestation to decide the default members for | ||
| 6163 | * @param withMoveCtor Also decide about the default move ctor | ||
| 6164 | */ | ||
| 6165 | 5354 | p decideDefaultMembers(Struct& spiceStruct, bool withMoveCtor) { | |
| 6166 | 5354 | spiceStruct.structBase.implicitDefaultMembersDecided = true; | |
| 6167 | |||
| 6168 | 5354 | createDefaultCtorIfRequired(spiceStruct, spiceStruct.structBase.scope); | |
| 6169 | 5354 | createDefaultCopyCtorIfRequired(spiceStruct, spiceStruct.structBase.scope); | |
| 6170 | 5354 | if withMoveCtor { | |
| 6171 | 3238 | createDefaultMoveCtorIfRequired(spiceStruct, spiceStruct.structBase.scope); | |
| 6172 | 3238 | } | |
| 6173 | 5354 | createDefaultDtorIfRequired(spiceStruct, spiceStruct.structBase.scope); | |
| 6174 | 5354 | inheritDefaultMethods(spiceStruct); | |
| 6175 | 5354 | } | |
| 6176 | |||
| 6177 | /** | ||
| 6178 | * Let a struct manifestation inherit the default methods of its interfaces for all interface methods, that the struct does | ||
| 6179 | * not implement itself. This happens as early as possible, so that calls to inherited methods can be type-checked before | ||
| 6180 | * the struct itself is checked to implement all interface methods. Default methods of interfaces in a circular import | ||
| 6181 | * might not be prepared yet at this point. Those are inherited when checking the struct (see visitStructDefCheck). | ||
| 6182 | * | ||
| 6183 | * @param spiceStruct Struct manifestation | ||
| 6184 | */ | ||
| 6185 | 5354 | p inheritDefaultMethods(Struct& spiceStruct) { | |
| 6186 | 5354 | if !spiceStruct.structBase.isFullySubstantiated() { | |
| 6187 | 1124 | return; | |
| 6188 | } | ||
| 6189 | |||
| 6190 | 4230 | const QualType structType = spiceStruct.structBase.entry.getQualType(); | |
| 6191 | 4963 | foreach const QualType& interfaceType : spiceStruct.interfaceTypes { | |
| 6192 | // Generic interfaces cannot have default methods | ||
| 6193 | 733 | const QualTypeList interfaceTemplateTypes = interfaceType.getTemplateTypes(); | |
| 6194 | 733 | if !interfaceTemplateTypes.isEmpty() { | |
| 6195 | 685 | continue; | |
| 6196 | } | ||
| 6197 | 48 | const Interface* spiceInterface = interfaceType.getInterface(spiceStruct.structBase.declNode); | |
| 6198 | 48 | assert spiceInterface != nil<const Interface*>; | |
| 6199 | |||
| 6200 | 114 | foreach const Function* method : spiceInterface.methods { | |
| 6201 | // Check if the struct implements the method itself | ||
| 6202 | 66 | const QualTypeList paramTypes = method.getParamTypes(); | |
| 6203 | 66 | ArgList args; | |
| 6204 | 66 | args.reserve(paramTypes.getSize()); | |
| 6205 | 85 | foreach const QualType& paramType : paramTypes { | |
| 6206 | 19 | args.pushBack(Pair<QualType, bool>(paramType, false)); | |
| 6207 | 19 | } | |
| 6208 | 66 | const QualTypeList noTemplateTypeHints; | |
| 6209 | 66 | if matchFunction(spiceStruct.structBase.scope, method.name, structType, args, noTemplateTypeHints, true, spiceStruct.structBase.declNode) != nil<Function*> { | |
| 6210 | 51 | continue; | |
| 6211 | } | ||
| 6212 | |||
| 6213 | 15 | const Function* _ = inheritDefaultMethod(spiceStruct.structBase.scope, structType, spiceInterface, method.name, paramTypes, method.returnType); | |
| 6214 | 66 | } | |
| 6215 | 733 | } | |
| 6216 | 4230 | } | |
| 6217 | |||
| 6218 | /** | ||
| 6219 | * Let a struct inherit the default method of an interface for the given method signature, if the interface provides one. | ||
| 6220 | * The inherited method is inserted into the struct scope, but has no body on its own. It refers to the default method, | ||
| 6221 | * which is emitted only once and gets called with a pointer to the interface part of the struct. | ||
| 6222 | * | ||
| 6223 | * @param structScope Scope of the struct manifestation | ||
| 6224 | * @param structType Struct type, which implements the interface | ||
| 6225 | * @param spiceInterface Interface, which might provide the default method | ||
| 6226 | * @param methodName Name of the requested method | ||
| 6227 | * @param paramTypes Parameter types of the requested method | ||
| 6228 | * @param returnType Return type of the requested method | ||
| 6229 | * @return Inherited method or nil if the interface has no default method for the signature | ||
| 6230 | */ | ||
| 6231 | 20 | f<Function*> inheritDefaultMethod(Scope* structScope, const QualType& structType, const Interface* spiceInterface, const String& methodName, const QualTypeList& paramTypes, const QualType& returnType) { | |
| 6232 | // Search for a default method with the exact signature | ||
| 6233 | 20 | Function* defaultMethod = lookupDefaultMethod(spiceInterface.structBase.scope, methodName, paramTypes, returnType); | |
| 6234 | 20 | if defaultMethod == nil<Function*> { | |
| 6235 | 8 | return nil<Function*>; | |
| 6236 | } | ||
| 6237 | |||
| 6238 | // If the struct declares a method with this name, it most likely tries to implement the interface method, but with a | ||
| 6239 | // signature that does not match. Do not silently inherit the default method in this case, but report the missing method | ||
| 6240 | 12 | if hasUserMethodWithName(structScope, methodName) { | |
| 6241 | 2 | return nil<Function*>; | |
| 6242 | } | ||
| 6243 | |||
| 6244 | // Create the inherited method in the struct scope | ||
| 6245 | 10 | Function inheritedMethod = *defaultMethod; | |
| 6246 | 10 | inheritedMethod.thisType = structType; | |
| 6247 | 10 | inheritedMethod.bodyScope = nil<Scope*>; | |
| 6248 | 10 | inheritedMethod.isInterfaceDefaultMethod = false; | |
| 6249 | 10 | inheritedMethod.defaultMethod = defaultMethod; | |
| 6250 | 10 | inheritedMethod.alreadyTypeChecked = true; // The body is type-checked as part of the default method | |
| 6251 | 10 | inheritedMethod.used = false; | |
| 6252 | |||
| 6253 | // The default method is referenced by the vtable of the struct, so it has to be emitted | ||
| 6254 | 10 | defaultMethod.used = true; | |
| 6255 | 10 | defaultMethod.entry.used = true; | |
| 6256 | |||
| 6257 | 20 | return insertFunction(structScope, inheritedMethod); | |
| 6258 | 10 | } | |
| 6259 | |||
| 6260 | /** | ||
| 6261 | * Drain every struct manifestation that createImplicitDefaultMembers previously had to defer because a by-value | ||
| 6262 | * field struct had not decided its own default members yet. Called whenever a struct's decision completes, since | ||
| 6263 | * that can unblock others - directly (a struct that was waiting on exactly this one) or transitively (a chain of | ||
| 6264 | * Result<Outer<Inner>>-shaped dependencies). | ||
| 6265 | * | ||
| 6266 | * This calls decideDefaultMembers directly rather than createImplicitDefaultMembers, so draining one entry can never | ||
| 6267 | * recurse back into this function - no reentrancy guard is needed, the loop below just keeps going until a full pass | ||
| 6268 | * makes no further progress. | ||
| 6269 | * | ||
| 6270 | * @param resourceManager Resource manager that owns the pending list for this compilation | ||
| 6271 | */ | ||
| 6272 | 5319 | p drainPendingImplicitDefaultMemberDecisions(GlobalResourceManager& resourceManager) { | |
| 6273 | 5319 | bool progressed = true; | |
| 6274 | 10672 | while progressed { | |
| 6275 | 5353 | progressed = false; | |
| 6276 | // Snapshot before iterating: deciding one pending manifestation can append newly-deferred entries to the very | ||
| 6277 | // list being iterated (a struct that unblocks can itself be a field of another, still-blocked struct). | ||
| 6278 | 5353 | const Vector<Pair<Struct*, bool>> snapshot = resourceManager.pendingStructDefaultMemberDecisions; | |
| 6279 | 5539 | foreach const Pair<Struct*, bool>& pendingEntry : snapshot { | |
| 6280 | 186 | Struct* pendingStruct = pendingEntry.getFirst(); | |
| 6281 | 186 | if pendingStruct.structBase.implicitDefaultMembersDecided { | |
| 6282 | 35 | continue; // Already resolved by an earlier entry in this same pass | |
| 6283 | } | ||
| 6284 | |||
| 6285 | 151 | Vector<const Scope*> visitedScopes; | |
| 6286 | 151 | if !structFullyManifested(pendingStruct, pendingStruct.structBase.declNode, visitedScopes) { | |
| 6287 | 116 | continue; // Still blocked on something else; leave it queued | |
| 6288 | } | ||
| 6289 | |||
| 6290 | 35 | decideDefaultMembers(*pendingStruct, pendingEntry.getSecond()); | |
| 6291 | 35 | progressed = true; | |
| 6292 | 151 | } | |
| 6293 | 5353 | } | |
| 6294 | |||
| 6295 | // Drop everything that got resolved. Whatever remains is still genuinely blocked (e.g. a circular import still in | ||
| 6296 | // progress) and stays queued to be retried again the next time some other struct's decision completes. | ||
| 6297 | 5319 | Vector<Pair<Struct*, bool>> stillPending; | |
| 6298 | 5464 | foreach const Pair<Struct*, bool>& pendingEntry : resourceManager.pendingStructDefaultMemberDecisions { | |
| 6299 | 145 | const Struct* pendingStruct = pendingEntry.getFirst(); | |
| 6300 | 145 | if !pendingStruct.structBase.implicitDefaultMembersDecided { | |
| 6301 | 110 | stillPending.pushBack(pendingEntry); | |
| 6302 | 110 | } | |
| 6303 | 145 | } | |
| 6304 | 5319 | resourceManager.pendingStructDefaultMemberDecisions = stillPending; | |
| 6305 | 5319 | } | |
| 6306 | |||
| 6307 | /** | ||
| 6308 | * Create a default struct method | ||
| 6309 | * Checks if the given struct scope already has a user-defined constructor and creates a default one if not. | ||
| 6310 | * | ||
| 6311 | * @param spiceStruct Struct instance | ||
| 6312 | * @param entryName Name of the symbol table entry | ||
| 6313 | * @param name Name of the method to create | ||
| 6314 | * @param params Parameter types of the method | ||
| 6315 | */ | ||
| 6316 | 1731 | p createDefaultStructMethod(const Struct& spiceStruct, const String& entryName, const String& name, const ParamList& params) { | |
| 6317 | 1731 | Scope* structScope = spiceStruct.structBase.scope; | |
| 6318 | 1731 | SourceFile* sourceFile = structScope.sourceFile; | |
| 6319 | 1731 | ASTNode* node = spiceStruct.structBase.declNode; | |
| 6320 | 1731 | const SymbolTableEntry* structEntry = spiceStruct.structBase.entry; | |
| 6321 | 1731 | const QualType structType = structEntry.getQualType(); | |
| 6322 | 1731 | const String fqFctName = structType.getSubType() + MEMBER_ACCESS_TOKEN + name; | |
| 6323 | |||
| 6324 | // Procedure type | ||
| 6325 | 1731 | QualType procedureType = QualType(SuperType::TY_PROCEDURE); | |
| 6326 | 1731 | procedureType.makePublic(); // Always public | |
| 6327 | |||
| 6328 | // Insert symbol for function into the symbol table | ||
| 6329 | 1731 | SymbolTableEntry* procEntry = structScope.insert(entryName, structEntry.declNode); | |
| 6330 | 1731 | procEntry.updateType(procedureType, true); | |
| 6331 | |||
| 6332 | // Add to external name registry. Only the generic preset does this: the registry is keyed by the plain struct name, | ||
| 6333 | // so an entry added for one manifestation would be indistinguishable from a user-defined member and would wrongly | ||
| 6334 | // suppress the default member of every sibling manifestation (e.g. HashEntry<int, String> vs. HashEntry<int, int>). | ||
| 6335 | // Manifestation-level default members are only ever looked up through their struct scope anyway. | ||
| 6336 | 1731 | if !spiceStruct.structBase.isGenericSubstantiation() { | |
| 6337 | 1194 | sourceFile.addNameRegistryEntry(fqFctName, cast<unsigned long>(SuperType::TY_PROCEDURE), procEntry, structScope, true, nil<SymbolTableEntry*>); | |
| 6338 | 1194 | } | |
| 6339 | |||
| 6340 | // Create the default method | ||
| 6341 | 1731 | const Vector<GenericType> templateTypes = spiceStruct.structBase.templateTypes; | |
| 6342 | 1731 | const QualType returnType = QualType(SuperType::TY_DYN); | |
| 6343 | 1731 | Function defaultMethod = Function(name, procEntry, structType, returnType, params, templateTypes, structEntry.declNode); | |
| 6344 | 1731 | defaultMethod.implicitDefault = true; | |
| 6345 | |||
| 6346 | // Fill type mapping for the case, that the template type list contains non-generic types. Only struct, interface | ||
| 6347 | // and enum types carry a sub type usable as a key here; a non-generic primitive template argument (e.g. the 'int' | ||
| 6348 | // in BlockAllocator<int>) has no sub type, so getSubType() would assert on it - skip those. | ||
| 6349 | 1731 | const dyn subTypeOptions = [SuperType::TY_STRUCT, SuperType::TY_INTERFACE, SuperType::TY_ENUM]; | |
| 6350 | 2880 | foreach const GenericType& templateType : templateTypes { | |
| 6351 | 1149 | const QualType& templateQualType = templateType.qualType; | |
| 6352 | 1149 | if !templateQualType.is(SuperType::TY_GENERIC) && templateQualType.isOneOf(subTypeOptions, len(subTypeOptions)) { | |
| 6353 | 201 | defaultMethod.typeMapping.upsert(templateQualType.getSubType(), templateQualType); | |
| 6354 | 201 | } | |
| 6355 | 1149 | } | |
| 6356 | |||
| 6357 | // Create function scope | ||
| 6358 | 1731 | Scope* procScope = structScope.createChildScope(defaultMethod.getScopeName(), ScopeType::FUNC_PROC_BODY, &node.codeLoc); | |
| 6359 | 1731 | defaultMethod.bodyScope = procScope; | |
| 6360 | |||
| 6361 | // Create 'this' symbol in the function scope | ||
| 6362 | 1731 | SymbolTableEntry* thisEntry = procScope.insert(String(THIS_VARIABLE_NAME), node); | |
| 6363 | 1731 | thisEntry.updateType(structType.toPtr(node), true); | |
| 6364 | 1731 | thisEntry.used = true; // Always set to used to not print warnings for non-existing code | |
| 6365 | |||
| 6366 | // Hand it off to the function manager to register the function | ||
| 6367 | 1731 | ASTNode* structDeclNode = structEntry.declNode; | |
| 6368 | 1731 | insertFunction(structScope, defaultMethod, structDeclNode.getFctManifestations(name)); | |
| 6369 | |||
| 6370 | // The body of a default member is not prepared here, but in visitStructDefCheck, which runs over all manifestations | ||
| 6371 | // of the struct. For a manifestation that was substantiated after its file was already checked, that run is long | ||
| 6372 | // over, so ask for another one - otherwise the member would end up without a body preamble, and the IR generator | ||
| 6373 | // would e.g. not find the copy ctor of a field it is supposed to copy. During the prepare stage the flag is set | ||
| 6374 | // anyway, so this is a no-op there. | ||
| 6375 | 1731 | sourceFile.reVisitRequested = true; | |
| 6376 | 1731 | } | |
| 6377 | |||
| 6378 | /** | ||
| 6379 | * Checks if the given struct scope already has a user-defined constructor and creates a default one if not. | ||
| 6380 | * | ||
| 6381 | * For generating a default ctor, the following conditions need to be met: | ||
| 6382 | * - No user-defined constructors (incl. copy/move ctors) | ||
| 6383 | * | ||
| 6384 | * @param spiceStruct Struct instance | ||
| 6385 | * @param structScope Scope of the struct | ||
| 6386 | */ | ||
| 6387 | 5354 | p createDefaultCtorIfRequired(const Struct& spiceStruct, Scope* structScope) { | |
| 6388 | 5354 | const ASTStructDefNode* node = downcast<ASTStructDefNode>(spiceStruct.structBase.declNode); | |
| 6389 | 5354 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 6390 | 5354 | assert structScope != nil<Scope*> && structScope.scopeType == ScopeType::STRUCT; | |
| 6391 | 5354 | SourceFile* sourceFile = structScope.sourceFile; | |
| 6392 | |||
| 6393 | // Abort if the struct already has a constructor. The name registry is the authoritative source here: it is filled by | ||
| 6394 | // the symbol table builder, so it already knows about every user-defined ctor, even one that is declared further down | ||
| 6395 | // in the file and therefore has not been registered in the struct scope yet. It also covers a ctor that the generic | ||
| 6396 | // preset received, which every manifestation inherits through its copied scope. The scope scan on top of it only | ||
| 6397 | // guards against creating a second ctor into a scope that visibly has one already. | ||
| 6398 | // A registry entry that stems from a default ctor of the generic preset does not count for a manifestation, that has no | ||
| 6399 | // ctor in its scope: Such a manifestation was substantiated before the preset got its default ctors, so it could not | ||
| 6400 | // inherit them through its copied scope and has to create its own. The registry entry itself cannot tell this apart, | ||
| 6401 | // since all ctors share the registry name, and a default copy or move ctor of the preset replaces the entry of a | ||
| 6402 | // user-defined ctor. Ask the scope of the preset instead, which knows all user-defined ctors at this point. | ||
| 6403 | 5354 | const SymbolTableEntry* structEntry = spiceStruct.structBase.entry; | |
| 6404 | 5354 | const QualType structType = structEntry.getQualType(); | |
| 6405 | 5354 | const String fqFctName = structType.getSubType() + MEMBER_ACCESS_TOKEN + CTOR_FUNCTION_NAME; | |
| 6406 | 5354 | const NameRegistryEntry* registryEntry = sourceFile.getNameRegistryEntry(fqFctName); | |
| 6407 | 5354 | if hasAnyCtor(structScope) { | |
| 6408 | 3955 | return; | |
| 6409 | } | ||
| 6410 | 1399 | if registryEntry != nil<const NameRegistryEntry*> { | |
| 6411 | 3 | const bool hasOnlyPresetDefaultCtors = spiceStruct.structBase.isGenericSubstantiation() && !hasUserMethodWithName(spiceStruct.structBase.genericPreset.scope, String(CTOR_FUNCTION_NAME)); | |
| 6412 | 3 | if !hasOnlyPresetDefaultCtors { | |
| 6413 | 1 | return; | |
| 6414 | } | ||
| 6415 | 2 | } | |
| 6416 | |||
| 6417 | // Check if we have fields, that require us to do anything in the ctor | ||
| 6418 | 1398 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6419 | 1398 | bool hasFieldsWithDefaultValue = false; | |
| 6420 | 1398 | bool hasFieldsToConstruct = false; | |
| 6421 | 4891 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6422 | 3520 | const SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6423 | 3520 | assert fieldSymbol != nil<const SymbolTableEntry*> && fieldSymbol.declNode != nil<ASTNode*>; | |
| 6424 | |||
| 6425 | 3520 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6426 | 3520 | if fieldType.hasAnyGenericParts() && !spiceStruct.structBase.typeMapping.isEmpty() { | |
| 6427 | 15 | substantiateTypeWithTypeMapping(fieldType, spiceStruct.structBase.typeMapping, fieldSymbol.declNode); | |
| 6428 | 15 | } | |
| 6429 | |||
| 6430 | // Abort if we have a field, that is a reference | ||
| 6431 | 3520 | if fieldType.isRef() { | |
| 6432 | 16 | return; | |
| 6433 | } | ||
| 6434 | |||
| 6435 | 3504 | ASTNode* fieldDeclNode = fieldSymbol.declNode; | |
| 6436 | 3504 | if fieldDeclNode.kind == ASTNodeKind::FIELD { | |
| 6437 | 3443 | const ASTFieldNode* fieldNode = downcast<ASTFieldNode>(fieldDeclNode); | |
| 6438 | 3443 | hasFieldsWithDefaultValue = hasFieldsWithDefaultValue || (fieldNode.defaultValue != nil<IVisitable*>); | |
| 6439 | 3443 | } else { | |
| 6440 | 61 | assert fieldDeclNode.kind == ASTNodeKind::DATA_TYPE; | |
| 6441 | 61 | } | |
| 6442 | |||
| 6443 | 3504 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6444 | 179 | Scope* bodyScope = fieldType.getBodyScope(); | |
| 6445 | // Check if we are required to call a ctor | ||
| 6446 | 179 | const bool isCtorCallRequired = !fieldType.isTriviallyConstructible(baseNode); | |
| 6447 | // While the outer struct is still a generic preset, a field type that is itself generic (e.g. Inner<T, bool>, | ||
| 6448 | // the internal field of Outer<T>) cannot be matched to a concrete ctor yet. Since a default ctor takes no | ||
| 6449 | // arguments, matching it against the still-generic 'this' type would partially substantiate a bogus | ||
| 6450 | // manifestation (e.g. Inner<K, bool>, with K left generic) that is wrongly treated as fully substantiated | ||
| 6451 | // later on and crashes name mangling during IR generation (see #1255). In that case we only record that the | ||
| 6452 | // field has a default ctor via a direct scan; the concrete ctor is matched later, per manifestation, in | ||
| 6453 | // createCtorBodyPreamble. | ||
| 6454 | 179 | if fieldType.hasAnyGenericParts() { | |
| 6455 | 14 | const bool hasDefaultCtor = hasDefaultCtor(bodyScope); | |
| 6456 | 14 | if !hasDefaultCtor && isCtorCallRequired { | |
| 6457 | ✗ | return; | |
| 6458 | } | ||
| 6459 | 14 | hasFieldsToConstruct = hasFieldsToConstruct || hasDefaultCtor; | |
| 6460 | 14 | } else { | |
| 6461 | // Lookup ctor function | ||
| 6462 | 165 | const ArgList noArgs; | |
| 6463 | 165 | const QualTypeList noTemplateTypeHints; | |
| 6464 | 165 | const Function* ctorFct = matchFunction(bodyScope, String(CTOR_FUNCTION_NAME), fieldType, noArgs, noTemplateTypeHints, true, baseNode); | |
| 6465 | // If we are required to construct, but no constructor is found, we can't generate a default ctor for the outer struct | ||
| 6466 | 165 | if ctorFct == nil<const Function*> && isCtorCallRequired { | |
| 6467 | 11 | return; | |
| 6468 | } | ||
| 6469 | 154 | hasFieldsToConstruct = hasFieldsToConstruct || (ctorFct != nil<const Function*>); | |
| 6470 | 165 | } | |
| 6471 | 168 | } | |
| 6472 | 3493 | } | |
| 6473 | |||
| 6474 | // If we don't have any fields, that require us to do anything in the ctor, we can skip it | ||
| 6475 | 1371 | if !hasFieldsWithDefaultValue && !hasFieldsToConstruct && !node.emitVTable { | |
| 6476 | 1170 | return; | |
| 6477 | } | ||
| 6478 | |||
| 6479 | // Create the default ctor function | ||
| 6480 | 201 | const String entryName = getSymbolTableEntryNameDefaultCtor(baseNode.codeLoc); | |
| 6481 | 201 | const ParamList noParams; | |
| 6482 | 201 | createDefaultStructMethod(spiceStruct, entryName, String(CTOR_FUNCTION_NAME), noParams); | |
| 6483 | 5354 | } | |
| 6484 | |||
| 6485 | /** | ||
| 6486 | * Checks if the given struct scope already has a user-defined constructor and creates a default one if not. | ||
| 6487 | * | ||
| 6488 | * For generating a default copy ctor, the following conditions need to be met: | ||
| 6489 | * - No user-defined copy ctor | ||
| 6490 | * | ||
| 6491 | * @param spiceStruct Struct instance | ||
| 6492 | * @param structScope Scope of the struct | ||
| 6493 | */ | ||
| 6494 | 5354 | p createDefaultCopyCtorIfRequired(const Struct& spiceStruct, Scope* structScope) { | |
| 6495 | 5354 | const ASTStructDefNode* node = downcast<ASTStructDefNode>(spiceStruct.structBase.declNode); | |
| 6496 | 5354 | ASTNode* baseNode = cast<ASTNode*>(node); | |
| 6497 | 5354 | assert structScope != nil<Scope*> && structScope.scopeType == ScopeType::STRUCT; | |
| 6498 | |||
| 6499 | // Abort if the struct already has a user-defined copy constructor | ||
| 6500 | 5354 | const SymbolTableEntry* structEntry = spiceStruct.structBase.entry; | |
| 6501 | 5354 | const QualType structType = structEntry.getQualType(); | |
| 6502 | 5354 | if hasCopyCtor(structScope) { | |
| 6503 | 1442 | return; | |
| 6504 | } | ||
| 6505 | |||
| 6506 | // Check if we have fields, that require us to do anything in the ctor | ||
| 6507 | 3912 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6508 | 3912 | bool copyCtorRequired = false; | |
| 6509 | 12843 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6510 | 8931 | const SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6511 | 8931 | assert fieldSymbol != nil<const SymbolTableEntry*> && fieldSymbol.declNode != nil<ASTNode*>; | |
| 6512 | |||
| 6513 | 8931 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6514 | 8931 | if fieldType.hasAnyGenericParts() && !spiceStruct.structBase.typeMapping.isEmpty() { | |
| 6515 | 171 | substantiateTypeWithTypeMapping(fieldType, spiceStruct.structBase.typeMapping, fieldSymbol.declNode); | |
| 6516 | 171 | } | |
| 6517 | |||
| 6518 | // If the field is of type struct, check if this struct has a copy ctor that has to be called | ||
| 6519 | 8931 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6520 | // A field whose copy is non-trivial forces the outer struct to have a copy ctor as well. | ||
| 6521 | 1456 | const bool fieldRequiresCopyCtor = !fieldType.isTriviallyCopyable(baseNode); | |
| 6522 | // While the outer struct is still a generic preset, a field type that is itself generic (e.g. Inner<T, bool>, | ||
| 6523 | // the internal field of Outer<T>) cannot be matched to a concrete copy ctor yet. Matching it via the | ||
| 6524 | // substantiating matchFunction() below can partially substantiate a bogus manifestation (e.g. | ||
| 6525 | // Inner<K, bool>, with K left generic) that is wrongly treated as fully substantiated later on and crashes | ||
| 6526 | // name mangling during IR generation (see #1255). This can happen even though the copy ctor call carries an | ||
| 6527 | // argument of the same (still-generic) field type, whenever the field struct's own generic type name | ||
| 6528 | // collides with the outer struct's generic type name (e.g. both named 'V', as in Set<V> wrapping | ||
| 6529 | // RedBlackTree<K,V>). The concrete copy ctor is matched later, per manifestation, in | ||
| 6530 | // createCopyCtorBodyPreamble, and its result here is not otherwise consulted while the field type is still | ||
| 6531 | // generic (fieldRequiresCopyCtor already reflects copy-ctor existence via the non-substantiating | ||
| 6532 | // isTriviallyCopyable check above) - so skip the substantiating match entirely in that case. | ||
| 6533 | 1456 | if !fieldType.hasAnyGenericParts() { | |
| 6534 | 1333 | Scope* bodyScope = fieldType.getBodyScope(); | |
| 6535 | 1333 | ArgList args; | |
| 6536 | 1333 | args.pushBack(Pair<QualType, bool>(fieldType.toConstRef(baseNode), false /* we always have the field as storage */)); | |
| 6537 | 1333 | const QualTypeList noTemplateTypeHints; | |
| 6538 | 1333 | const Function* ctorFct = matchFunction(bodyScope, String(CTOR_FUNCTION_NAME), fieldType, args, noTemplateTypeHints, true, baseNode); | |
| 6539 | // If the field requires a copy ctor, but we proved none exists, we cannot synthesize one for the outer struct. | ||
| 6540 | 1333 | if ctorFct == nil<const Function*> && fieldRequiresCopyCtor { | |
| 6541 | ✗ | return; | |
| 6542 | } | ||
| 6543 | 1333 | } | |
| 6544 | 1456 | copyCtorRequired = copyCtorRequired || fieldRequiresCopyCtor; | |
| 6545 | 1456 | } | |
| 6546 | |||
| 6547 | // If we have an owning heap pointer, we need to do a memcpy of the heap storage and therefore need a default copy ctor | ||
| 6548 | 8931 | if fieldType.isHeap() { | |
| 6549 | 220 | assert fieldType.isPtr(); | |
| 6550 | 220 | copyCtorRequired = true; | |
| 6551 | 220 | } | |
| 6552 | 8931 | } | |
| 6553 | |||
| 6554 | // If we don't have any fields, that require us to do anything in the copy ctor, we can skip it | ||
| 6555 | 3912 | if !copyCtorRequired && !node.emitVTable { | |
| 6556 | 2929 | return; | |
| 6557 | } | ||
| 6558 | |||
| 6559 | // Create the default copy ctor function | ||
| 6560 | 983 | const String entryName = getSymbolTableEntryNameDefaultCopyCtor(baseNode.codeLoc); | |
| 6561 | 983 | ParamList paramTypes; | |
| 6562 | 983 | paramTypes.pushBack(Param{structType.toConstRef(baseNode), false}); | |
| 6563 | 983 | createDefaultStructMethod(spiceStruct, entryName, String(CTOR_FUNCTION_NAME), paramTypes); | |
| 6564 | 983 | } | |
| 6565 | |||
| 6566 | /** | ||
| 6567 | * Checks if the given struct scope already has a user-defined move constructor and creates a default one if not. | ||
| 6568 | * | ||
| 6569 | * For generating a default move ctor, the following conditions need to be met: | ||
| 6570 | * - No user-defined move ctor | ||
| 6571 | * - At least one field requires non-trivial moving (heap pointer or struct field with move ctor) | ||
| 6572 | * | ||
| 6573 | * @param spiceStruct Struct instance | ||
| 6574 | * @param structScope Scope of the struct | ||
| 6575 | */ | ||
| 6576 | 3238 | p createDefaultMoveCtorIfRequired(const Struct& spiceStruct, Scope* structScope) { | |
| 6577 | 3238 | ASTNode* node = spiceStruct.structBase.declNode; | |
| 6578 | 3238 | assert structScope != nil<Scope*> && structScope.scopeType == ScopeType::STRUCT; | |
| 6579 | |||
| 6580 | // Skip generic struct presets - we only synthesize a default move ctor for fully substantiated structs. | ||
| 6581 | // Reason: each manifestation gets its own scope (deep-copied from the generic), and the implicit move ctor | ||
| 6582 | // body's preamble writes to field lifecycle state via bodyScope.parent. If we created the move ctor on the | ||
| 6583 | // generic struct, the deep-copied Function in the manifestation's scope would still carry a bodyScope pointer | ||
| 6584 | // into the generic struct's scope, causing the preamble to update fields in the wrong scope. | ||
| 6585 | 3238 | if !spiceStruct.structBase.isFullySubstantiated() { | |
| 6586 | 925 | return; | |
| 6587 | } | ||
| 6588 | |||
| 6589 | // Abort if the struct already has a user-defined move constructor. | ||
| 6590 | // We can't just call lookupFunction with a non-const ref arg here, since the lookup permits | ||
| 6591 | // const-param-to-non-const-arg matching ("constify") and would return the copy ctor (if one exists) as | ||
| 6592 | // a false positive. Instead, check the function manifestations directly for a single-self-non-const-ref ctor. | ||
| 6593 | 2313 | if hasMoveCtor(structScope) { | |
| 6594 | 4 | return; | |
| 6595 | } | ||
| 6596 | |||
| 6597 | // Abort if the struct has a user-defined copy ctor. Reason: in Spice the move-vs-copy tie-breaker picks the | ||
| 6598 | // non-const-ref (move) candidate for non-const lvalue arguments, so silently auto-generating a move ctor on | ||
| 6599 | // top of a user-defined copy ctor would change the behavior of existing `T b = T(a)` call sites (a's heap | ||
| 6600 | // contents would be stolen instead of deep-copied). Users that want a default move ctor in addition to their | ||
| 6601 | // own copy ctor can write an empty `p T.ctor(T& other) {}` to opt in. We still synthesize the default move | ||
| 6602 | // ctor when only the auto-generated copy ctor exists (heap-owning struct with no user ctors) - the user did | ||
| 6603 | // not write any binding semantics in that case, so picking move for non-const lvalues is fine. | ||
| 6604 | 2309 | if hasUserCopyCtor(structScope) { | |
| 6605 | 329 | return; | |
| 6606 | } | ||
| 6607 | 1980 | const SymbolTableEntry* structEntry = spiceStruct.structBase.entry; | |
| 6608 | 1980 | const QualType structType = structEntry.getQualType(); | |
| 6609 | |||
| 6610 | // Check if we have fields, that require us to do anything in the move ctor | ||
| 6611 | 1980 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6612 | 1980 | bool moveCtorRequired = false; | |
| 6613 | 6740 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6614 | 4760 | const SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6615 | 4760 | assert fieldSymbol != nil<const SymbolTableEntry*> && fieldSymbol.declNode != nil<ASTNode*>; | |
| 6616 | |||
| 6617 | 4760 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6618 | 4760 | if fieldType.hasAnyGenericParts() && !spiceStruct.structBase.typeMapping.isEmpty() { | |
| 6619 | ✗ | substantiateTypeWithTypeMapping(fieldType, spiceStruct.structBase.typeMapping, fieldSymbol.declNode); | |
| 6620 | ✗ | } | |
| 6621 | |||
| 6622 | // If the field is of type struct, check whether the field's struct has its own move ctor. We use | ||
| 6623 | // findMoveCtor (a direct scan of the manifestations) rather than matchFunction here for two | ||
| 6624 | // reasons: (1) match permits const-param-to-non-const-arg "constify" matching and would return the | ||
| 6625 | // field's copy ctor as a false positive, and (2) match populates the lookup cache, which would inflate | ||
| 6626 | // the per-struct cache-miss counts even for structs that have nothing to do with move ctors. | ||
| 6627 | 4760 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6628 | 465 | Scope* bodyScope = fieldType.getBodyScope(); | |
| 6629 | 465 | moveCtorRequired = moveCtorRequired || (findMoveCtor(bodyScope) != nil<Function*>); | |
| 6630 | 465 | } | |
| 6631 | |||
| 6632 | // If we have an owning heap pointer, we transfer ownership of the heap storage | ||
| 6633 | 4760 | if fieldType.isHeap() { | |
| 6634 | 14 | assert fieldType.isPtr(); | |
| 6635 | 14 | moveCtorRequired = true; | |
| 6636 | 14 | } | |
| 6637 | 4760 | } | |
| 6638 | |||
| 6639 | // If we don't have any fields that require us to do anything special in the move ctor, we can skip it. | ||
| 6640 | // Unlike the copy ctor we do NOT generate a default move ctor just because the struct emits a vtable - the | ||
| 6641 | // vtable pointer is part of the struct layout and gets shallow-copied along with everything else, no | ||
| 6642 | // special move handling needed. Users that want to move a vtable-bearing struct fall back to the default | ||
| 6643 | // copy ctor. | ||
| 6644 | 1980 | if !moveCtorRequired { | |
| 6645 | 1959 | return; | |
| 6646 | } | ||
| 6647 | |||
| 6648 | // Create the default move ctor function | ||
| 6649 | 21 | const String entryName = getSymbolTableEntryNameDefaultMoveCtor(node.codeLoc); | |
| 6650 | 21 | const QualType nonConstStructType = structType.toNonConst(); | |
| 6651 | 21 | ParamList paramTypes; | |
| 6652 | 21 | paramTypes.pushBack(Param{nonConstStructType.toRef(node), false}); | |
| 6653 | 21 | createDefaultStructMethod(spiceStruct, entryName, String(CTOR_FUNCTION_NAME), paramTypes); | |
| 6654 | 21 | } | |
| 6655 | |||
| 6656 | /** | ||
| 6657 | * Checks if the given struct scope already has a user-defined destructor and creates a default one if not. | ||
| 6658 | * | ||
| 6659 | * For generating a default dtor, the following conditions need to be met: | ||
| 6660 | * - No user-defined dtor | ||
| 6661 | * | ||
| 6662 | * @param spiceStruct Struct instance | ||
| 6663 | * @param structScope Scope of the struct | ||
| 6664 | */ | ||
| 6665 | 5354 | p createDefaultDtorIfRequired(const Struct& spiceStruct, Scope* structScope) { | |
| 6666 | 5354 | const ASTNode* node = spiceStruct.structBase.declNode; | |
| 6667 | 5354 | assert structScope != nil<Scope*> && structScope.scopeType == ScopeType::STRUCT; | |
| 6668 | 5354 | SourceFile* sourceFile = structScope.sourceFile; | |
| 6669 | |||
| 6670 | // Abort if the struct already has a destructor. See createDefaultCtorIfRequired for why the name registry is asked | ||
| 6671 | // first and the struct scope only on top of it. A registry entry that stems from the default dtor of the generic | ||
| 6672 | // preset does not count for a manifestation, that has no dtor in its scope: Such a manifestation was substantiated | ||
| 6673 | // before the preset got its default dtor, so it could not inherit it through its copied scope and has to create its own. | ||
| 6674 | 5354 | const SymbolTableEntry* structEntry = spiceStruct.structBase.entry; | |
| 6675 | 5354 | const QualType structType = structEntry.getQualType(); | |
| 6676 | 5354 | const String fqFctName = structType.getSubType() + MEMBER_ACCESS_TOKEN + DTOR_FUNCTION_NAME; | |
| 6677 | 5354 | const NameRegistryEntry* registryEntry = sourceFile.getNameRegistryEntry(fqFctName); | |
| 6678 | 5354 | if hasDtor(structScope) { | |
| 6679 | 1137 | return; | |
| 6680 | } | ||
| 6681 | 4217 | if registryEntry != nil<const NameRegistryEntry*> { | |
| 6682 | 6 | const bool isPresetDefaultDtor = spiceStruct.structBase.isGenericSubstantiation() && registryEntry.targetEntry.name == getSymbolTableEntryNameDefaultDtor(node.codeLoc); | |
| 6683 | 6 | if !isPresetDefaultDtor { | |
| 6684 | 1 | return; | |
| 6685 | } | ||
| 6686 | 5 | } | |
| 6687 | |||
| 6688 | // Check we have field types, that require use to do anything in the destructor | ||
| 6689 | 4216 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6690 | 4216 | bool hasFieldsToDeAllocate = false; | |
| 6691 | 4216 | bool hasFieldsToDestruct = false; | |
| 6692 | 14226 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6693 | 10010 | const SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6694 | 10010 | assert fieldSymbol != nil<const SymbolTableEntry*> && fieldSymbol.declNode != nil<ASTNode*>; | |
| 6695 | |||
| 6696 | 10010 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6697 | 10010 | if fieldType.hasAnyGenericParts() && !spiceStruct.structBase.typeMapping.isEmpty() { | |
| 6698 | 206 | substantiateTypeWithTypeMapping(fieldType, spiceStruct.structBase.typeMapping, fieldSymbol.declNode); | |
| 6699 | 206 | } | |
| 6700 | |||
| 6701 | 10010 | hasFieldsToDeAllocate = hasFieldsToDeAllocate || fieldType.needsDeAllocation(); | |
| 6702 | 10010 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6703 | 1495 | Scope* fieldScope = fieldType.getBodyScope(); | |
| 6704 | // While the outer struct is still a generic preset, a field type that is itself generic (e.g. | ||
| 6705 | // RedBlackTree<V, bool>, the internal field of Set<V>) cannot be matched to a concrete dtor yet. Since a | ||
| 6706 | // dtor takes no arguments, matching it against the still-generic 'this' type would partially substantiate a | ||
| 6707 | // bogus manifestation (e.g. RedBlackTree<K, bool>, with K left generic) that is wrongly treated as fully | ||
| 6708 | // substantiated later on and crashes name mangling during IR generation. In that case we only record that | ||
| 6709 | // the field has a dtor via a direct scan; the concrete dtor is matched later, per manifestation, in | ||
| 6710 | // createDtorBodyPreamble. | ||
| 6711 | 1495 | if fieldType.hasAnyGenericParts() { | |
| 6712 | 133 | hasFieldsToDestruct = hasFieldsToDestruct || hasDtor(fieldScope); | |
| 6713 | 133 | } else { | |
| 6714 | // Lookup dtor function | ||
| 6715 | 1362 | const ArgList noArgs; | |
| 6716 | 1362 | const QualTypeList noTemplateTypeHints; | |
| 6717 | 1362 | const Function* dtorFct = matchFunction(fieldScope, String(DTOR_FUNCTION_NAME), fieldType, noArgs, noTemplateTypeHints, true, node); | |
| 6718 | 1362 | hasFieldsToDestruct = hasFieldsToDestruct || (dtorFct != nil<const Function*>); | |
| 6719 | 1362 | requestRevisitIfRequired(dtorFct); | |
| 6720 | 1362 | } | |
| 6721 | 1495 | } | |
| 6722 | 10010 | } | |
| 6723 | |||
| 6724 | // If we don't have any fields, that require us to do anything in the dtor, we can skip it | ||
| 6725 | 4216 | if !hasFieldsToDeAllocate && !hasFieldsToDestruct { | |
| 6726 | 3690 | return; | |
| 6727 | } | ||
| 6728 | |||
| 6729 | // Create the default dtor function. The memory runtime that the generated body needs for its de-allocations is not | ||
| 6730 | // requested here, but only when that body is prepared (see createDtorBodyPreamble) - see the comment there. | ||
| 6731 | 526 | const String entryName = getSymbolTableEntryNameDefaultDtor(node.codeLoc); | |
| 6732 | 526 | const ParamList noParams; | |
| 6733 | 526 | createDefaultStructMethod(spiceStruct, entryName, String(DTOR_FUNCTION_NAME), noParams); | |
| 6734 | 5354 | } | |
| 6735 | |||
| 6736 | /** | ||
| 6737 | * Prepare the generation of the ctor body preamble. This preamble is used to initialize the VTable, construct or initialize | ||
| 6738 | * fields. | ||
| 6739 | */ | ||
| 6740 | 5895 | p TypeChecker.createCtorBodyPreamble(const Scope* bodyScope) { | |
| 6741 | // Retrieve struct scope | ||
| 6742 | 5895 | Scope* structScope = bodyScope.parent; | |
| 6743 | 5895 | assert structScope != nil<Scope*>; | |
| 6744 | |||
| 6745 | 5895 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6746 | 21455 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6747 | 15560 | SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6748 | 15560 | assert fieldSymbol != nil<SymbolTableEntry*> && fieldSymbol.isField(); | |
| 6749 | 15560 | if fieldSymbol.isImplicitField { | |
| 6750 | 1079 | continue; | |
| 6751 | } | ||
| 6752 | |||
| 6753 | 14481 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6754 | 14481 | if fieldType.hasAnyGenericParts() { | |
| 6755 | ✗ | substantiateTypeWithTypeMapping(fieldType, this.typeMapping, fieldSymbol.declNode); | |
| 6756 | ✗ | } | |
| 6757 | |||
| 6758 | // A reference field has no default value to construct - it can only be bound by an explicit assignment in the | ||
| 6759 | // ctor body (e.g. Pair<K, V&>'s 'this.second = second;'), and that assignment is what should mark the field's | ||
| 6760 | // lifecycle as initialized (see the isDecl / initial-field-ref-assign logic in TypeChecker.visitAssignExpr and | ||
| 6761 | // IRGenerator.doAssignment). Marking it initialized here, before that binding assignment runs, would make the | ||
| 6762 | // compiler treat the binding as a reassignment through the reference instead of the initial binding - illegal | ||
| 6763 | // for a const reference field, and wrong even for a non-const one. | ||
| 6764 | 14481 | if fieldType.isRef() { | |
| 6765 | 723 | continue; | |
| 6766 | } | ||
| 6767 | |||
| 6768 | 13758 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6769 | 2462 | ASTNode* fieldNode = fieldSymbol.declNode; | |
| 6770 | 2462 | assert fieldNode.kind == ASTNodeKind::FIELD; | |
| 6771 | // Match ctor function, create the concrete manifestation and set it to used | ||
| 6772 | 2462 | Scope* matchScope = fieldType.getBodyScope(); | |
| 6773 | 2462 | const ArgList noArgs; | |
| 6774 | 2462 | const QualTypeList noTemplateTypeHints; | |
| 6775 | 2462 | const Function* spiceFunc = matchFunction(matchScope, String(CTOR_FUNCTION_NAME), fieldType, noArgs, noTemplateTypeHints, false, fieldNode); | |
| 6776 | 2462 | if spiceFunc != nil<const Function*> { | |
| 6777 | 2280 | const QualType& ctorThisType = spiceFunc.thisType; | |
| 6778 | 2280 | fieldSymbol.updateType(fieldType.getWithBodyScope(ctorThisType.getBodyScope()), true); | |
| 6779 | 2462 | } else if !fieldType.isTriviallyConstructible(fieldNode) { | |
| 6780 | 127 | continue; | |
| 6781 | 55 | } | |
| 6782 | 2462 | } | |
| 6783 | |||
| 6784 | 13631 | fieldSymbol.updateState(LifecycleState::INITIALIZED); | |
| 6785 | 13631 | } | |
| 6786 | 5895 | } | |
| 6787 | |||
| 6788 | /** | ||
| 6789 | * Prepare the generation of the copy ctor body preamble. This preamble is used to initialize the VTable, construct or initialize | ||
| 6790 | * fields. | ||
| 6791 | */ | ||
| 6792 | 1272 | p TypeChecker.createCopyCtorBodyPreamble(const Scope* bodyScope) { | |
| 6793 | // Retrieve struct scope | ||
| 6794 | 1272 | Scope* structScope = bodyScope.parent; | |
| 6795 | 1272 | assert structScope != nil<Scope*>; | |
| 6796 | |||
| 6797 | 1272 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6798 | 4395 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6799 | 3123 | SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6800 | 3123 | assert fieldSymbol != nil<SymbolTableEntry*> && fieldSymbol.isField(); | |
| 6801 | 3123 | if fieldSymbol.isImplicitField { | |
| 6802 | 121 | continue; | |
| 6803 | } | ||
| 6804 | |||
| 6805 | 3002 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6806 | 3002 | if fieldType.hasAnyGenericParts() { | |
| 6807 | ✗ | substantiateTypeWithTypeMapping(fieldType, this.typeMapping, fieldSymbol.declNode); | |
| 6808 | ✗ | } | |
| 6809 | |||
| 6810 | 3002 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6811 | 1679 | ASTNode* fieldNode = fieldSymbol.declNode; | |
| 6812 | 1679 | assert fieldNode.kind == ASTNodeKind::FIELD; | |
| 6813 | // Match ctor function, create the concrete manifestation and set it to used | ||
| 6814 | 1679 | Scope* matchScope = fieldType.getBodyScope(); | |
| 6815 | 1679 | ArgList args; | |
| 6816 | 1679 | args.pushBack(Pair<QualType, bool>(fieldType.toConstRef(fieldNode), false /* we always have the field as storage */)); | |
| 6817 | 1679 | const QualTypeList noTemplateTypeHints; | |
| 6818 | 1679 | const Function* copyCtorFct = matchFunction(matchScope, String(CTOR_FUNCTION_NAME), fieldType, args, noTemplateTypeHints, false, fieldNode); | |
| 6819 | 1679 | if copyCtorFct != nil<const Function*> { | |
| 6820 | 1173 | const QualType& ctorThisType = copyCtorFct.thisType; | |
| 6821 | 1173 | fieldSymbol.updateType(fieldType.getWithBodyScope(ctorThisType.getBodyScope()), true); | |
| 6822 | 1679 | } else if !fieldType.isTriviallyCopyable(fieldNode) { | |
| 6823 | ✗ | continue; | |
| 6824 | 506 | } | |
| 6825 | 1679 | } | |
| 6826 | |||
| 6827 | 3002 | fieldSymbol.updateState(LifecycleState::INITIALIZED); | |
| 6828 | 3002 | } | |
| 6829 | 1272 | } | |
| 6830 | |||
| 6831 | /** | ||
| 6832 | * Prepare the generation of the move ctor body preamble. This preamble is used to initialize the VTable, move-construct or | ||
| 6833 | * shallow-copy fields and transfer ownership of heap allocations. | ||
| 6834 | */ | ||
| 6835 | 36 | p TypeChecker.createMoveCtorBodyPreamble(const Scope* bodyScope) { | |
| 6836 | // Retrieve struct scope | ||
| 6837 | 36 | Scope* structScope = bodyScope.parent; | |
| 6838 | 36 | assert structScope != nil<Scope*>; | |
| 6839 | |||
| 6840 | 36 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6841 | 94 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6842 | 58 | SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(i)); | |
| 6843 | 58 | assert fieldSymbol != nil<SymbolTableEntry*> && fieldSymbol.isField(); | |
| 6844 | 58 | if fieldSymbol.isImplicitField { | |
| 6845 | ✗ | continue; | |
| 6846 | } | ||
| 6847 | |||
| 6848 | 58 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6849 | 58 | if fieldType.hasAnyGenericParts() { | |
| 6850 | ✗ | substantiateTypeWithTypeMapping(fieldType, this.typeMapping, fieldSymbol.declNode); | |
| 6851 | ✗ | } | |
| 6852 | |||
| 6853 | 58 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6854 | // Find a move ctor on the field's struct. We use findMoveCtor rather than matchFunction to | ||
| 6855 | // avoid a constify-based false positive returning the copy ctor. findMoveCtor doesn't mark the | ||
| 6856 | // function as used, so do that explicitly to ensure the inner move ctor's body is actually emitted. | ||
| 6857 | 18 | Scope* matchScope = fieldType.getBodyScope(); | |
| 6858 | 18 | Function* moveCtorFct = findMoveCtor(matchScope); | |
| 6859 | 18 | if moveCtorFct != nil<Function*> { | |
| 6860 | 18 | moveCtorFct.used = true; | |
| 6861 | 18 | if moveCtorFct.entry != nil<SymbolTableEntry*> { | |
| 6862 | 18 | moveCtorFct.entry.used = true; | |
| 6863 | 18 | } | |
| 6864 | 18 | const QualType& ctorThisType = moveCtorFct.thisType; | |
| 6865 | 18 | fieldSymbol.updateType(fieldType.getWithBodyScope(ctorThisType.getBodyScope()), true); | |
| 6866 | 18 | } | |
| 6867 | 18 | } | |
| 6868 | |||
| 6869 | 58 | fieldSymbol.updateState(LifecycleState::INITIALIZED); | |
| 6870 | 58 | } | |
| 6871 | 36 | } | |
| 6872 | |||
| 6873 | /** | ||
| 6874 | * Prepare the generation of the dtor body preamble. This preamble is used to destruct all fields and to free all heap fields. | ||
| 6875 | * | ||
| 6876 | * @param bodyScope Body scope of the dtor | ||
| 6877 | * @param node Struct definition node for error messages | ||
| 6878 | */ | ||
| 6879 | 1146 | p TypeChecker.createDtorBodyPreamble(const Scope* bodyScope, const ASTNode* node) { | |
| 6880 | // Retrieve struct scope | ||
| 6881 | 1146 | Scope* structScope = bodyScope.parent; | |
| 6882 | 1146 | assert structScope != nil<Scope*>; | |
| 6883 | |||
| 6884 | 1146 | const unsigned long fieldCount = structScope.getFieldCount(); | |
| 6885 | 1146 | bool hasFieldsToDeAllocate = false; | |
| 6886 | 4480 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 6887 | 3334 | const unsigned long fieldIdx = fieldCount - 1l - i; // Destruct fields in reverse order | |
| 6888 | 3334 | const SymbolTableEntry* fieldSymbol = structScope.lookupField(cast<unsigned int>(fieldIdx)); | |
| 6889 | 3334 | assert fieldSymbol != nil<const SymbolTableEntry*> && fieldSymbol.isField(); | |
| 6890 | 3334 | if fieldSymbol.isImplicitField { | |
| 6891 | 112 | continue; | |
| 6892 | } | ||
| 6893 | |||
| 6894 | 3222 | QualType fieldType = fieldSymbol.getQualType(); | |
| 6895 | 3222 | if fieldType.hasAnyGenericParts() { | |
| 6896 | ✗ | substantiateTypeWithTypeMapping(fieldType, this.typeMapping, fieldSymbol.declNode); | |
| 6897 | ✗ | } | |
| 6898 | |||
| 6899 | 3222 | hasFieldsToDeAllocate = hasFieldsToDeAllocate || fieldType.needsDeAllocation(); | |
| 6900 | |||
| 6901 | 3222 | if fieldType.is(SuperType::TY_STRUCT) { | |
| 6902 | 1815 | ASTNode* fieldNode = fieldSymbol.declNode; | |
| 6903 | 1815 | assert fieldNode.kind == ASTNodeKind::FIELD; | |
| 6904 | // Match ctor function, create the concrete manifestation and set it to used | ||
| 6905 | 1815 | Scope* matchScope = fieldType.getBodyScope(); | |
| 6906 | 1815 | const ArgList noArgs; | |
| 6907 | 1815 | const QualTypeList noTemplateTypeHints; | |
| 6908 | 1815 | matchFunction(matchScope, String(DTOR_FUNCTION_NAME), fieldType, noArgs, noTemplateTypeHints, false, fieldNode); | |
| 6909 | 1815 | } | |
| 6910 | 3222 | } | |
| 6911 | |||
| 6912 | // Request the memory runtime for the de-allocations that the generated body will contain, and mark its sDealloc as | ||
| 6913 | // used so that a definition is emitted for it. This deliberately happens here and not where the dtor is created | ||
| 6914 | // (createDefaultDtorIfRequired): a manifestation can come into existence while memory_rt.spice is itself still being | ||
| 6915 | // pre-checked - sAlloc() returns Result<heap byte*>, so the very first Result manifestation is substantiated from | ||
| 6916 | // memory_rt's own signature list. Asking that half-prepared file for sDealloc, which is declared further down in it, | ||
| 6917 | // would come up empty. By the time a default member's body is prepared, every runtime module is fully pre-checked. | ||
| 6918 | // The string runtime does not use sDealloc, but frees manually to avoid a circular dependency. | ||
| 6919 | 1146 | SourceFile* structSourceFile = structScope.sourceFile; | |
| 6920 | 1146 | if hasFieldsToDeAllocate && !structSourceFile.isStringRT() { | |
| 6921 | 497 | SourceFile* memoryRT = structSourceFile.requestRuntimeModule(RuntimeModule::MEMORY_RT); | |
| 6922 | 497 | assert memoryRT != nil<SourceFile*>; | |
| 6923 | 497 | Scope* matchScope = memoryRT.getGlobalScope(); | |
| 6924 | // Set dealloc function to used | ||
| 6925 | 497 | const QualType thisType = QualType(SuperType::TY_DYN); | |
| 6926 | 497 | const QualType byteType = QualType(SuperType::TY_BYTE); | |
| 6927 | 497 | const QualType bytePtrType = byteType.toPtr(node); | |
| 6928 | 497 | QualType bytePtrRefType = bytePtrType.toRef(node); | |
| 6929 | 497 | bytePtrRefType.makeHeap(); | |
| 6930 | 497 | ArgList args; | |
| 6931 | 497 | args.pushBack(Pair<QualType, bool>(bytePtrRefType, false /* we always have the field as storage */)); | |
| 6932 | 497 | const QualTypeList noTemplateTypeHints; | |
| 6933 | 497 | Function* deallocFct = matchFunction(matchScope, String(FCT_NAME_DEALLOC), thisType, args, noTemplateTypeHints, true, node); | |
| 6934 | 497 | assert deallocFct != nil<Function*>; | |
| 6935 | 497 | deallocFct.used = true; | |
| 6936 | 497 | } | |
| 6937 | 1146 | } | |
| 6938 | |||
| 6939 | /** | ||
| 6940 | * Prepare the generation of a call to a method of a given struct | ||
| 6941 | * | ||
| 6942 | * @param entry Symbol entry to use as 'this' pointer for the method call | ||
| 6943 | * @param methodName Name of the method to call | ||
| 6944 | * @param args Provided arguments by the caller | ||
| 6945 | * @param node AST node | ||
| 6946 | */ | ||
| 6947 | 7015 | public f<Function*> TypeChecker.implicitlyCallStructMethod(const SymbolTableEntry* entry, const String& methodName, const ArgList& args, const ASTNode* node) { | |
| 6948 | 7015 | const QualType entryType = entry.getQualType(); | |
| 6949 | 7015 | const QualType unwrappedType = entryType.removeReferenceWrapper(); | |
| 6950 | 7015 | const QualType thisType = unwrappedType.toNonConst(); | |
| 6951 | 7015 | return this.implicitlyCallStructMethod(thisType, methodName, args, node); | |
| 6952 | } | ||
| 6953 | |||
| 6954 | /** | ||
| 6955 | * Prepare the generation of a call to a method of a given struct | ||
| 6956 | * | ||
| 6957 | * @param thisType Struct type to call the method on | ||
| 6958 | * @param methodName Name of the method to call | ||
| 6959 | * @param args Provided arguments by the caller | ||
| 6960 | * @param node AST node | ||
| 6961 | */ | ||
| 6962 | 8340 | public f<Function*> TypeChecker.implicitlyCallStructMethod(const QualType& origThisType, const String& methodName, const ArgList& args, const ASTNode* node) { | |
| 6963 | 8340 | assert origThisType.is(SuperType::TY_STRUCT); | |
| 6964 | 8340 | Scope* matchScope = origThisType.getBodyScope(); | |
| 6965 | 8340 | assert matchScope.scopeType == ScopeType::STRUCT; | |
| 6966 | |||
| 6967 | // Search for dtor | ||
| 6968 | 8340 | QualType thisType = origThisType; | |
| 6969 | 8340 | if matchScope.isImportedBy(this.rootScope) { | |
| 6970 | 6136 | thisType = this.mapLocalTypeToImportedScopeType(matchScope, thisType); | |
| 6971 | 6136 | } | |
| 6972 | 8340 | const QualTypeList noTemplateTypeHints; | |
| 6973 | 16680 | return matchFunction(matchScope, methodName, thisType, args, noTemplateTypeHints, true, node); | |
| 6974 | 8340 | } | |
| 6975 | |||
| 6976 | /** | ||
| 6977 | * Prepare the generation of a call to the copy ctor of a given struct | ||
| 6978 | * | ||
| 6979 | * @param thisType Struct type to call the copy ctor on | ||
| 6980 | * @param node Current AST node | ||
| 6981 | */ | ||
| 6982 | 1281 | public f<Function*> TypeChecker.implicitlyCallStructCopyCtor(const QualType& thisType, const ASTNode* node) { | |
| 6983 | 1281 | const QualType unwrappedType = thisType.removeReferenceWrapper(); | |
| 6984 | 1281 | const QualType argType = unwrappedType.toConstRef(node); | |
| 6985 | 1281 | ArgList args; | |
| 6986 | 1281 | args.pushBack(Pair<QualType, bool>(argType, false /* we always have an entry here */)); | |
| 6987 | // The new instance being constructed is never const, regardless of whether the copy source is const | ||
| 6988 | 1281 | const QualType nonConstThisType = thisType.toNonConst(); | |
| 6989 | 2562 | return this.implicitlyCallStructMethod(nonConstThisType, String(CTOR_FUNCTION_NAME), args, node); | |
| 6990 | 1281 | } | |
| 6991 | |||
| 6992 | /** | ||
| 6993 | * Prepare the generation of a call to the dtor of a given struct | ||
| 6994 | * | ||
| 6995 | * @param entry Symbol entry to use as 'this' pointer for the dtor call | ||
| 6996 | * @param resources Resources to clean up at the end of the current scope | ||
| 6997 | * @param node Current AST node for error messages | ||
| 6998 | */ | ||
| 6999 | 6982 | p TypeChecker.implicitlyCallStructDtor(SymbolTableEntry* entry, ResourcesForManifestationToCleanup& resources, const ASTNode* node) { | |
| 7000 | // Add the dtor to the resources to clean up to call it later in codegen | ||
| 7001 | 6982 | const ArgList noArgs; | |
| 7002 | 6982 | Function* dtor = this.implicitlyCallStructMethod(entry, String(DTOR_FUNCTION_NAME), noArgs, node); | |
| 7003 | 6982 | if dtor != nil<Function*> { | |
| 7004 | 5907 | resources.dtorFunctionsToCall.pushBack(Pair<SymbolTableEntry*, Function*>(entry, dtor)); | |
| 7005 | 5907 | } | |
| 7006 | 6982 | } | |
| 7007 | |||
| 7008 | /** | ||
| 7009 | * Prepare the generation of a call to the deallocate function for a heap-allocated variable | ||
| 7010 | * | ||
| 7011 | * @param node Current AST node for error messages | ||
| 7012 | */ | ||
| 7013 | 494 | p TypeChecker.implicitlyCallDeallocate(const ASTNode* node) { | |
| 7014 | 494 | SourceFile* memoryRT = this.sourceFile.requestRuntimeModule(RuntimeModule::MEMORY_RT); | |
| 7015 | 494 | assert memoryRT != nil<SourceFile*>; | |
| 7016 | 494 | Scope* matchScope = memoryRT.getGlobalScope(); | |
| 7017 | // Set dealloc function to used | ||
| 7018 | 494 | const QualType thisType = QualType(SuperType::TY_DYN); | |
| 7019 | 494 | const QualType byteType = QualType(SuperType::TY_BYTE); | |
| 7020 | 494 | const QualType bytePtrType = byteType.toPtr(node); | |
| 7021 | 494 | QualType bytePtrRefType = bytePtrType.toRef(node); | |
| 7022 | 494 | bytePtrRefType.makeHeap(); | |
| 7023 | 494 | ArgList args; | |
| 7024 | 494 | args.pushBack(Pair<QualType, bool>(bytePtrRefType, false /* we always have the field as storage */)); | |
| 7025 | 494 | const QualTypeList noTemplateTypeHints; | |
| 7026 | 494 | Function* deallocFct = matchFunction(matchScope, String(FCT_NAME_DEALLOC), thisType, args, noTemplateTypeHints, true, node); | |
| 7027 | 494 | assert deallocFct != nil<Function*>; | |
| 7028 | 494 | deallocFct.used = true; | |
| 7029 | 494 | } | |
| 7030 | |||
| 7031 | /** | ||
| 7032 | * Sort the given variables in reverse declaration order. This is the order in which their dtors are called. | ||
| 7033 | * | ||
| 7034 | * @param vars Variables to sort | ||
| 7035 | */ | ||
| 7036 | 151617 | p TypeChecker.sortByReverseDeclarationOrder(Vector<SymbolTableEntry*>& vars) { | |
| 7037 | // Stable insertion sort | ||
| 7038 | 165288 | for unsigned long i = 1l; i < vars.getSize(); i++ { | |
| 7039 | 13671 | SymbolTableEntry* current = vars.get(i); | |
| 7040 | 13671 | unsigned long j = i; | |
| 7041 | 77951 | while j > 0l && this.isDeclaredAfter(current, vars.get(j - 1l)) { | |
| 7042 | 64280 | SymbolTableEntry*& slot = vars.get(j); | |
| 7043 | 64280 | slot = vars.get(j - 1l); | |
| 7044 | 64280 | j--; | |
| 7045 | 64280 | } | |
| 7046 | 13671 | SymbolTableEntry*& targetSlot = vars.get(j); | |
| 7047 | 13671 | targetSlot = current; | |
| 7048 | 13671 | } | |
| 7049 | 151617 | } | |
| 7050 | |||
| 7051 | // Comparator of sortByReverseDeclarationOrder | ||
| 7052 | 73255 | f<bool> TypeChecker.isDeclaredAfter(const SymbolTableEntry* a, const SymbolTableEntry* b) { | |
| 7053 | 73255 | const ASTNode* aDeclNode = a.declNode; | |
| 7054 | 73255 | const ASTNode* bDeclNode = b.declNode; | |
| 7055 | // Primary sort criteria is the code location | ||
| 7056 | 73255 | if !(aDeclNode.codeLoc == bDeclNode.codeLoc) { | |
| 7057 | 73204 | return aDeclNode.codeLoc.isAfter(bDeclNode.codeLoc); | |
| 7058 | } | ||
| 7059 | // Secondary sort criteria is the node id | ||
| 7060 | 51 | return this.resourceManager.getNodeId(aDeclNode) > this.resourceManager.getNodeId(bDeclNode); | |
| 7061 | } | ||
| 7062 | |||
| 7063 | /** | ||
| 7064 | * Consider calls to destructors for the current scope | ||
| 7065 | * | ||
| 7066 | * @param resources Resources to clean up at the end of the current scope | ||
| 7067 | * @param node Node the current scope belongs to (a StmtLstNode or the head of a loop) | ||
| 7068 | */ | ||
| 7069 | 100419 | p TypeChecker.doScopeCleanup(ResourcesForManifestationToCleanup& resources, const ASTNode* node) { | |
| 7070 | // Get all variables, that are approved for de-allocation | ||
| 7071 | 100419 | Vector<SymbolTableEntry*> vars = this.currentScope.getVarsGoingOutOfScope(); | |
| 7072 | // Sort by reverse declaration order | ||
| 7073 | 100419 | this.sortByReverseDeclarationOrder(vars); | |
| 7074 | // Call the dtor of each variable. We call the dtor in reverse declaration order | ||
| 7075 | 139223 | foreach SymbolTableEntry* varEntry : vars { | |
| 7076 | 38804 | const QualType varType = varEntry.getQualType(); | |
| 7077 | // Check if we have a heap-allocated pointer | ||
| 7078 | 38804 | const dyn heapOptions = [SuperType::TY_PTR, SuperType::TY_STRING, SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 7079 | 38804 | if varType.isHeap() && varType.isOneOf(heapOptions, len(heapOptions)) { | |
| 7080 | // The memory runtime is ignored, because it manually allocates to avoid circular dependencies. | ||
| 7081 | // Same goes for the string runtime. | ||
| 7082 | 3951 | if this.sourceFile.isMemoryRT() || this.sourceFile.isStringRT() { | |
| 7083 | 3331 | continue; | |
| 7084 | } | ||
| 7085 | // If the local variable currently does not have the ownership, we must not deallocate its memory | ||
| 7086 | 620 | const Lifecycle& lifecycle = varEntry.getLifecycle(); | |
| 7087 | 620 | if !lifecycle.isInOwningState() { | |
| 7088 | 126 | continue; | |
| 7089 | } | ||
| 7090 | |||
| 7091 | 494 | this.implicitlyCallDeallocate(node); // Required to request the memory runtime | |
| 7092 | 494 | resources.heapVarsToFree.pushBack(varEntry); | |
| 7093 | 494 | } | |
| 7094 | // Only generate dtor call for structs | ||
| 7095 | 35347 | if !varType.is(SuperType::TY_STRUCT) { | |
| 7096 | 28365 | continue; | |
| 7097 | } | ||
| 7098 | // Variable must be either initialized or a struct field | ||
| 7099 | 6982 | if !varEntry.isInitialized() && varEntry.scope.scopeType != ScopeType::STRUCT { | |
| 7100 | ✗ | continue; | |
| 7101 | } | ||
| 7102 | // Call dtor | ||
| 7103 | 6982 | this.implicitlyCallStructDtor(varEntry, resources, node); | |
| 7104 | 6982 | } | |
| 7105 | 100419 | } | |
| 7106 | |||
| 7107 | /** | ||
| 7108 | * Consider calls to the destructors of the temporaries of the current expression scope | ||
| 7109 | * | ||
| 7110 | * @param node Expression, that owns the current expression scope | ||
| 7111 | */ | ||
| 7112 | 51198 | p TypeChecker.doExprScopeCleanup(IVisitable* node) { | |
| 7113 | 51198 | assert this.currentScope.scopeType == ScopeType::EXPR_BODY; | |
| 7114 | |||
| 7115 | // Get all temporaries that go out of scope. This is executed again if the expression is type-checked again | ||
| 7116 | 51198 | Vector<SymbolTableEntry*> temporaries = this.currentScope.getVarsGoingOutOfScope(); | |
| 7117 | 51198 | this.sortByReverseDeclarationOrder(temporaries); | |
| 7118 | 51198 | this.currentScope.temporaryDtorsToCall.clear(); | |
| 7119 | |||
| 7120 | // Call the dtor of each temporary. We call the dtor in reverse declaration order | ||
| 7121 | 51198 | ASTNode* baseNode = getASTNode(node); | |
| 7122 | 51231 | foreach SymbolTableEntry* temporary : temporaries { | |
| 7123 | // Only generate dtor call for initialized structs | ||
| 7124 | 33 | const QualType temporaryType = temporary.getQualType(); | |
| 7125 | 33 | if !temporaryType.is(SuperType::TY_STRUCT) || !temporary.isInitialized() { | |
| 7126 | ✗ | continue; | |
| 7127 | } | ||
| 7128 | 33 | const ArgList noArgs; | |
| 7129 | 33 | Function* dtor = this.implicitlyCallStructMethod(temporary, String(DTOR_FUNCTION_NAME), noArgs, baseNode); | |
| 7130 | 33 | if dtor != nil<Function*> { | |
| 7131 | 33 | this.currentScope.temporaryDtorsToCall.pushBack(Pair<SymbolTableEntry*, Function*>(temporary, dtor)); | |
| 7132 | 33 | } | |
| 7133 | 33 | } | |
| 7134 | 51198 | } | |
| 7135 | |||
| 7136 | /** | ||
| 7137 | * Visit an expression in its expression scope, if it has one, and consider calls to the destructors of its temporaries | ||
| 7138 | * | ||
| 7139 | * @param expr Expression to visit | ||
| 7140 | * @return Result of the expression | ||
| 7141 | */ | ||
| 7142 | 46909 | #[ignoreUnusedReturnValue] | |
| 7143 | 46909 | f<ExprResult> TypeChecker.visitInExprScope(IVisitable* expr) { | |
| 7144 | 46909 | ExprScopeHandle exprScopeHandle = ExprScopeHandle(&this.compilerPass, expr); | |
| 7145 | 46909 | const ExprResult exprResult = this.visitExpr(expr); | |
| 7146 | // If the expression is not resolved yet, it is visited again later on, which is when the temporaries are known | ||
| 7147 | 46909 | if exprScopeHandle.getExprScope() != nil<Scope*> && !exprResult.ty.is(SuperType::TY_UNRESOLVED) { | |
| 7148 | 46866 | this.doExprScopeCleanup(expr); | |
| 7149 | 46866 | } | |
| 7150 | 93818 | return exprResult; | |
| 7151 | 46909 | } | |
| 7152 |