GCC Code Coverage Report


Directory: ../
File: src/symboltablebuilder/SymbolTable.cpp
Date: 2025-11-03 22:22:45
Coverage Exec Excl Total
Lines: 98.4% 120 0 122
Functions: 100.0% 15 0 15
Branches: 65.2% 137 0 210

Line Branch Exec Source
1 // Copyright (c) 2021-2025 ChilliBits. All rights reserved.
2
3 #include "SymbolTable.h"
4
5 #include "SourceFile.h"
6 #include <ast/ASTNodes.h>
7 #include <symboltablebuilder/SymbolTableBuilder.h>
8 #include <util/CodeLoc.h>
9 #include <util/CompilerWarning.h>
10
11 namespace spice::compiler {
12
13 /**
14 * Insert a new symbol into the current symbol table. If it is a parameter, append its name to the paramNames vector
15 *
16 * @param name Name of the symbol
17 * @param declNode AST node where the symbol is declared
18 * @param isAnonymousSymbol If this symbol should be anonymous
19 * @return Inserted entry
20 */
21 55826 SymbolTableEntry *SymbolTable::insert(const std::string &name, ASTNode *declNode, bool isAnonymousSymbol) {
22 55826 const bool isGlobal = parent == nullptr;
23 55826 size_t orderIndex = SIZE_MAX;
24
2/2
✓ Branch 2 → 3 taken 53528 times.
✓ Branch 2 → 5 taken 2298 times.
55826 if (!isAnonymousSymbol)
25 417501 orderIndex = std::ranges::count_if(symbols, [](const auto &entry) { return !entry.second.anonymous; });
26 // Insert into symbols map. The type is 'dyn', because concrete types are determined by the type checker later on
27
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 55826 times.
55826 assert(!symbols.contains(name));
28
5/10
✓ Branch 8 → 9 taken 55826 times.
✗ Branch 8 → 43 not taken.
✓ Branch 9 → 10 taken 55826 times.
✗ Branch 9 → 42 not taken.
✓ Branch 10 → 11 taken 55826 times.
✗ Branch 10 → 40 not taken.
✓ Branch 11 → 12 taken 55826 times.
✗ Branch 11 → 38 not taken.
✓ Branch 12 → 13 taken 55826 times.
✗ Branch 12 → 36 not taken.
55826 symbols.insert({name, SymbolTableEntry(name, QualType(TY_INVALID), scope, declNode, orderIndex, isGlobal)});
29 // Set entry to declared
30 55826 SymbolTableEntry *entry = &symbols.at(name);
31
1/2
✓ Branch 17 → 18 taken 55826 times.
✗ Branch 17 → 46 not taken.
55826 entry->updateState(DECLARED, declNode);
32
33 // Check if shadowed
34
8/8
✓ Branch 18 → 19 taken 45675 times.
✓ Branch 18 → 24 taken 10151 times.
✓ Branch 20 → 21 taken 780 times.
✓ Branch 20 → 24 taken 44895 times.
✓ Branch 22 → 23 taken 14 times.
✓ Branch 22 → 24 taken 766 times.
✓ Branch 25 → 26 taken 14 times.
✓ Branch 25 → 34 taken 55812 times.
55826 if (parent != nullptr && parent->lookup(name) != nullptr && !declNode->isParam()) {
35
2/4
✓ Branch 26 → 27 taken 14 times.
✗ Branch 26 → 49 not taken.
✓ Branch 27 → 28 taken 14 times.
✗ Branch 27 → 47 not taken.
14 const std::string warningMsg = "Variable '" + name + "' shadows a variable in a parent scope";
36
1/2
✓ Branch 29 → 30 taken 14 times.
✗ Branch 29 → 52 not taken.
14 const CompilerWarning warning(declNode->codeLoc, SHADOWED_VARIABLE, warningMsg);
37
1/2
✓ Branch 30 → 31 taken 14 times.
✗ Branch 30 → 50 not taken.
14 scope->sourceFile->compilerOutput.warnings.push_back(warning);
38 14 }
39
40 55826 return entry;
41 }
42
43 /**
44 * Insert a new anonymous symbol into the current symbol table.
45 * The anonymous symbol will be identified via the definition code location
46 *
47 * @param qualType Type of the symbol
48 * @param declNode AST node where the anonymous symbol is declared
49 * @param numericSuffix Custom numeric suffix
50 * @return Inserted entry
51 */
52 2321 SymbolTableEntry *SymbolTable::insertAnonymous(const QualType &qualType, ASTNode *declNode, size_t numericSuffix) {
53 // Check if the anonymous entry already exists
54
3/4
✓ Branch 2 → 3 taken 2321 times.
✗ Branch 2 → 38 not taken.
✓ Branch 3 → 4 taken 23 times.
✓ Branch 3 → 5 taken 2298 times.
2321 if (SymbolTableEntry *anonSymbol = lookupAnonymous(declNode->codeLoc, numericSuffix))
55 23 return anonSymbol;
56 // Otherwise, create an anonymous entry
57
1/2
✓ Branch 5 → 6 taken 2298 times.
✗ Branch 5 → 38 not taken.
2298 std::stringstream name;
58
3/6
✓ Branch 6 → 7 taken 2298 times.
✗ Branch 6 → 36 not taken.
✓ Branch 7 → 8 taken 2298 times.
✗ Branch 7 → 27 not taken.
✓ Branch 8 → 9 taken 2298 times.
✗ Branch 8 → 25 not taken.
2298 name << "anon." << declNode->codeLoc.toString();
59
2/2
✓ Branch 10 → 11 taken 48 times.
✓ Branch 10 → 16 taken 2250 times.
2298 if (numericSuffix > 0)
60
3/6
✓ Branch 11 → 12 taken 48 times.
✗ Branch 11 → 36 not taken.
✓ Branch 12 → 13 taken 48 times.
✗ Branch 12 → 30 not taken.
✓ Branch 13 → 14 taken 48 times.
✗ Branch 13 → 28 not taken.
48 name << '.' << std::to_string(numericSuffix);
61
2/4
✓ Branch 16 → 17 taken 2298 times.
✗ Branch 16 → 33 not taken.
✓ Branch 17 → 18 taken 2298 times.
✗ Branch 17 → 31 not taken.
2298 SymbolTableEntry *anonSymbol = insert(name.str(), declNode, true);
62
1/2
✓ Branch 19 → 20 taken 2298 times.
✗ Branch 19 → 36 not taken.
2298 anonSymbol->updateType(qualType, false);
63
1/2
✓ Branch 20 → 21 taken 2298 times.
✗ Branch 20 → 34 not taken.
2298 anonSymbol->updateState(DECLARED, declNode);
64
1/2
✓ Branch 21 → 22 taken 2298 times.
✗ Branch 21 → 35 not taken.
2298 anonSymbol->updateState(INITIALIZED, declNode);
65 2298 anonSymbol->anonymous = true;
66 2298 anonSymbol->used = true;
67 2298 return anonSymbol;
68 2298 }
69
70 /**
71 * Copy a symbol by its name
72 *
73 * @param originalName Original symbol name
74 * @param newName New symbol name
75 * @return Copied entry
76 */
77 2813 SymbolTableEntry *SymbolTable::copySymbol(const std::string &originalName, const std::string &newName) {
78
1/2
✓ Branch 2 → 3 taken 2813 times.
✗ Branch 2 → 19 not taken.
2813 SymbolTableEntry *entryToCopy = lookupStrict(originalName);
79
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2813 times.
2813 assert(entryToCopy != nullptr);
80
2/4
✓ Branch 5 → 6 taken 2813 times.
✗ Branch 5 → 18 not taken.
✓ Branch 6 → 7 taken 2813 times.
✗ Branch 6 → 16 not taken.
2813 auto [it, success] = symbols.insert({newName, *entryToCopy});
81
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2813 times.
2813 assert(success);
82 5626 return &it->second;
83 }
84
85 /**
86 * Check if a symbol exists in the current or any parent scope and return it if possible
87 *
88 * @param name Name of the desired symbol
89 * @return Desired symbol / nullptr if the symbol was not found
90 */
91 268396 SymbolTableEntry *SymbolTable::lookup(const std::string &name) { // NOLINT(misc-no-recursion)
92 // Check if the symbol exists in the current scope. If yes, take it
93
2/2
✓ Branch 3 → 4 taken 83075 times.
✓ Branch 3 → 5 taken 185321 times.
268396 if (SymbolTableEntry *entry = lookupStrict(name))
94 83075 return entry;
95
96 // Symbol was not found in the current scope
97 // We reached the root scope, the symbol does not exist at all
98
2/2
✓ Branch 5 → 6 taken 73922 times.
✓ Branch 5 → 7 taken 111399 times.
185321 if (!parent)
99 73922 return nullptr;
100 // If we search for the result variable, we want to stop the search when exiting a lambda body
101
6/6
✓ Branch 8 → 9 taken 6859 times.
✓ Branch 8 → 11 taken 104540 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 6858 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 111398 times.
111399 if (name == RETURN_VARIABLE_NAME && scope->type == ScopeType::LAMBDA_BODY)
102 1 return nullptr;
103 // If there is a parent scope, continue the search there
104 111398 SymbolTableEntry *entry = parent->lookup(name);
105 // Symbol was also not found in all the parent scopes, return nullptr
106
2/2
✓ Branch 15 → 16 taken 55199 times.
✓ Branch 15 → 17 taken 56199 times.
111398 if (!entry)
107 55199 return nullptr;
108 // Check if this scope requires capturing and capture the variable if appropriate
109
9/14
✓ Branch 17 → 18 taken 24 times.
✓ Branch 17 → 24 taken 56175 times.
✓ Branch 18 → 19 taken 24 times.
✗ Branch 18 → 37 not taken.
✓ Branch 19 → 20 taken 24 times.
✗ Branch 19 → 24 not taken.
✓ Branch 20 → 21 taken 24 times.
✗ Branch 20 → 37 not taken.
✓ Branch 21 → 22 taken 24 times.
✗ Branch 21 → 37 not taken.
✓ Branch 22 → 23 taken 24 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 24 times.
✓ Branch 25 → 35 taken 56175 times.
56199 if (capturingRequired && !captures.contains(name) && !entry->getQualType().isOneOf({TY_IMPORT, TY_FUNCTION, TY_PROCEDURE})) {
110 // 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
111
3/4
✓ Branch 27 → 28 taken 8 times.
✓ Branch 27 → 31 taken 16 times.
✓ Branch 29 → 30 taken 8 times.
✗ Branch 29 → 31 not taken.
24 entry->isVolatile = scope->isInAsyncScope() && !entry->scope->isInAsyncScope();
112 // Add the capture to the current scope
113
2/4
✓ Branch 32 → 33 taken 24 times.
✗ Branch 32 → 38 not taken.
✓ Branch 33 → 34 taken 24 times.
✗ Branch 33 → 38 not taken.
24 captures.emplace(name, Capture(entry));
114 }
115 56199 return entry;
116 }
117
118 /**
119 * Check if a symbol exists in the current or any parent scope and return it if possible.
120 * If the target symbol is an alias symbol, resolve the alias container entry.
121 *
122 * @param name Name of the desired symbol
123 * @return Desired symbol / nullptr if the symbol was not found + alias or not
124 */
125 15459 std::pair<SymbolTableEntry *, bool> SymbolTable::lookupWithAliasResolution(const std::string &name) {
126
1/2
✓ Branch 2 → 3 taken 15459 times.
✗ Branch 2 → 30 not taken.
15459 SymbolTableEntry *entry = lookup(name);
127
8/10
✓ Branch 3 → 4 taken 3590 times.
✓ Branch 3 → 7 taken 11869 times.
✓ Branch 4 → 5 taken 3590 times.
✗ Branch 4 → 30 not taken.
✓ Branch 5 → 6 taken 3590 times.
✗ Branch 5 → 30 not taken.
✓ Branch 6 → 7 taken 3588 times.
✓ Branch 6 → 8 taken 2 times.
✓ Branch 9 → 10 taken 15457 times.
✓ Branch 9 → 13 taken 2 times.
15459 if (!entry || !entry->getQualType().is(TY_ALIAS))
128 15457 return {entry, false};
129
130 // We have an alias type here, resolve it
131
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 2 times.
4 assert(entry->scope->isRootScope());
132 2 entry->used = true;
133
1/2
✓ Branch 17 → 18 taken 2 times.
✗ Branch 17 → 30 not taken.
2 const std::string aliasedContainerEntryName = entry->name + ALIAS_CONTAINER_SUFFIX;
134
1/2
✓ Branch 18 → 19 taken 2 times.
✗ Branch 18 → 28 not taken.
2 SymbolTableEntry *aliasedTypeContainerEntry = entry->scope->lookupStrict(aliasedContainerEntryName);
135
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2 times.
2 assert(aliasedTypeContainerEntry != nullptr);
136 2 return {aliasedTypeContainerEntry, true};
137 2 }
138
139 /**
140 * Check if a symbol exists in the current scope and return it if possible
141 *
142 * @param symbolName Name of the desired symbol
143 * @return Desired symbol / nullptr if the symbol was not found
144 */
145 433581 SymbolTableEntry *SymbolTable::lookupStrict(const std::string &symbolName) {
146
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 433581 times.
433581 if (symbolName.empty())
147 return nullptr;
148 // Check if a symbol with this name exists in this scope
149
2/2
✓ Branch 6 → 7 taken 220705 times.
✓ Branch 6 → 9 taken 212876 times.
433581 if (symbols.contains(symbolName))
150 220705 return &symbols.at(symbolName);
151 // Check if a capture with this name exists in this scope
152
2/2
✓ Branch 10 → 11 taken 17 times.
✓ Branch 10 → 13 taken 212859 times.
212876 if (captures.contains(symbolName))
153 17 return captures.at(symbolName).capturedSymbol;
154 // Otherwise, return a nullptr
155 212859 return nullptr;
156 }
157
158 /**
159 * Check if a symbol exists in one of the composed field scopes of the current scope and return it if possible.
160 * This only works if the current scope is a struct scope.
161 *
162 * @param name Name of the desired symbol
163 * @param indexPath How to index the found symbol using order indices (e.g. for GEP)
164 * @return Desired symbol / nullptr if the symbol was not found
165 */
166 30907 SymbolTableEntry *SymbolTable::lookupInComposedFields(const std::string &name, // NOLINT(misc-no-recursion)
167 std::vector<size_t> &indexPath) {
168
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 30907 times.
30907 assert(scope->type == ScopeType::STRUCT);
169
170 // Check if we have a symbol with this name in the current scope
171
2/2
✓ Branch 5 → 6 taken 30878 times.
✓ Branch 5 → 8 taken 29 times.
30907 if (SymbolTableEntry *result = lookupStrict(name)) {
172 30878 indexPath.push_back(result->orderIndex);
173 30878 return result;
174 }
175
176 // If it was not found in the current scope, loop through all composed fields in this scope
177
2/2
✓ Branch 25 → 9 taken 41 times.
✓ Branch 25 → 26 taken 13 times.
54 for (size_t i = 0; i < scope->getFieldCount(); i++) {
178 41 const SymbolTableEntry *fieldEntry = lookupStrictByIndex(i);
179
180 // Skip all fields that are not composition fields
181
2/2
✓ Branch 12 → 13 taken 14 times.
✓ Branch 12 → 14 taken 27 times.
41 if (!fieldEntry->getQualType().isComposition())
182 14 continue;
183
184 // Add the current field's order index to the index path
185 27 indexPath.push_back(fieldEntry->orderIndex);
186
187 // Search in the composed field's body scope
188 27 Scope *searchScope = fieldEntry->getQualType().getBodyScope();
189
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 27 times.
27 assert(searchScope != nullptr);
190
2/2
✓ Branch 20 → 21 taken 16 times.
✓ Branch 20 → 22 taken 11 times.
27 if (SymbolTableEntry *result = searchScope->symbolTable.lookupInComposedFields(name, indexPath))
191 16 return result;
192
193 // Remove the current field's order index from the index path
194 11 indexPath.pop_back();
195 }
196
197 // Symbol was not found in current scope, return nullptr
198 13 return nullptr;
199 }
200
201 /**
202 * Check if an order index exists in the current or any parent scope and returns it if possible.
203 * Warning: Unlike the `lookup` method, this one doesn't consider the parent scopes
204 *
205 * @param orderIndex Order index of the desired symbol
206 * @return Desired symbol / nullptr if the symbol was not found
207 */
208 36003 SymbolTableEntry *SymbolTable::lookupStrictByIndex(unsigned int orderIndex) {
209
4/8
✓ Branch 2 → 3 taken 36003 times.
✗ Branch 2 → 14 not taken.
✓ Branch 3 → 4 taken 36003 times.
✗ Branch 3 → 14 not taken.
✓ Branch 4 → 5 taken 36003 times.
✗ Branch 4 → 14 not taken.
✓ Branch 11 → 6 taken 351008 times.
✗ Branch 11 → 12 not taken.
351008 for (auto &val : symbols | std::views::values) {
210
2/2
✓ Branch 7 → 8 taken 36003 times.
✓ Branch 7 → 9 taken 315005 times.
351008 if (val.orderIndex == orderIndex)
211 36003 return &val;
212 }
213 return nullptr;
214 }
215
216 /**
217 * Check if an anonymous symbol exists in the current scope and return it if possible
218 *
219 * @param codeLoc Definition code loc
220 * @param numericSuffix Numeric suffix of the anonymous symbol
221 * @return Anonymous symbol
222 */
223 6208 SymbolTableEntry *SymbolTable::lookupAnonymous(const CodeLoc &codeLoc, size_t numericSuffix) {
224
2/4
✓ Branch 2 → 3 taken 6208 times.
✗ Branch 2 → 19 not taken.
✓ Branch 3 → 4 taken 6208 times.
✗ Branch 3 → 17 not taken.
6208 std::string name = "anon." + codeLoc.toString();
225
2/2
✓ Branch 5 → 6 taken 96 times.
✓ Branch 5 → 12 taken 6112 times.
6208 if (numericSuffix > 0)
226
3/6
✓ Branch 6 → 7 taken 96 times.
✗ Branch 6 → 24 not taken.
✓ Branch 7 → 8 taken 96 times.
✗ Branch 7 → 22 not taken.
✓ Branch 8 → 9 taken 96 times.
✗ Branch 8 → 20 not taken.
96 name += "." + std::to_string(numericSuffix);
227
1/2
✓ Branch 12 → 13 taken 6208 times.
✗ Branch 12 → 26 not taken.
12416 return lookup(name);
228 6208 }
229
230 /**
231 * Check if a capture exists in the current or any parent scope and return it if possible
232 *
233 * @param name Name of the desired captured symbol
234 * @return Capture / nullptr if the capture was not found
235 */
236 213069 Capture *SymbolTable::lookupCapture(const std::string &name) { // NOLINT(misc-no-recursion)
237 // Check if the capture exists in the current scope. If yes, take it
238
2/2
✓ Branch 3 → 4 taken 36 times.
✓ Branch 3 → 5 taken 213033 times.
213069 if (Capture *capture = lookupCaptureStrict(name))
239 36 return capture;
240
241 // We reached the root scope, the symbol does not exist at all
242
2/2
✓ Branch 5 → 6 taken 59262 times.
✓ Branch 5 → 7 taken 153771 times.
213033 if (parent == nullptr)
243 59262 return nullptr;
244
245 153771 return parent->lookupCapture(name);
246 }
247
248 /**
249 * Check if a capture exists in the current scope and return it if possible
250 *
251 * @param name Name of the desired captured symbol
252 * @return Capture / nullptr if the capture was not found
253 */
254 213069 Capture *SymbolTable::lookupCaptureStrict(const std::string &name) {
255 // If available in the current scope, return it
256
2/2
✓ Branch 3 → 4 taken 36 times.
✓ Branch 3 → 6 taken 213033 times.
213069 if (captures.contains(name))
257 36 return &captures.at(name);
258 // Otherwise, return nullptr
259 213033 return nullptr;
260 }
261
262 /**
263 * Set capturing for this scope required.
264 */
265 40 void SymbolTable::setCapturingRequired() { capturingRequired = true; }
266
267 /**
268 * Deletes an existing anonymous symbol
269 *
270 * @param name Anonymous symbol name
271 */
272 1977 void SymbolTable::deleteAnonymous(const std::string &name) { symbols.erase(name); }
273
274 /**
275 * Stringify a symbol table to a human-readable form. This is used to realize dumps of symbol tables
276 *
277 * Example:
278 * {
279 * "name": "<SymbolTableName>"
280 * "symbols": [
281 * ... (SymbolTableEntry)
282 * ],
283 * "children": [
284 * ... (SymbolTable)
285 * ]
286 * }
287 *
288 * @return Symbol table if form of a string
289 */
290 75575 nlohmann::json SymbolTable::toJSON() const {
291 // Collect all symbols
292 75575 std::vector<nlohmann::json> jsonSymbols;
293
1/2
✓ Branch 3 → 4 taken 75575 times.
✗ Branch 3 → 59 not taken.
75575 jsonSymbols.reserve(symbols.size());
294
5/8
✓ Branch 4 → 5 taken 75575 times.
✗ Branch 4 → 44 not taken.
✓ Branch 5 → 6 taken 75575 times.
✗ Branch 5 → 44 not taken.
✓ Branch 6 → 7 taken 75575 times.
✗ Branch 6 → 44 not taken.
✓ Branch 14 → 8 taken 168188 times.
✓ Branch 14 → 15 taken 75575 times.
243763 for (const SymbolTableEntry &symbol : symbols | std::views::values)
295
2/4
✓ Branch 9 → 10 taken 168188 times.
✗ Branch 9 → 43 not taken.
✓ Branch 10 → 11 taken 168188 times.
✗ Branch 10 → 41 not taken.
168188 jsonSymbols.emplace_back(symbol.toJSON());
296
297 // Collect all captures
298 75575 std::vector<nlohmann::json> jsonCaptures;
299
1/2
✓ Branch 16 → 17 taken 75575 times.
✗ Branch 16 → 57 not taken.
75575 jsonCaptures.reserve(captures.size());
300
5/8
✓ Branch 17 → 18 taken 75575 times.
✗ Branch 17 → 48 not taken.
✓ Branch 18 → 19 taken 75575 times.
✗ Branch 18 → 48 not taken.
✓ Branch 19 → 20 taken 75575 times.
✗ Branch 19 → 48 not taken.
✓ Branch 27 → 21 taken 26 times.
✓ Branch 27 → 28 taken 75575 times.
75601 for (const Capture &capture : captures | std::views::values)
301
2/4
✓ Branch 22 → 23 taken 26 times.
✗ Branch 22 → 47 not taken.
✓ Branch 23 → 24 taken 26 times.
✗ Branch 23 → 45 not taken.
26 jsonCaptures.emplace_back(capture.toJSON());
302
303 // Generate json
304 75575 nlohmann::json result;
305
2/4
✓ Branch 29 → 30 taken 75575 times.
✗ Branch 29 → 51 not taken.
✓ Branch 30 → 31 taken 75575 times.
✗ Branch 30 → 49 not taken.
75575 result["symbols"] = jsonSymbols;
306
2/4
✓ Branch 33 → 34 taken 75575 times.
✗ Branch 33 → 54 not taken.
✓ Branch 34 → 35 taken 75575 times.
✗ Branch 34 → 52 not taken.
75575 result["captures"] = jsonCaptures;
307 75575 return result;
308 75575 }
309
310 } // namespace spice::compiler
311