symboltablebuilder/scope.spice
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | // Std imports | ||
| 4 | import "std/data/unordered-map"; | ||
| 5 | import "std/data/map"; | ||
| 6 | import "std/data/vector"; | ||
| 7 | import "std/data/pair"; | ||
| 8 | import "std/text/json/json-value"; | ||
| 9 | |||
| 10 | // Own imports | ||
| 11 | import "@/ast/ast-nodes"; | ||
| 12 | import "@/reader/code-loc"; | ||
| 13 | import "@/exception/semantic-error"; | ||
| 14 | import "@/symboltablebuilder/super-type"; | ||
| 15 | import "@/symboltablebuilder/symbol-table"; | ||
| 16 | import "@/symboltablebuilder/symbol-table-entry"; | ||
| 17 | import "@/symboltablebuilder/symbol-table-builder"; | ||
| 18 | import "@/symboltablebuilder/qual-type"; | ||
| 19 | import "@/model/function"; | ||
| 20 | import "@/model/generic-type"; | ||
| 21 | import "@/model/struct"; | ||
| 22 | import "@/typechecker/function-manager"; | ||
| 23 | import "@/typechecker/struct-manager"; | ||
| 24 | import "@/typechecker/interface-manager"; | ||
| 25 | import "@/typechecker/union-manager"; | ||
| 26 | import "@/source-file"; | ||
| 27 | import "@/util/common-util"; | ||
| 28 | import "@/util/compiler-warning"; | ||
| 29 | |||
| 30 | public type ScopeType enum { | ||
| 31 | GLOBAL, | ||
| 32 | FUNC_PROC_BODY, | ||
| 33 | LAMBDA_BODY, | ||
| 34 | STRUCT, | ||
| 35 | INTERFACE, | ||
| 36 | UNION, | ||
| 37 | ENUM, | ||
| 38 | IF_ELSE_BODY, | ||
| 39 | WHILE_BODY, | ||
| 40 | FOR_BODY, | ||
| 41 | FOREACH_BODY, | ||
| 42 | CASE_BODY, | ||
| 43 | DEFAULT_BODY, | ||
| 44 | UNSAFE_BODY, | ||
| 45 | ANONYMOUS_BLOCK_BODY, | ||
| 46 | EXPR_BODY, | ||
| 47 | LOOP_HEAD | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Represents an enclosed group of instructions, which are semantically separated from other scopes. | ||
| 52 | * In the source code, scopes usually are written as curly braces. | ||
| 53 | * | ||
| 54 | * Following language structures use scopes: | ||
| 55 | * - global scope | ||
| 56 | * - functions/procedures | ||
| 57 | * - structs | ||
| 58 | * - enums | ||
| 59 | * - interfaces | ||
| 60 | * - thread blocks | ||
| 61 | * - unsafe blocks | ||
| 62 | * - for loops | ||
| 63 | * - foreach loops | ||
| 64 | * - while loops | ||
| 65 | * - if statements | ||
| 66 | * - anonymous scopes | ||
| 67 | * - conditions, and expressions that are only evaluated conditionally (e.g. the right side of '&&' and '||') | ||
| 68 | */ | ||
| 69 | 262054 | public type Scope struct { | |
| 70 | // Public members | ||
| 71 | public Scope* parent | ||
| 72 | public SourceFile* sourceFile | ||
| 73 | public unsigned long scopeId | ||
| 74 | // All scopes are owned by the scope pool of the resource manager. They are stored as stable pointers into the pool rather than by value, so that adding a child | ||
| 75 | // never has to copy a Scope (which is not trivially copyable and must never be deep-copied implicitly). | ||
| 76 | public Map<String, Scope*> children // Ordered, since the order shows in the warnings | ||
| 77 | public SymbolTable symbolTable | ||
| 78 | 271440 | public const CodeLoc* codeLoc = nil<const CodeLoc*> | |
| 79 | public ScopeType scopeType | ||
| 80 | 271440 | public bool isGenericScope = false | |
| 81 | 271440 | public bool isAsyncScope = false | |
| 82 | 271440 | public bool isCtorScope = false | |
| 83 | 271440 | public bool isDtorScope = false | |
| 84 | // Only for expression scopes: dtor calls for the temporaries of the expression, in the order they are to be generated | ||
| 85 | public Vector<Pair<SymbolTableEntry*, Function*>> temporaryDtorsToCall | ||
| 86 | // Private members | ||
| 87 | FunctionRegistry functions | ||
| 88 | StructRegistry structs | ||
| 89 | InterfaceRegistry interfaces | ||
| 90 | UnionRegistry unions | ||
| 91 | UnorderedMap<String, GenericType> genericTypes | ||
| 92 | } | ||
| 93 | |||
| 94 | 271440 | public p Scope.ctor(Scope* parent, SourceFile* sourceFile, ScopeType scopeType, const CodeLoc* codeLoc) { | |
| 95 | 271440 | this.parent = parent; | |
| 96 | 271440 | this.sourceFile = sourceFile; | |
| 97 | 271440 | this.scopeId = sourceFile.resourceManager.getNextScopeId(); | |
| 98 | 271440 | this.scopeType = scopeType; | |
| 99 | 271440 | this.symbolTable = SymbolTable(parent == nil<Scope*> ? nil<SymbolTable*> : &parent.symbolTable, this); | |
| 100 | 271440 | this.codeLoc = codeLoc; | |
| 101 | 271440 | } | |
| 102 | |||
| 103 | // ======================================================== Accessors ========================================================= | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Get the source file this scope belongs to | ||
| 107 | */ | ||
| 108 | 3438 | public f<SourceFile*> Scope.getSourceFile() { | |
| 109 | 3438 | return this.sourceFile; | |
| 110 | } | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Get the kind of this scope | ||
| 114 | */ | ||
| 115 | 112402 | public f<ScopeType> Scope.getScopeType() { | |
| 116 | 112402 | return this.scopeType; | |
| 117 | } | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Collect all warnings, produced within this scope | ||
| 121 | * | ||
| 122 | * @param warnings List of warnings | ||
| 123 | * @return Collection of warnings | ||
| 124 | */ | ||
| 125 | 7668 | public p Scope.collectWarnings(Vector<CompilerWarning>& warnings) { | |
| 126 | 7668 | const dyn genericCandidates = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE, SuperType::TY_STRUCT, SuperType::TY_INTERFACE, SuperType::TY_UNION]; | |
| 127 | 7668 | const dyn fctOrProc = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 128 | |||
| 129 | // Visit own symbols | ||
| 130 | 25117 | foreach Pair<const String&, SymbolTableEntry&> symbol : this.symbolTable.symbols { | |
| 131 | 17449 | const SymbolTableEntry& entry = symbol.getSecond(); | |
| 132 | // Do not produce a warning if the symbol is used or has a special name | ||
| 133 | 17449 | const String& name = entry.name; | |
| 134 | 17449 | if entry.used || name.startsWith(UNUSED_VARIABLE_NAME) || name == THIS_VARIABLE_NAME { | |
| 135 | 17132 | continue; | |
| 136 | } | ||
| 137 | |||
| 138 | 317 | const QualType entryType = entry.getQualType(); | |
| 139 | |||
| 140 | // When compiling a static or shared library, publicly accessible symbols form part of the library's exported | ||
| 141 | // API. They may legitimately go unused within the library itself, since external consumers are expected to | ||
| 142 | // use them, so do not report them as unused. isPublic() is only defined for the type categories checked here | ||
| 143 | // (see the assertion in QualType::isPublic()), so guard the call accordingly. | ||
| 144 | 317 | const bool isPublicApiCandidate = entryType.isExtendedPrimitive() || entryType.is(SuperType::TY_ENUM); | |
| 145 | 317 | if isPublicApiCandidate && entryType.isPublic() && this.sourceFile.isLibraryOutput() { | |
| 146 | 2 | continue; | |
| 147 | } | ||
| 148 | |||
| 149 | // Determine warning type and message by the scope type and the symbol type | ||
| 150 | 315 | CompilerWarningType warningType = CompilerWarningType::UNUSED_VARIABLE; | |
| 151 | 315 | String warningMessage; | |
| 152 | 315 | switch this.scopeType { | |
| 153 | case ScopeType::GLOBAL: { | ||
| 154 | // Skip generic function/procedure/struct/interface/union entries | ||
| 155 | 18 | const QualTypeList entryTemplateTypes = entryType.getTemplateTypes(); | |
| 156 | 18 | if entryType.isOneOf(genericCandidates, len(genericCandidates)) && !entryTemplateTypes.isEmpty() { | |
| 157 | ✗ | continue; | |
| 158 | } | ||
| 159 | |||
| 160 | 18 | if entryType.is(SuperType::TY_FUNCTION) { | |
| 161 | 3 | Vector<Function*>* fctManifestations = entry.declNode.getFctManifestations(name); | |
| 162 | 3 | warningType = CompilerWarningType::UNUSED_FUNCTION; | |
| 163 | 3 | const Function* firstManifestation = fctManifestations.front(); | |
| 164 | 3 | warningMessage = "'" + firstManifestation.getSignature() + "' is unused"; | |
| 165 | 18 | } else if entryType.is(SuperType::TY_PROCEDURE) { | |
| 166 | 4 | Vector<Function*>* fctManifestations = entry.declNode.getFctManifestations(name); | |
| 167 | 4 | warningType = CompilerWarningType::UNUSED_PROCEDURE; | |
| 168 | 4 | const Function* firstManifestation = fctManifestations.front(); | |
| 169 | 4 | warningMessage = "'" + firstManifestation.getSignature() + "' is unused"; | |
| 170 | 15 | } else if entryType.is(SuperType::TY_STRUCT) { | |
| 171 | 1 | warningType = CompilerWarningType::UNUSED_STRUCT; | |
| 172 | 1 | warningMessage = "The struct '" + entry.name + "' is unused"; | |
| 173 | 11 | } else if entryType.is(SuperType::TY_INTERFACE) { | |
| 174 | 1 | warningType = CompilerWarningType::UNUSED_INTERFACE; | |
| 175 | 1 | warningMessage = "The interface '" + entry.name + "' is unused"; | |
| 176 | 10 | } else if entryType.is(SuperType::TY_UNION) { | |
| 177 | 1 | warningType = CompilerWarningType::UNUSED_UNION; | |
| 178 | 1 | warningMessage = "The union '" + entry.name + "' is unused"; | |
| 179 | 9 | } else if entryType.is(SuperType::TY_ENUM) { | |
| 180 | 2 | continue; // Do not report unused enums. Only unused enum items are reported | |
| 181 | 6 | } else if entryType.is(SuperType::TY_IMPORT) { | |
| 182 | 5 | warningType = CompilerWarningType::UNUSED_IMPORT; | |
| 183 | 5 | warningMessage = "The import '" + entry.name + "' is unused"; | |
| 184 | 6 | } else if entryType.is(SuperType::TY_ALIAS) { | |
| 185 | 1 | warningType = CompilerWarningType::UNUSED_ALIAS; | |
| 186 | 1 | warningMessage = "The type alias '" + entry.name + "' is unused"; | |
| 187 | 1 | } else { | |
| 188 | ✗ | warningType = CompilerWarningType::UNUSED_VARIABLE; | |
| 189 | ✗ | warningMessage = "The variable '" + entry.name + "' is unused"; | |
| 190 | 13 | } | |
| 191 | 18 | } | |
| 192 | case ScopeType::STRUCT, ScopeType::INTERFACE, ScopeType::UNION: { | ||
| 193 | 211 | if entry.isField() { | |
| 194 | 59 | warningType = CompilerWarningType::UNUSED_FIELD; | |
| 195 | 59 | warningMessage = "The field '" + entry.name + "' is unused"; | |
| 196 | 211 | } else if entryType.isOneOf(fctOrProc, len(fctOrProc)) { | |
| 197 | // Skip implicit method entries and generic templates | ||
| 198 | 152 | Vector<Function*>* fctManifestations = entry.declNode.getFctManifestations(name); | |
| 199 | 152 | if fctManifestations.isEmpty() { | |
| 200 | 138 | continue; | |
| 201 | } | ||
| 202 | 14 | const Function* firstManifestation = fctManifestations.front(); | |
| 203 | 14 | if firstManifestation.implicitDefault { | |
| 204 | ✗ | continue; | |
| 205 | } | ||
| 206 | // The manifestations of a generic struct share the method declarations. So the method is used, if it | ||
| 207 | // is used for any of them | ||
| 208 | 14 | bool isAnyManifestationUsed = false; | |
| 209 | 28 | foreach const Function* manifestation : *fctManifestations { | |
| 210 | 20 | if manifestation.used { | |
| 211 | 6 | isAnyManifestationUsed = true; | |
| 212 | 6 | break; | |
| 213 | } | ||
| 214 | 14 | } | |
| 215 | 14 | if isAnyManifestationUsed { | |
| 216 | 6 | continue; | |
| 217 | } | ||
| 218 | |||
| 219 | 8 | warningType = CompilerWarningType::UNUSED_METHOD; | |
| 220 | 8 | warningMessage = "The method '" + firstManifestation.getSignature() + "' is unused"; | |
| 221 | 8 | } | |
| 222 | 67 | } | |
| 223 | case ScopeType::ENUM: { | ||
| 224 | 3 | warningType = CompilerWarningType::UNUSED_ENUM_ITEM; | |
| 225 | 3 | warningMessage = "The enum item '" + entry.name + "' is unused"; | |
| 226 | 3 | } | |
| 227 | default: { | ||
| 228 | 83 | warningType = CompilerWarningType::UNUSED_VARIABLE; | |
| 229 | 83 | warningMessage = "The variable '" + entry.name + "' is unused"; | |
| 230 | 83 | } | |
| 231 | } | ||
| 232 | |||
| 233 | // Add warning | ||
| 234 | 169 | warnings.pushBack(CompilerWarning(entry.getDeclCodeLoc(), warningType, warningMessage.getRaw())); | |
| 235 | 315 | } | |
| 236 | |||
| 237 | // Visit children | ||
| 238 | 14794 | foreach Pair<const String&, Scope*&> childEntry : this.children { | |
| 239 | 7126 | Scope* childScope = childEntry.getSecond(); | |
| 240 | 7126 | if !childScope.isGenericScope { | |
| 241 | 6831 | childScope.collectWarnings(warnings); | |
| 242 | 6831 | } | |
| 243 | 7126 | } | |
| 244 | 7668 | } | |
| 245 | |||
| 246 | /** | ||
| 247 | * Checks if all variables of this and all child scopes are of an explicit type. | ||
| 248 | * This is executed after type inference to check that all variables could be inferred correctly. | ||
| 249 | */ | ||
| 250 | 278902 | public p Scope.ensureSuccessfulTypeInference() { | |
| 251 | // Check symbols in this scope | ||
| 252 | 656356 | foreach Pair<const String&, SymbolTableEntry&> symbol : this.symbolTable.symbols { | |
| 253 | 377454 | const SymbolTableEntry& entry = symbol.getSecond(); | |
| 254 | 377454 | const QualType entryType = entry.getQualType(); | |
| 255 | 377454 | if entryType.is(SuperType::TY_DYN) { | |
| 256 | 1 | const String message = "For the variable '" + symbol.getFirst() + "' no type could be inferred"; | |
| 257 | 1 | abortWithSemanticError(entry.declNode, SemanticErrorType::UNEXPECTED_DYN_TYPE, message.getRaw()); | |
| 258 | 1 | } | |
| 259 | 377454 | } | |
| 260 | |||
| 261 | // Check child scopes | ||
| 262 | 554107 | foreach Pair<const String&, Scope*&> childEntry : this.children { | |
| 263 | 275205 | Scope* childScope = childEntry.getSecond(); | |
| 264 | 275205 | childScope.ensureSuccessfulTypeInference(); | |
| 265 | 275205 | } | |
| 266 | 278902 | } | |
| 267 | |||
| 268 | /** | ||
| 269 | * Get the number of field symbols in this (struct) scope | ||
| 270 | */ | ||
| 271 | 270340 | public f<unsigned long> Scope.getFieldCount() { | |
| 272 | 270340 | assert this.scopeType == ScopeType::STRUCT || this.scopeType == ScopeType::UNION; | |
| 273 | 270340 | return this.symbolTable.getFieldCount(); | |
| 274 | } | ||
| 275 | |||
| 276 | /** | ||
| 277 | * Insert a new symbol into the current scope | ||
| 278 | * | ||
| 279 | * @param name Name of the symbol | ||
| 280 | * @param declNode AST node where the symbol is declared | ||
| 281 | * @return Inserted entry | ||
| 282 | */ | ||
| 283 | 220093 | #[ignoreUnusedReturnValue] | |
| 284 | 220093 | public f<SymbolTableEntry*> Scope.insert(const String& name, const ASTNode* declNode) { | |
| 285 | 220093 | return this.symbolTable.insert(name, declNode); | |
| 286 | } | ||
| 287 | |||
| 288 | /** | ||
| 289 | * Check if a symbol exists in the current or any parent scope | ||
| 290 | * | ||
| 291 | * @param name Name of the symbol | ||
| 292 | * @return Entry, or nil if not found | ||
| 293 | */ | ||
| 294 | 378463 | public f<SymbolTableEntry*> Scope.lookup(const String& name) { | |
| 295 | 378463 | return this.symbolTable.lookup(name); | |
| 296 | } | ||
| 297 | |||
| 298 | /** | ||
| 299 | * Check if a symbol exists in the current scope only (does not walk up to parent scopes) | ||
| 300 | * | ||
| 301 | * @param name Name of the symbol | ||
| 302 | * @return Entry, or nil if not found | ||
| 303 | */ | ||
| 304 | 891294 | public f<SymbolTableEntry*> Scope.lookupStrict(const String& name) { | |
| 305 | 891294 | return this.symbolTable.lookupStrict(name); | |
| 306 | } | ||
| 307 | |||
| 308 | /** | ||
| 309 | * Look up a field symbol of this (struct or union) scope by its order index | ||
| 310 | * | ||
| 311 | * @param n Order index of the field | ||
| 312 | * @return Field entry, or nil if not found | ||
| 313 | */ | ||
| 314 | 224611 | public f<SymbolTableEntry*> Scope.lookupField(const unsigned int n) { | |
| 315 | 224611 | assert this.scopeType == ScopeType::STRUCT || this.scopeType == ScopeType::UNION; | |
| 316 | 224611 | return this.symbolTable.lookupStrictByIndex(n); | |
| 317 | } | ||
| 318 | |||
| 319 | /** | ||
| 320 | * Insert a generic type into the current scope | ||
| 321 | * | ||
| 322 | * @param typeName Name of the generic type | ||
| 323 | * @param genericType Generic type to insert | ||
| 324 | */ | ||
| 325 | 18292 | public p Scope.insertGenericType(const String& typeName, const GenericType& genericType) { | |
| 326 | 18292 | assert !this.genericTypes.contains(typeName); | |
| 327 | 18292 | this.genericTypes.upsert(typeName, genericType); | |
| 328 | 18292 | } | |
| 329 | |||
| 330 | /** | ||
| 331 | * Look up a generic type by its name in the current scope | ||
| 332 | * | ||
| 333 | * @param typeName Name of the generic type | ||
| 334 | * @return Generic type or nil if not found | ||
| 335 | */ | ||
| 336 | 147933 | public f<GenericType*> Scope.lookupGenericTypeStrict(const String& typeName) { | |
| 337 | 147933 | if !this.genericTypes.contains(typeName) { | |
| 338 | 91311 | return nil<GenericType*>; | |
| 339 | } | ||
| 340 | 56622 | return &this.genericTypes.get(typeName); | |
| 341 | } | ||
| 342 | |||
| 343 | /** | ||
| 344 | * Retrieve all struct manifestations in this scope in the order of their declaration | ||
| 345 | * | ||
| 346 | * @return All struct manifestations in declaration order | ||
| 347 | */ | ||
| 348 | 3890 | public f<Vector<Struct*>> Scope.getAllStructManifestationsInDeclarationOrder() { | |
| 349 | // Retrieve all struct manifestations in this scope | ||
| 350 | 3890 | Vector<Struct*> manifestations; | |
| 351 | 7105 | foreach Pair<const String&, StructManifestationList&> structEntry : this.structs { | |
| 352 | 6454 | foreach Pair<const String&, Struct&> manifestationEntry : structEntry.getSecond() { | |
| 353 | 3239 | manifestations.pushBack(&manifestationEntry.getSecond()); | |
| 354 | 3239 | } | |
| 355 | 3215 | } | |
| 356 | |||
| 357 | // Sort manifestations by declaration code location (insertion sort, the lists are small) | ||
| 358 | 4872 | for unsigned long i = 1l; i < manifestations.getSize(); i++ { | |
| 359 | 982 | Struct* current = manifestations.get(i); | |
| 360 | 982 | const CodeLoc& currentCodeLoc = current.structBase.getDeclCodeLoc(); | |
| 361 | 982 | unsigned long j = i; | |
| 362 | 2092 | while j > 0l { | |
| 363 | 1628 | Struct* previous = manifestations.get(j - 1l); | |
| 364 | 1628 | const CodeLoc& previousCodeLoc = previous.structBase.getDeclCodeLoc(); | |
| 365 | 1628 | if !currentCodeLoc.isBefore(previousCodeLoc) { | |
| 366 | 518 | break; | |
| 367 | } | ||
| 368 | 1110 | Struct*& slot = manifestations.get(j); | |
| 369 | 1110 | slot = previous; | |
| 370 | 1110 | j--; | |
| 371 | 1110 | } | |
| 372 | 982 | Struct*& targetSlot = manifestations.get(j); | |
| 373 | 982 | targetSlot = current; | |
| 374 | 982 | } | |
| 375 | 3890 | return manifestations; | |
| 376 | } | ||
| 377 | |||
| 378 | /** | ||
| 379 | * Retrieve all virtual methods of this struct or interface scope, sorted by their VTable index | ||
| 380 | * | ||
| 381 | * @return Virtual methods | ||
| 382 | */ | ||
| 383 | 3122 | public f<Vector<const Function*>> Scope.getVirtualMethods() { | |
| 384 | 3122 | assert this.scopeType == ScopeType::STRUCT || this.scopeType == ScopeType::INTERFACE; | |
| 385 | |||
| 386 | // Collect all virtual methods | ||
| 387 | 3122 | Vector<const Function*> methods; | |
| 388 | 26874 | foreach Pair<const String&, FunctionManifestationList&> fctEntry : this.functions { | |
| 389 | 23752 | FunctionManifestationList& manifestationList = fctEntry.getSecond(); | |
| 390 | 23752 | assert !manifestationList.isEmpty(); | |
| 391 | 58454 | foreach Pair<const String&, Function&> manifestationEntry : manifestationList { | |
| 392 | 34702 | const Function& function = manifestationEntry.getSecond(); | |
| 393 | 34702 | if function.isVirtualMethod() { | |
| 394 | 7038 | methods.pushBack(&function); | |
| 395 | 7038 | } | |
| 396 | 34702 | } | |
| 397 | 23752 | } | |
| 398 | |||
| 399 | // Sort the list by VTable index, so that the VTable layout matches the slot indices used at the call sites (insertion sort, | ||
| 400 | // the lists are small) | ||
| 401 | 7386 | for unsigned long i = 1l; i < methods.getSize(); i++ { | |
| 402 | 4264 | const Function* current = methods.get(i); | |
| 403 | 4264 | unsigned long j = i; | |
| 404 | 4310 | while j > 0l { | |
| 405 | 4282 | const Function* previous = methods.get(j - 1l); | |
| 406 | 4282 | if previous.vtableIndex <= current.vtableIndex { | |
| 407 | 4236 | break; | |
| 408 | } | ||
| 409 | 46 | const Function*& slot = methods.get(j); | |
| 410 | 46 | slot = previous; | |
| 411 | 46 | j--; | |
| 412 | 46 | } | |
| 413 | 4264 | const Function*& targetSlot = methods.get(j); | |
| 414 | 4264 | targetSlot = current; | |
| 415 | 4264 | } | |
| 416 | |||
| 417 | 3122 | return methods; | |
| 418 | } | ||
| 419 | |||
| 420 | /** | ||
| 421 | * Check if this scope is within an async scope | ||
| 422 | * | ||
| 423 | * @return Within async scope or not | ||
| 424 | */ | ||
| 425 | 292 | public f<bool> Scope.isInAsyncScope() { | |
| 426 | 292 | if this.isAsyncScope { | |
| 427 | 25 | return true; | |
| 428 | } | ||
| 429 | 267 | return !this.isRootScope() && this.parent.isInAsyncScope(); | |
| 430 | } | ||
| 431 | |||
| 432 | /** | ||
| 433 | * Get the scope of the function/procedure/lambda body, this scope is located in | ||
| 434 | * | ||
| 435 | * @return Function scope | ||
| 436 | */ | ||
| 437 | 79482 | public f<Scope*> Scope.getFunctionScope() { | |
| 438 | 79482 | if this.scopeType == ScopeType::FUNC_PROC_BODY || this.scopeType == ScopeType::LAMBDA_BODY { | |
| 439 | 48163 | return this; | |
| 440 | } | ||
| 441 | 31319 | assert !this.isRootScope(); | |
| 442 | 31319 | return this.parent.getFunctionScope(); | |
| 443 | } | ||
| 444 | |||
| 445 | /** | ||
| 446 | * Get the implicit result variable of the function/lambda function, this scope is located in. A local variable, that the | ||
| 447 | * user named 'result', is no result variable, so it is skipped | ||
| 448 | * | ||
| 449 | * @return Result variable entry or nil, if this scope is not located in a function or lambda function | ||
| 450 | */ | ||
| 451 | 46734 | public f<SymbolTableEntry*> Scope.lookupResultVar() { | |
| 452 | 46734 | Scope* scope = this; | |
| 453 | 75770 | while scope != nil<Scope*> { | |
| 454 | 75770 | if scope.scopeType == ScopeType::FUNC_PROC_BODY || scope.scopeType == ScopeType::LAMBDA_BODY { | |
| 455 | 46734 | SymbolTableEntry* entry = scope.lookupStrict(String(RETURN_VARIABLE_NAME)); | |
| 456 | 46734 | if entry == nil<SymbolTableEntry*> || entry.declNode.kind == ASTNodeKind::DECL_STMT { | |
| 457 | 8341 | return nil<SymbolTableEntry*>; | |
| 458 | } | ||
| 459 | 76786 | return entry; | |
| 460 | 46734 | } | |
| 461 | 29036 | scope = scope.parent; | |
| 462 | 29036 | } | |
| 463 | ✗ | return nil<SymbolTableEntry*>; | |
| 464 | } | ||
| 465 | |||
| 466 | /** | ||
| 467 | * Check if this scope is the body scope of a ctor or one of its nested scopes (e.g. the body of an if statement in a ctor) | ||
| 468 | * | ||
| 469 | * @return In ctor body or not | ||
| 470 | */ | ||
| 471 | 30969 | public f<bool> Scope.isInCtorBody() { | |
| 472 | 30969 | Scope* scope = this; | |
| 473 | 50361 | while scope != nil<Scope*> { | |
| 474 | 50361 | if scope.scopeType == ScopeType::FUNC_PROC_BODY || scope.scopeType == ScopeType::LAMBDA_BODY { | |
| 475 | 30969 | return scope.isCtorScope; | |
| 476 | } | ||
| 477 | 19392 | scope = scope.parent; | |
| 478 | 19392 | } | |
| 479 | ✗ | return false; | |
| 480 | } | ||
| 481 | |||
| 482 | /** | ||
| 483 | * Check if this scope is the root scope of its source file | ||
| 484 | * | ||
| 485 | * @return Root scope or not | ||
| 486 | */ | ||
| 487 | 118527 | public f<bool> Scope.isRootScope() { | |
| 488 | 118527 | return this.parent == nil<Scope*>; | |
| 489 | } | ||
| 490 | |||
| 491 | /** | ||
| 492 | * Get the number of nested loops, which enclose this scope | ||
| 493 | * | ||
| 494 | * @return Loop nesting depth | ||
| 495 | */ | ||
| 496 | 14269 | public f<unsigned int> Scope.getLoopNestingDepth() { | |
| 497 | 14269 | assert !this.isRootScope(); | |
| 498 | 14269 | if this.parent.parent == nil<Scope*> { | |
| 499 | 2195 | return 0u; | |
| 500 | } | ||
| 501 | 12074 | unsigned int loopCount = this.parent.getLoopNestingDepth(); | |
| 502 | 12074 | if this.scopeType == ScopeType::WHILE_BODY || this.scopeType == ScopeType::FOR_BODY || this.scopeType == ScopeType::FOREACH_BODY { | |
| 503 | 3537 | loopCount++; | |
| 504 | 3537 | } | |
| 505 | 12074 | return loopCount; | |
| 506 | } | ||
| 507 | |||
| 508 | /** | ||
| 509 | * Check if this scope is within a case branch | ||
| 510 | * | ||
| 511 | * @return Within a case branch or not | ||
| 512 | */ | ||
| 513 | 21 | public f<bool> Scope.isInCaseBranch() { | |
| 514 | 21 | assert !this.isRootScope(); | |
| 515 | 21 | if this.parent.parent == nil<Scope*> { | |
| 516 | 2 | return false; | |
| 517 | } | ||
| 518 | 19 | if this.scopeType == ScopeType::CASE_BODY { | |
| 519 | 14 | return true; | |
| 520 | } | ||
| 521 | 5 | return this.parent.isInCaseBranch(); | |
| 522 | } | ||
| 523 | |||
| 524 | /** | ||
| 525 | * Check if unsafe operations are allowed in this scope. This is the case in unsafe blocks and all scopes within them. | ||
| 526 | * | ||
| 527 | * @return Allowed or not | ||
| 528 | */ | ||
| 529 | 93289 | public f<bool> Scope.doesAllowUnsafeOperations() { | |
| 530 | 93289 | if this.scopeType == ScopeType::UNSAFE_BODY { | |
| 531 | 20644 | return true; | |
| 532 | } | ||
| 533 | 72645 | return !this.isRootScope() && this.parent.doesAllowUnsafeOperations(); | |
| 534 | } | ||
| 535 | |||
| 536 | /** | ||
| 537 | * Check if the source file of this scope is imported by the source file of the asking scope | ||
| 538 | * | ||
| 539 | * @param askingScope Scope, that asks | ||
| 540 | * @return Imported or not | ||
| 541 | */ | ||
| 542 | 430538 | public f<bool> Scope.isImportedBy(const Scope* askingScope) { | |
| 543 | 430538 | return askingScope.sourceFile.imports(this.sourceFile); | |
| 544 | } | ||
| 545 | |||
| 546 | /** | ||
| 547 | * Get JSON representation of the symbol table. | ||
| 548 | * The returned heap-allocated JSON value is owned by the caller. | ||
| 549 | * | ||
| 550 | * @return Symbol table as JSON object | ||
| 551 | */ | ||
| 552 | 277588 | public f<JsonValue*> Scope.getSymbolTableJSON() { | |
| 553 | 277588 | result = this.symbolTable.toJSON(); | |
| 554 | |||
| 555 | // Collect all children, sorted by name | ||
| 556 | 277588 | Map<String, Scope*> sortedChildren; | |
| 557 | 551499 | foreach Pair<const String&, Scope*&> childEntry : this.children { | |
| 558 | 273911 | sortedChildren.upsert(childEntry.getFirst(), childEntry.getSecond()); | |
| 559 | 273911 | } | |
| 560 | 277588 | JsonValue* jsonChildren = __new<JsonValue>(JsonValueKind::JSON_ARRAY); | |
| 561 | 551499 | foreach Pair<const String&, Scope*&> childEntry : sortedChildren { | |
| 562 | 273911 | Scope* childScope = childEntry.getSecond(); | |
| 563 | 273911 | JsonValue* c = childScope.getSymbolTableJSON(); | |
| 564 | 273911 | setJsonField(c, String("name"), __new<JsonValue>(childEntry.getFirst())); // Inject symbol table name into JSON object | |
| 565 | 273911 | jsonChildren.addArrayItem(c); | |
| 566 | 273911 | } | |
| 567 | 277588 | setJsonField(result, String("children"), jsonChildren); | |
| 568 | 277588 | } | |
| 569 | |||
| 570 | /** | ||
| 571 | * Create a child scope and return it | ||
| 572 | * | ||
| 573 | * @param scopeName Name of the child scope | ||
| 574 | * @param scopeType Type of the child scope | ||
| 575 | * @param declCodeLoc Code location of the scope | ||
| 576 | * @return Child scope (heap allocated) | ||
| 577 | */ | ||
| 578 | 157154 | #[ignoreUnusedReturnValue] | |
| 579 | 157154 | public f<Scope*> Scope.createChildScope(const String& scopeName, ScopeType scopeType, const CodeLoc *declCodeLoc) { | |
| 580 | 157154 | assert !this.children.contains(scopeName); | |
| 581 | 157154 | Scope* child = this.sourceFile.resourceManager.createScope(this, this.sourceFile, scopeType, declCodeLoc); | |
| 582 | 157154 | this.children.upsert(scopeName, child); | |
| 583 | 157154 | return child; | |
| 584 | } | ||
| 585 | |||
| 586 | /** | ||
| 587 | * Rename the child scope. This is useful for realizing function overloading by storing a function with not | ||
| 588 | * only its name, but also its signature | ||
| 589 | * | ||
| 590 | * @param oldName Old name of the child table | ||
| 591 | * @param newName New name of the child table | ||
| 592 | */ | ||
| 593 | 50907 | public p Scope.renameChildScope(const String& oldName, const String& newName) { | |
| 594 | 50907 | assert this.children.contains(oldName) && !this.children.contains(newName); | |
| 595 | // Re-key the owning pointer; the pooled Scope itself is not moved or copied. | ||
| 596 | 50907 | Scope* child = this.children.get(oldName); | |
| 597 | 50907 | this.children.upsert(newName, child); | |
| 598 | 50907 | this.children.remove(oldName); | |
| 599 | 50907 | } | |
| 600 | |||
| 601 | /** | ||
| 602 | * Duplicates the child scope by copying it. The duplicated symbols point to the original ones. | ||
| 603 | * | ||
| 604 | * @param oldName Old name of the child block | ||
| 605 | * @param newName New block name | ||
| 606 | * @return New scope | ||
| 607 | */ | ||
| 608 | 18840 | #[ignoreUnusedReturnValue] | |
| 609 | 18840 | public f<Scope*> Scope.copyChildScope(const String& oldName, const String& newName) { | |
| 610 | 18840 | assert this.children.contains(oldName) && !this.children.contains(newName); | |
| 611 | // Create copy. The copy is constructed in place in the scope pool of the resource manager, which owns it (no Scope copy). | ||
| 612 | 18840 | Scope* oldScope = this.children.get(oldName); | |
| 613 | 18840 | Scope* newScope = this.sourceFile.resourceManager.createScope(this, oldScope.sourceFile, oldScope.scopeType, oldScope.codeLoc); | |
| 614 | 18840 | oldScope.deepCopyScope(newScope); | |
| 615 | // Save copy under new name | ||
| 616 | 18840 | this.children.upsert(newName, newScope); | |
| 617 | 18840 | return newScope; | |
| 618 | } | ||
| 619 | |||
| 620 | /** | ||
| 621 | * Deep copy the current scope and all its children into the given scope. The target scope must be freshly constructed with | ||
| 622 | * the same parent, source file, scope type and code location as the current scope. Scopes are owned by the scope pool of the | ||
| 623 | * resource manager, so the caller allocates the target. | ||
| 624 | * | ||
| 625 | * @param newScope Target scope of the copy | ||
| 626 | */ | ||
| 627 | 110335 | public p Scope.deepCopyScope(Scope* newScope) { | |
| 628 | // Copy the members | ||
| 629 | 110335 | newScope.symbolTable.copyContentsFrom(this.symbolTable); | |
| 630 | 110335 | newScope.isGenericScope = this.isGenericScope; | |
| 631 | 110335 | newScope.isAsyncScope = this.isAsyncScope; | |
| 632 | 110335 | newScope.isCtorScope = this.isCtorScope; | |
| 633 | 110335 | newScope.isDtorScope = this.isDtorScope; | |
| 634 | 110335 | newScope.functions = this.functions; | |
| 635 | 110335 | newScope.structs = this.structs; | |
| 636 | 110335 | newScope.interfaces = this.interfaces; | |
| 637 | 110335 | newScope.unions = this.unions; | |
| 638 | 110335 | newScope.genericTypes = this.genericTypes; | |
| 639 | 110335 | newScope.temporaryDtorsToCall = this.temporaryDtorsToCall; | |
| 640 | // Deep copy the children. Their parent and symbol table parent point to the new scope (set by the ctor) | ||
| 641 | 201830 | foreach Pair<const String&, Scope*&> childEntry : this.children { | |
| 642 | 91495 | Scope* oldChild = childEntry.getSecond(); | |
| 643 | 91495 | Scope* newChild = this.sourceFile.resourceManager.createScope(newScope, oldChild.sourceFile, oldChild.scopeType, oldChild.codeLoc); | |
| 644 | 91495 | oldChild.deepCopyScope(newChild); | |
| 645 | 91495 | newScope.children.upsert(childEntry.getFirst(), newChild); | |
| 646 | 91495 | } | |
| 647 | 110335 | } | |
| 648 | |||
| 649 | /** | ||
| 650 | * Get a child scope of the current scope by its name | ||
| 651 | * | ||
| 652 | * @param scopeName Child scope name | ||
| 653 | * @return Child scope | ||
| 654 | */ | ||
| 655 | 351660 | public f<Scope*> Scope.getChildScope(const String& scopeName) { | |
| 656 | 351660 | if this.children.contains(scopeName) { | |
| 657 | 351585 | return this.children.get(scopeName); | |
| 658 | } | ||
| 659 | 75 | return nil<Scope*>; | |
| 660 | } | ||
| 661 | |||
| 662 | /** | ||
| 663 | * Retrieve all variables in the current scope, that have reached the end of their lifetime at the end of this scope | ||
| 664 | * | ||
| 665 | * @return Collection of EOL variables | ||
| 666 | */ | ||
| 667 | 155704 | public f<Vector<SymbolTableEntry*>> Scope.getVarsGoingOutOfScope() { | |
| 668 | 155704 | assert this.parent != nil<Scope*>; // Should not be called in root scope | |
| 669 | 155704 | Vector<SymbolTableEntry*> varsGoingOutOfScope; | |
| 670 | |||
| 671 | // Collect all variables in this scope | ||
| 672 | 307874 | foreach Pair<const String&, SymbolTableEntry&> symbol : this.symbolTable.symbols { | |
| 673 | 152170 | const String& name = symbol.getFirst(); | |
| 674 | 152170 | SymbolTableEntry& entry = symbol.getSecond(); | |
| 675 | // Skip 'this' and the implicit result variable. A local variable, that the user named 'result', goes out of scope | ||
| 676 | 152170 | if name == THIS_VARIABLE_NAME || (name == RETURN_VARIABLE_NAME && entry.declNode.kind != ASTNodeKind::DECL_STMT) { | |
| 677 | 58595 | continue; | |
| 678 | } | ||
| 679 | // Params only go out of scope, if they own their argument (see QualType.isOwnedByValueParam). All other params hold | ||
| 680 | // references, pointers or bitwise copies of values, that the caller owns | ||
| 681 | 93575 | if entry.isParam { | |
| 682 | 55510 | const QualType paramType = entry.getQualType(); | |
| 683 | 55510 | if !paramType.isOwnedByValueParam(entry.declNode) { | |
| 684 | 55409 | continue; | |
| 685 | } | ||
| 686 | 101 | } | |
| 687 | // Found variable, that goes out of scope | ||
| 688 | 38166 | varsGoingOutOfScope.pushBack(&entry); | |
| 689 | 38166 | } | |
| 690 | |||
| 691 | // If this is the scope of a dtor, also return all fields of the struct | ||
| 692 | 155704 | if this.isDtorScope { | |
| 693 | 811 | assert this.parent != nil<Scope*> && this.parent.scopeType == ScopeType::STRUCT; | |
| 694 | // Get all fields of the struct | ||
| 695 | 811 | const dyn fctOptions = [SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE]; | |
| 696 | 27501 | foreach Pair<const String&, SymbolTableEntry&> field : this.parent.symbolTable.symbols { | |
| 697 | 26690 | SymbolTableEntry& entry = field.getSecond(); | |
| 698 | 26690 | const QualType& qualType = entry.getQualType(); | |
| 699 | 26690 | if !qualType.isOneOf(fctOptions, len(fctOptions)) { | |
| 700 | 2561 | varsGoingOutOfScope.pushBack(&entry); | |
| 701 | 2561 | } | |
| 702 | 26690 | } | |
| 703 | 811 | } | |
| 704 | |||
| 705 | 155704 | return varsGoingOutOfScope; | |
| 706 | } | ||
| 707 | |||
| 708 |