GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
324 / 330
98.2%
Branches:
155 / 166, 38 excl
93.4%
Functions:
31 / 31
100.0%

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
2/2
✓ Branch 0 taken 267489 times.
✓ Branch 1 taken 3951 times.
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
2/2
✓ Branch 0 taken 17449 times.
✓ Branch 1 taken 7668 times.
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
6/6
✓ Branch 0 taken 631 times.
✓ Branch 1 taken 16818 times.
✓ Branch 2 taken 214 times.
✓ Branch 3 taken 417 times.
✓ Branch 4 taken 17132 times.
✓ Branch 5 taken 317 times.
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
2/2
✓ Branch 0 taken 266 times.
✓ Branch 1 taken 51 times.
317 const bool isPublicApiCandidate = entryType.isExtendedPrimitive() || entryType.is(SuperType::TY_ENUM);
145
6/6
✓ Branch 0 taken 268 times.
✓ Branch 1 taken 49 times.
✓ Branch 2 taken 116 times.
✓ Branch 3 taken 152 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 315 times.
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
4/4
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 211 times.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 3 times.
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
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if entryType.isOneOf(genericCandidates, len(genericCandidates)) && !entryTemplateTypes.isEmpty() {
157 ✗ continue;
158 }
159
160
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
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
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 10 times.
15 } else if entryType.is(SuperType::TY_STRUCT) {
171 1 warningType = CompilerWarningType::UNUSED_STRUCT;
172 1 warningMessage = "The struct '" + entry.name + "' is unused";
173
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
11 } else if entryType.is(SuperType::TY_INTERFACE) {
174 1 warningType = CompilerWarningType::UNUSED_INTERFACE;
175 1 warningMessage = "The interface '" + entry.name + "' is unused";
176
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
10 } else if entryType.is(SuperType::TY_UNION) {
177 1 warningType = CompilerWarningType::UNUSED_UNION;
178 1 warningMessage = "The union '" + entry.name + "' is unused";
179
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
9 } else if entryType.is(SuperType::TY_ENUM) {
180 2 continue; // Do not report unused enums. Only unused enum items are reported
181
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 } else if entryType.is(SuperType::TY_IMPORT) {
182 5 warningType = CompilerWarningType::UNUSED_IMPORT;
183 5 warningMessage = "The import '" + entry.name + "' is unused";
184
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
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
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 152 times.
211 if entry.isField() {
194 59 warningType = CompilerWarningType::UNUSED_FIELD;
195 59 warningMessage = "The field '" + entry.name + "' is unused";
196
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 152 times.
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
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 14 times.
152 if fctManifestations.isEmpty() {
200 138 continue;
201 }
202 14 const Function* firstManifestation = fctManifestations.front();
203
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
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
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 20 times.
28 foreach const Function* manifestation : *fctManifestations {
210
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
20 if manifestation.used {
211 6 isAnyManifestationUsed = true;
212 6 break;
213 }
214 14 }
215
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
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
2/2
✓ Branch 0 taken 7126 times.
✓ Branch 1 taken 7668 times.
14794 foreach Pair<const String&, Scope*&> childEntry : this.children {
239 7126 Scope* childScope = childEntry.getSecond();
240
2/2
✓ Branch 0 taken 295 times.
✓ Branch 1 taken 6831 times.
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
2/2
✓ Branch 0 taken 377454 times.
✓ Branch 1 taken 278902 times.
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
2/2
✓ Branch 0 taken 377453 times.
✓ Branch 1 taken 1 time.
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
2/2
✓ Branch 0 taken 275205 times.
✓ Branch 1 taken 278902 times.
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
2/2
✓ Branch 0 taken 91311 times.
✓ Branch 1 taken 56622 times.
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
2/2
✓ Branch 0 taken 3215 times.
✓ Branch 1 taken 3890 times.
7105 foreach Pair<const String&, StructManifestationList&> structEntry : this.structs {
352
2/2
✓ Branch 0 taken 3239 times.
✓ Branch 1 taken 3215 times.
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
2/2
✓ Branch 0 taken 982 times.
✓ Branch 1 taken 3890 times.
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
2/2
✓ Branch 0 taken 464 times.
✓ Branch 1 taken 1628 times.
2092 while j > 0l {
363 1628 Struct* previous = manifestations.get(j - 1l);
364 1628 const CodeLoc& previousCodeLoc = previous.structBase.getDeclCodeLoc();
365
2/2
✓ Branch 0 taken 518 times.
✓ Branch 1 taken 1110 times.
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
2/2
✓ Branch 0 taken 3122 times.
✓ Branch 1 taken 23752 times.
26874 foreach Pair<const String&, FunctionManifestationList&> fctEntry : this.functions {
389 23752 FunctionManifestationList& manifestationList = fctEntry.getSecond();
390 23752 assert !manifestationList.isEmpty();
391
2/2
✓ Branch 0 taken 34702 times.
✓ Branch 1 taken 23752 times.
58454 foreach Pair<const String&, Function&> manifestationEntry : manifestationList {
392 34702 const Function& function = manifestationEntry.getSecond();
393
2/2
✓ Branch 0 taken 27664 times.
✓ Branch 1 taken 7038 times.
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
2/2
✓ Branch 0 taken 4264 times.
✓ Branch 1 taken 3122 times.
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
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 4282 times.
4310 while j > 0l {
405 4282 const Function* previous = methods.get(j - 1l);
406
2/2
✓ Branch 0 taken 4236 times.
✓ Branch 1 taken 46 times.
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
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 267 times.
292 if this.isAsyncScope {
427 25 return true;
428 }
429
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 173 times.
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
4/4
✓ Branch 0 taken 48088 times.
✓ Branch 1 taken 31394 times.
✓ Branch 2 taken 48163 times.
✓ Branch 3 taken 31319 times.
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
1/2
✓ Branch 0 taken 75770 times.
✗ Branch 1 not taken.
75770 while scope != nil<Scope*> {
454
4/4
✓ Branch 0 taken 46664 times.
✓ Branch 1 taken 29106 times.
✓ Branch 2 taken 29036 times.
✓ Branch 3 taken 46734 times.
75770 if scope.scopeType == ScopeType::FUNC_PROC_BODY || scope.scopeType == ScopeType::LAMBDA_BODY {
455 46734 SymbolTableEntry* entry = scope.lookupStrict(String(RETURN_VARIABLE_NAME));
456
4/4
✓ Branch 0 taken 8331 times.
✓ Branch 1 taken 38403 times.
✓ Branch 2 taken 8341 times.
✓ Branch 3 taken 38393 times.
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
1/2
✓ Branch 0 taken 50361 times.
✗ Branch 1 not taken.
50361 while scope != nil<Scope*> {
474
4/4
✓ Branch 0 taken 30952 times.
✓ Branch 1 taken 19409 times.
✓ Branch 2 taken 30969 times.
✓ Branch 3 taken 19392 times.
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
2/2
✓ Branch 0 taken 12074 times.
✓ Branch 1 taken 2195 times.
14269 if this.parent.parent == nil<Scope*> {
499 2195 return 0u;
500 }
501 12074 unsigned int loopCount = this.parent.getLoopNestingDepth();
502
6/6
✓ Branch 0 taken 11289 times.
✓ Branch 1 taken 785 times.
✓ Branch 2 taken 2464 times.
✓ Branch 3 taken 8825 times.
✓ Branch 4 taken 8537 times.
✓ Branch 5 taken 3537 times.
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
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 2 times.
21 if this.parent.parent == nil<Scope*> {
516 2 return false;
517 }
518
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 5 times.
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
2/2
✓ Branch 0 taken 20644 times.
✓ Branch 1 taken 72645 times.
93289 if this.scopeType == ScopeType::UNSAFE_BODY {
531 20644 return true;
532 }
533
2/2
✓ Branch 0 taken 17770 times.
✓ Branch 1 taken 54875 times.
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
2/2
✓ Branch 0 taken 273911 times.
✓ Branch 1 taken 277588 times.
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
2/2
✓ Branch 0 taken 273911 times.
✓ Branch 1 taken 277588 times.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
110335 newScope.functions = this.functions;
635
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
110335 newScope.structs = this.structs;
636
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
110335 newScope.interfaces = this.interfaces;
637
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
110335 newScope.unions = this.unions;
638
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
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
2/2
✓ Branch 0 taken 110335 times.
✓ Branch 1 taken 91495 times.
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
2/2
✓ Branch 0 taken 351585 times.
✓ Branch 1 taken 75 times.
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
2/2
✓ Branch 0 taken 152170 times.
✓ Branch 1 taken 155704 times.
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
6/6
✓ Branch 0 taken 28928 times.
✓ Branch 1 taken 123242 times.
✓ Branch 2 taken 93545 times.
✓ Branch 3 taken 29697 times.
✓ Branch 4 taken 58595 times.
✓ Branch 5 taken 93575 times.
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
2/2
✓ Branch 0 taken 38065 times.
✓ Branch 1 taken 55510 times.
93575 if entry.isParam {
682 55510 const QualType paramType = entry.getQualType();
683
2/2
✓ Branch 0 taken 55409 times.
✓ Branch 1 taken 101 times.
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
2/2
✓ Branch 0 taken 154893 times.
✓ Branch 1 taken 811 times.
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
2/2
✓ Branch 0 taken 26690 times.
✓ Branch 1 taken 811 times.
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
2/2
✓ Branch 0 taken 24129 times.
✓ Branch 1 taken 2561 times.
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