GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
169 / 172
98.3%
Branches:
77 / 98, 12 excl
78.6%
Functions:
21 / 22
95.5%

symboltablebuilder/symbol-table.spice
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 // Std imports
4 import "std/data/map";
5 import "std/data/vector";
6 import "std/data/pair";
7 import "std/type/type-conversion";
8 import "std/type/types";
9 import "std/text/json/json-value";
10
11 // Own imports
12 import "@/ast/ast-nodes";
13 import "@/symboltablebuilder/qual-type";
14 import "@/symboltablebuilder/super-type";
15 import "@/symboltablebuilder/scope";
16 import "@/symboltablebuilder/symbol-table-entry";
17 import "@/symboltablebuilder/capture";
18 import "@/symboltablebuilder/lifecycle";
19 import "@/util/compiler-warning";
20 import "@/util/common-util";
21 import "@/source-file";
22
23 // Type aliases
24 public type CaptureMap alias Map</*name=*/String, /*capture*/Capture>; // Ordered, since the order determines the capture struct layout
25 public type SymbolMap alias Map</*name=*/String, /*entry*/SymbolTableEntry>; // Ordered, since the order shows in the warnings
26
27 /**
28 * Class for storing information about symbols of the program.
29 * Symbol tables are arranged in a tree structure, so that you can navigate with the .parent property and getChild() method up
30 * and down the tree.
31 */
32 262054 public type SymbolTable struct {
33 SymbolTable* parent
34 Scope* scope
35 SymbolMap symbols
36 public CaptureMap captures
37 271440 bool capturingRequired = false
38 }
39
40 271440 public p SymbolTable.ctor(SymbolTable* parent, Scope* scope) {
41 271440 this.parent = parent;
42 271440 this.scope = scope;
43 271440 }
44
45 /**
46 * Copy the symbols and captures of another symbol table into this one. The parent and scope stay untouched. This is used
47 * when copying a scope.
48 *
49 * @param other Symbol table to copy from
50 */
51 110335 public p SymbolTable.copyContentsFrom(const SymbolTable& other) {
52
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
110335 this.symbols = other.symbols;
53
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110335 times.
110335 this.captures = other.captures;
54 110335 this.capturingRequired = other.capturingRequired;
55 110335 }
56
57 /**
58 * Count the number of field symbols in this symbol table (struct scopes):
59 * skip anonymous symbols, imports and nested function/struct/union definitions.
60 *
61 * @return Number of field symbols
62 */
63 270340 public f<unsigned long> SymbolTable.getFieldCount() {
64 270340 result = 0ul;
65
2/2
✓ Branch 0 taken 5569525 times.
✓ Branch 1 taken 270340 times.
5839865 foreach Pair<const String&, SymbolTableEntry&> symbolPair : this.symbols {
66 5569525 SymbolTableEntry& symbol = symbolPair.getSecond();
67
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5569525 times.
5569525 if symbol.anonymous { continue; }
68 5569525 const QualType& symbolType = symbol.getQualType();
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5569525 times.
5569525 if symbolType.is(SuperType::TY_IMPORT) { continue; }
70 5569525 const ASTNode* declNode = symbol.declNode;
71
6/6
✓ Branch 0 taken 1016052 times.
✓ Branch 1 taken 4553473 times.
✓ Branch 2 taken 100997 times.
✓ Branch 3 taken 915055 times.
✓ Branch 4 taken 4654470 times.
✓ Branch 5 taken 915055 times.
5569525 if declNode.isFctOrProcDef() || declNode.isStructDef() || declNode.isUnionDef() { continue; }
72 915055 result++;
73 915055 }
74 270340 }
75
76 /**
77 * Insert a new symbol into the current symbol table. If it is a parameter, append its name to the paramNames vector
78 *
79 * @param name Name of the symbol
80 * @param declNode AST node where the symbol is declared
81 * @return Inserted entry
82 */
83 228897 public f<SymbolTableEntry*> SymbolTable.insert(const String& name, const ASTNode* declNode, bool isAnonymousSymbol = false) {
84 228897 const bool isGlobal = this.parent == nil<SymbolTable*>;
85 // Anonymous symbols do not get an order index (ULONG_MAX_VALUE)
86 228897 unsigned long orderIndex = ULONG_MAX_VALUE;
87
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 220093 times.
✓ Branch 2 taken 8804 times.
✗ Branch 3 not taken.
228897 if !isAnonymousSymbol {
88 220093 orderIndex = 0ul;
89
2/4
✓ Branch 0 taken 1537477 times.
✓ Branch 1 taken 220093 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1757570 foreach Pair<const String&, SymbolTableEntry&> symbol : this.symbols {
90 1537477 const SymbolTableEntry& existingEntry = symbol.getSecond();
91
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1537477 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1537477 if !existingEntry.anonymous {
92 1537477 orderIndex++;
93 1537477 }
94 1537477 }
95 220093 }
96 // Insert into symbols map. The type is invalid, because concrete types are determined by the type checker later on
97 228897 assert !this.symbols.contains(name);
98 228897 const QualType qualType = QualType(SuperType::TY_INVALID);
99 228897 ASTNode* mutableDeclNode = cast<ASTNode*>(declNode);
100 228897 this.symbols.upsert(name, SymbolTableEntry(name, qualType, this.scope, mutableDeclNode, orderIndex, isGlobal));
101 // Set entry to declared
102 228897 SymbolTableEntry* entry = &this.symbols.get(name);
103 228897 entry.updateState(LifecycleState::DECLARED);
104
105 // Check if shadowed
106
11/16
✓ Branch 0 taken 180477 times.
✓ Branch 1 taken 39616 times.
✓ Branch 2 taken 2948 times.
✓ Branch 3 taken 177529 times.
✓ Branch 4 taken 2878 times.
✓ Branch 5 taken 70 times.
✓ Branch 6 taken 220087 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 8804 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 8804 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 8804 times.
✗ Branch 15 not taken.
228897 if this.parent != nil<SymbolTable*> && this.parent.lookup(name) != nil<SymbolTableEntry*> && !mutableDeclNode.isParam() && name != RETURN_VARIABLE_NAME {
107 6 const String warningMsg = "Variable '" + name + "' shadows a variable in a parent scope";
108 6 const CompilerWarning warning = CompilerWarning(declNode.codeLoc, CompilerWarningType::SHADOWED_VARIABLE, warningMsg.getRaw());
109 6 this.scope.sourceFile.compilerOutput.warnings.pushBack(warning);
110 6 }
111
112 457794 return entry;
113 228897 }
114
115 /**
116 * Insert a new anonymous symbol into the current symbol table.
117 * The anonymous symbol will be identified via the definition code location
118 *
119 * @param qualType Type of the symbol
120 * @param declNode AST node where the anonymous symbol is declared
121 * @return Inserted entry
122 */
123 8840 #[ignoreUnusedReturnValue]
124 8840 public f<SymbolTableEntry*> SymbolTable.insertAnonymous(const QualType& qualType, ASTNode* declNode, unsigned long numericSuffix = 0l) {
125 // Check if the anonymous entry already exists
126 8840 SymbolTableEntry* existingSymbol = this.lookupAnonymous(declNode, numericSuffix);
127
3/4
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 8655 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 149 times.
8840 if existingSymbol != nil<SymbolTableEntry*> {
128 36 return existingSymbol;
129 }
130 // Otherwise, create an anonymous entry
131 8804 const String name = this.getAnonymousSymbolName(declNode, numericSuffix);
132 8804 SymbolTableEntry* anonSymbol = this.insert(name, declNode, true);
133 8804 anonSymbol.updateType(qualType, false);
134 8804 anonSymbol.updateState(LifecycleState::DECLARED);
135 8804 anonSymbol.updateState(LifecycleState::INITIALIZED);
136 8804 anonSymbol.anonymous = true;
137 8804 anonSymbol.used = true;
138 17608 return anonSymbol;
139 8804 }
140
141 /**
142 * Build the name of an anonymous symbol. It is unique per declaration node and numeric suffix. The node id is used instead
143 * of the node address, so that the name and with that the order of anonymous symbols do not change between compiler runs
144 *
145 * @param declNode AST node where the anonymous symbol is declared
146 * @param numericSuffix Custom numeric suffix
147 * @return Name of the anonymous symbol
148 */
149 35250 f<String> SymbolTable.getAnonymousSymbolName(const ASTNode* declNode, unsigned long numericSuffix) {
150 35250 SourceFile* sourceFile = this.scope.sourceFile;
151 35250 const unsigned long nodeId = sourceFile.resourceManager.getNodeId(declNode);
152 35250 String name = String("anon.") + declNode.codeLoc.toString() + "." + toString(cast<long>(nodeId));
153
2/2
✓ Branch 0 taken 35103 times.
✓ Branch 1 taken 147 times.
35250 if numericSuffix > 0l {
154 147 name += ".";
155 147 name += toString(cast<long>(numericSuffix));
156 147 }
157 70500 return name;
158 35250 }
159
160 /**
161 * Copy a symbol by its name
162 *
163 * @param originalName Original symbol name
164 * @param newName New symbol name
165 * @return Copied entry
166 */
167 16162 public f<SymbolTableEntry*> SymbolTable.copySymbol(const String& originalName, const String& newName) {
168 16162 SymbolTableEntry* entryToCopy = this.lookupStrict(originalName);
169 16162 assert entryToCopy != nil<SymbolTableEntry*>;
170 16162 assert !this.symbols.contains(newName);
171 16162 this.symbols.upsert(newName, *entryToCopy);
172 16162 return &this.symbols.get(newName);
173 }
174
175 /**
176 * Check if a symbol exists in the current or any parent scope and return it if possible
177 *
178 * @param name Name of the desired symbol
179 * @return Desired symbol / nullptr if the symbol was not found
180 */
181 1318905 public f<SymbolTableEntry*> SymbolTable.lookup(const String& name) {
182 // Check if the symbol exists in the current scope. If yes, take it
183 1318905 SymbolTableEntry* entry = this.lookupStrict(name);
184
2/2
✓ Branch 0 taken 348945 times.
✓ Branch 1 taken 969960 times.
1318905 if entry == nil<SymbolTableEntry*> { // Symbol was not found in the current scope
185 // We reached the root scope, the symbol does not exist at all
186
2/2
✓ Branch 0 taken 653499 times.
✓ Branch 1 taken 316461 times.
969960 if this.parent == nil<SymbolTable*> {
187 316461 return nil<SymbolTableEntry*>;
188 }
189 // If we search for the result variable, we want to stop the search when exiting a lambda body
190
3/4
✓ Branch 0 taken 638958 times.
✓ Branch 1 taken 14541 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 653499 times.
653499 if name == RETURN_VARIABLE_NAME && this.scope.scopeType == ScopeType::LAMBDA_BODY {
191 ✗ return nil<SymbolTableEntry*>;
192 }
193 // If there is a parent scope, continue the search there
194 653499 entry = this.parent.lookup(name);
195 // Symbol was also not found in all the parent scopes, return nullptr
196
2/2
✓ Branch 0 taken 389425 times.
✓ Branch 1 taken 264074 times.
653499 if entry == nil<SymbolTableEntry*> {
197 264074 return nil<SymbolTableEntry*>;
198 }
199
200 // Check if this scope requires capturing and capture the variable if appropriate
201 389425 const QualType entryType = entry.getQualType();
202 389425 const dyn captureSkip = [SuperType::TY_IMPORT, SuperType::TY_FUNCTION, SuperType::TY_PROCEDURE];
203
5/6
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 389331 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 94 times.
✓ Branch 4 taken 389331 times.
✓ Branch 5 taken 94 times.
389425 if this.capturingRequired && !this.captures.contains(name) && !entryType.isOneOf(captureSkip, len(captureSkip)) {
204 // We need to make the symbol volatile if we are in an async scope and try to access a symbol that is not in an async scope
205
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 25 times.
94 entry.isVolatile = this.scope.isInAsyncScope() && !entry.scope.isInAsyncScope();
206 // Add the capture to the current scope
207 94 this.captures.upsert(name, Capture(entry));
208 94 }
209 389425 }
210 738370 return entry;
211 }
212
213 /**
214 * Check if a symbol exists in the current scope and return it if possible
215 *
216 * @param name Name of the desired symbol
217 * @return Desired symbol / nullptr if the symbol was not found
218 */
219 2226492 public f<SymbolTableEntry*> SymbolTable.lookupStrict(const String& name) {
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2226492 times.
2226492 if name.isEmpty() {
221 ✗ return nil<SymbolTableEntry*>;
222 }
223 // Check if a symbol with this name exists in this scope
224
2/2
✓ Branch 0 taken 1129125 times.
✓ Branch 1 taken 1097367 times.
2226492 if this.symbols.contains(name) {
225 1129125 return &this.symbols.get(name);
226 }
227 // Check if a capture with this name exists in this scope
228
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 1097312 times.
1097367 if this.captures.contains(name) {
229 55 Capture& capture = this.captures.get(name);
230 55 return capture.capturedSymbol;
231 }
232 // Otherwise, return a nullptr
233 1097312 return nil<SymbolTableEntry*>;
234 }
235
236 /**
237 * Check if an order index exists in the current or any parent scope and returns it if possible.
238 * Warning: Unlike the `lookup` method, this one doesn't consider the parent scopes
239 *
240 * @param orderIndex Order index of the desired symbol
241 * @return Desired symbol / nullptr if the symbol was not found
242 */
243 224611 public f<SymbolTableEntry*> SymbolTable.lookupStrictByIndex(const unsigned int orderIndex) {
244
1/2
✓ Branch 0 taken 2325956 times.
✗ Branch 1 not taken.
2325956 foreach Pair<const String&, SymbolTableEntry&> symbolPair : this.symbols {
245 2325956 SymbolTableEntry& symbol = symbolPair.getSecond();
246
2/2
✓ Branch 0 taken 224611 times.
✓ Branch 1 taken 2101345 times.
2325956 if symbol.orderIndex == cast<unsigned long>(orderIndex) {
247 224611 return &symbol;
248 }
249 2101345 }
250 ✗ return nil<SymbolTableEntry*>;
251 }
252
253 /**
254 * Check if an anonymous symbol exists in the current scope and return it if possible
255 *
256 * @param codeLoc Definition code loc
257 * @param numericSuffix Numeric suffix of the anonymous symbol
258 * @return Anonymous symbol
259 */
260 26446 public f<SymbolTableEntry*> SymbolTable.lookupAnonymous(const ASTNode* declNode, unsigned long numericSuffix = 0l) {
261 26446 const String name = this.getAnonymousSymbolName(declNode, numericSuffix);
262 52892 return this.lookup(name);
263 26446 }
264
265 /**
266 * Check if a symbol exists in the current or any parent scope and return it if possible.
267 * If the target symbol is an alias symbol, resolve the alias container entry.
268 *
269 * @param name Name of the desired symbol
270 * @return Desired symbol / nil if the symbol was not found + alias or not
271 */
272 71216 public f<Pair<SymbolTableEntry*, bool>> SymbolTable.lookupWithAliasResolution(const String& name) {
273 71216 SymbolTableEntry* entry = this.lookup(name);
274
2/2
✓ Branch 0 taken 16635 times.
✓ Branch 1 taken 54581 times.
71216 if entry == nil<SymbolTableEntry*> {
275 54581 return Pair<SymbolTableEntry*, bool>(entry, false);
276 }
277 16635 const QualType entryType = entry.getQualType();
278
2/2
✓ Branch 0 taken 16629 times.
✓ Branch 1 taken 6 times.
16635 if !entryType.is(SuperType::TY_ALIAS) {
279 16629 return Pair<SymbolTableEntry*, bool>(entry, false);
280 }
281
282 // We have an alias type here, resolve it
283 6 assert entry.scope.isRootScope();
284 6 entry.used = true;
285 6 const String aliasedContainerEntryName = entry.name + ALIAS_CONTAINER_SUFFIX;
286 6 SymbolTableEntry* aliasedTypeContainerEntry = entry.scope.lookupStrict(aliasedContainerEntryName);
287 6 assert aliasedTypeContainerEntry != nil<SymbolTableEntry*>;
288 12 return Pair<SymbolTableEntry*, bool>(aliasedTypeContainerEntry, true);
289 6 }
290
291 /**
292 * Check if a capture exists in the current or any parent scope scope and return it if possible
293 *
294 * @param name Name of the desired captured symbol
295 * @return Capture / nullptr if the capture was not found
296 */
297 1158115 public f<Capture*> SymbolTable.lookupCapture(const String& name) {
298 // Check if the capture exists in the current scope. If yes, take it
299 1158115 Capture* capture = this.lookupCaptureStrict(name);
300
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 1157987 times.
1158115 if capture != nil<Capture*> {
301 128 return capture;
302 }
303
304 // We reached the root scope, the symbol does not exist at all
305
2/2
✓ Branch 0 taken 874341 times.
✓ Branch 1 taken 283646 times.
1157987 if this.parent == nil<SymbolTable*> {
306 283646 return nil<Capture*>;
307 }
308
309 874341 return this.parent.lookupCapture(name);
310 }
311
312 /**
313 * Check if a capture exists in the current scope and return it if possible
314 *
315 * @param name Name of the desired captured symbol
316 * @return Capture / nullptr if the capture was not found
317 */
318 1158115 public f<Capture*> SymbolTable.lookupCaptureStrict(const String& captureName) {
319 // If available in the current scope, return it
320
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 1157987 times.
1158115 if this.captures.contains(captureName) {
321 128 return &this.captures.get(captureName);
322 }
323 // Otherwise, return nullptr
324 1157987 return nil<Capture*>;
325 }
326
327 /**
328 * Set capturing for this scope to required.
329 */
330 144 public p SymbolTable.setCapturingRequired() {
331 144 this.capturingRequired = true;
332 144 }
333
334 /**
335 * Deletes an existing anonymous symbol
336 *
337 * @param name Anonymous symbol name
338 */
339 11662 public p SymbolTable.deleteAnonymous(const String& name) {
340
2/2
✓ Branch 0 taken 5108 times.
✓ Branch 1 taken 6554 times.
11662 if this.symbols.contains(name) {
341 6554 this.symbols.remove(name);
342 6554 }
343 11662 }
344
345 /**
346 * Stringify a symbol table to a human-readable form. This is used to realize dumps of symbol tables
347 *
348 * Example:
349 * {
350 * "name": "<SymbolTableName>"
351 * "symbols": [
352 * ... (SymbolTableEntry)
353 * ],
354 * "children": [
355 * ... (SymbolTable)
356 * ]
357 * }
358 *
359 * The returned heap-allocated JSON value is owned by the caller.
360 *
361 * @return Symbol table if form of a string
362 */
363 277588 public f<JsonValue*> SymbolTable.toJSON() {
364 // Collect all symbols, sorted by name
365 277588 Map<String, SymbolTableEntry*> sortedSymbols;
366
2/2
✓ Branch 0 taken 375876 times.
✓ Branch 1 taken 277588 times.
653464 foreach Pair<const String&, SymbolTableEntry&> symbol : this.symbols {
367 375876 sortedSymbols.upsert(symbol.getFirst(), &symbol.getSecond());
368 375876 }
369 277588 JsonValue* jsonSymbols = __new<JsonValue>(JsonValueKind::JSON_ARRAY);
370
2/2
✓ Branch 0 taken 375876 times.
✓ Branch 1 taken 277588 times.
653464 foreach Pair<const String&, SymbolTableEntry*&> symbol : sortedSymbols {
371 375876 SymbolTableEntry* entry = symbol.getSecond();
372 375876 jsonSymbols.addArrayItem(entry.toJSON());
373 375876 }
374
375 // Collect all captures, sorted by name
376 277588 Map<String, Capture*> sortedCaptures;
377
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 277588 times.
277680 foreach Pair<const String&, Capture&> capture : this.captures {
378 92 sortedCaptures.upsert(capture.getFirst(), &capture.getSecond());
379 92 }
380 277588 JsonValue* jsonCaptures = __new<JsonValue>(JsonValueKind::JSON_ARRAY);
381
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 277588 times.
277680 foreach Pair<const String&, Capture*&> capture : sortedCaptures {
382 92 Capture* capturePtr = capture.getSecond();
383 92 jsonCaptures.addArrayItem(capturePtr.toJSON());
384 92 }
385
386 // Generate json
387 277588 result = __new<JsonValue>(JsonValueKind::JSON_OBJECT);
388 277588 setJsonField(result, String("symbols"), jsonSymbols);
389 277588 setJsonField(result, String("captures"), jsonCaptures);
390 277588 }
391