GCC Code Coverage Report


Directory: ../
File: src/symboltablebuilder/SymbolTable.cpp
Date: 2025-11-16 23:33:18
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 58629 SymbolTableEntry *SymbolTable::insert(const std::string &name, ASTNode *declNode, bool isAnonymousSymbol) {
22 58629 const bool isGlobal = parent == nullptr;
23 58629 size_t orderIndex = SIZE_MAX;
24
2/2
✓ Branch 2 → 3 taken 56231 times.
✓ Branch 2 → 5 taken 2398 times.
58629 if (!isAnonymousSymbol)
25 430771 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 58629 times.
58629 assert(!symbols.contains(name));
28
5/10
✓ Branch 8 → 9 taken 58629 times.
✗ Branch 8 → 43 not taken.
✓ Branch 9 → 10 taken 58629 times.
✗ Branch 9 → 42 not taken.
✓ Branch 10 → 11 taken 58629 times.
✗ Branch 10 → 40 not taken.
✓ Branch 11 → 12 taken 58629 times.
✗ Branch 11 → 38 not taken.
✓ Branch 12 → 13 taken 58629 times.
✗ Branch 12 → 36 not taken.
58629 symbols.insert({name, SymbolTableEntry(name, QualType(TY_INVALID), scope, declNode, orderIndex, isGlobal)});
29 // Set entry to declared
30 58629 SymbolTableEntry *entry = &symbols.at(name);
31
1/2
✓ Branch 17 → 18 taken 58629 times.
✗ Branch 17 → 46 not taken.
58629 entry->updateState(DECLARED, declNode);
32
33 // Check if shadowed
34
8/8
✓ Branch 18 → 19 taken 47977 times.
✓ Branch 18 → 24 taken 10652 times.
✓ Branch 20 → 21 taken 824 times.
✓ Branch 20 → 24 taken 47153 times.
✓ Branch 22 → 23 taken 14 times.
✓ Branch 22 → 24 taken 810 times.
✓ Branch 25 → 26 taken 14 times.
✓ Branch 25 → 34 taken 58615 times.
58629 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 58629 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 2421 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 2421 times.
✗ Branch 2 → 38 not taken.
✓ Branch 3 → 4 taken 23 times.
✓ Branch 3 → 5 taken 2398 times.
2421 if (SymbolTableEntry *anonSymbol = lookupAnonymous(declNode->codeLoc, numericSuffix))
55 23 return anonSymbol;
56 // Otherwise, create an anonymous entry
57
1/2
✓ Branch 5 → 6 taken 2398 times.
✗ Branch 5 → 38 not taken.
2398 std::stringstream name;
58
3/6
✓ Branch 6 → 7 taken 2398 times.
✗ Branch 6 → 36 not taken.
✓ Branch 7 → 8 taken 2398 times.
✗ Branch 7 → 27 not taken.
✓ Branch 8 → 9 taken 2398 times.
✗ Branch 8 → 25 not taken.
2398 name << "anon." << declNode->codeLoc.toString();
59
2/2
✓ Branch 10 → 11 taken 48 times.
✓ Branch 10 → 16 taken 2350 times.
2398 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 2398 times.
✗ Branch 16 → 33 not taken.
✓ Branch 17 → 18 taken 2398 times.
✗ Branch 17 → 31 not taken.
2398 SymbolTableEntry *anonSymbol = insert(name.str(), declNode, true);
62
1/2
✓ Branch 19 → 20 taken 2398 times.
✗ Branch 19 → 36 not taken.
2398 anonSymbol->updateType(qualType, false);
63
1/2
✓ Branch 20 → 21 taken 2398 times.
✗ Branch 20 → 34 not taken.
2398 anonSymbol->updateState(DECLARED, declNode);
64
1/2
✓ Branch 21 → 22 taken 2398 times.
✗ Branch 21 → 35 not taken.
2398 anonSymbol->updateState(INITIALIZED, declNode);
65 2398 anonSymbol->anonymous = true;
66 2398 anonSymbol->used = true;
67 2398 return anonSymbol;
68 2398 }
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 3019 SymbolTableEntry *SymbolTable::copySymbol(const std::string &originalName, const std::string &newName) {
78
1/2
✓ Branch 2 → 3 taken 3019 times.
✗ Branch 2 → 19 not taken.
3019 SymbolTableEntry *entryToCopy = lookupStrict(originalName);
79
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3019 times.
3019 assert(entryToCopy != nullptr);
80
2/4
✓ Branch 5 → 6 taken 3019 times.
✗ Branch 5 → 18 not taken.
✓ Branch 6 → 7 taken 3019 times.
✗ Branch 6 → 16 not taken.
3019 auto [it, success] = symbols.insert({newName, *entryToCopy});
81
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3019 times.
3019 assert(success);
82 6038 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 282268 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 87264 times.
✓ Branch 3 → 5 taken 195004 times.
282268 if (SymbolTableEntry *entry = lookupStrict(name))
94 87264 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 78112 times.
✓ Branch 5 → 7 taken 116892 times.
195004 if (!parent)
99 78112 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 7171 times.
✓ Branch 8 → 11 taken 109721 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 7170 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 116891 times.
116892 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 116891 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 58232 times.
✓ Branch 15 → 17 taken 58659 times.
116891 if (!entry)
107 58232 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 58635 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 58635 times.
58659 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 58659 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 16265 std::pair<SymbolTableEntry *, bool> SymbolTable::lookupWithAliasResolution(const std::string &name) {
126
1/2
✓ Branch 2 → 3 taken 16265 times.
✗ Branch 2 → 30 not taken.
16265 SymbolTableEntry *entry = lookup(name);
127
8/10
✓ Branch 3 → 4 taken 3714 times.
✓ Branch 3 → 7 taken 12551 times.
✓ Branch 4 → 5 taken 3714 times.
✗ Branch 4 → 30 not taken.
✓ Branch 5 → 6 taken 3714 times.
✗ Branch 5 → 30 not taken.
✓ Branch 6 → 7 taken 3712 times.
✓ Branch 6 → 8 taken 2 times.
✓ Branch 9 → 10 taken 16263 times.
✓ Branch 9 → 13 taken 2 times.
16265 if (!entry || !entry->getQualType().is(TY_ALIAS))
128 16263 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 455894 SymbolTableEntry *SymbolTable::lookupStrict(const std::string &symbolName) {
146
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 455894 times.
455894 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 231969 times.
✓ Branch 6 → 9 taken 223925 times.
455894 if (symbols.contains(symbolName))
150 231969 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 223908 times.
223925 if (captures.contains(symbolName))
153 17 return captures.at(symbolName).capturedSymbol;
154 // Otherwise, return a nullptr
155 223908 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 32139 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 32139 times.
32139 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 32110 times.
✓ Branch 5 → 8 taken 29 times.
32139 if (SymbolTableEntry *result = lookupStrict(name)) {
172 32110 indexPath.push_back(result->orderIndex);
173 32110 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 37333 SymbolTableEntry *SymbolTable::lookupStrictByIndex(unsigned int orderIndex) {
209
4/8
✓ Branch 2 → 3 taken 37333 times.
✗ Branch 2 → 14 not taken.
✓ Branch 3 → 4 taken 37333 times.
✗ Branch 3 → 14 not taken.
✓ Branch 4 → 5 taken 37333 times.
✗ Branch 4 → 14 not taken.
✓ Branch 11 → 6 taken 363875 times.
✗ Branch 11 → 12 not taken.
363875 for (auto &val : symbols | std::views::values) {
210
2/2
✓ Branch 7 → 8 taken 37333 times.
✓ Branch 7 → 9 taken 326542 times.
363875 if (val.orderIndex == orderIndex)
211 37333 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 6478 SymbolTableEntry *SymbolTable::lookupAnonymous(const CodeLoc &codeLoc, size_t numericSuffix) {
224
2/4
✓ Branch 2 → 3 taken 6478 times.
✗ Branch 2 → 19 not taken.
✓ Branch 3 → 4 taken 6478 times.
✗ Branch 3 → 17 not taken.
6478 std::string name = "anon." + codeLoc.toString();
225
2/2
✓ Branch 5 → 6 taken 96 times.
✓ Branch 5 → 12 taken 6382 times.
6478 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 6478 times.
✗ Branch 12 → 26 not taken.
12956 return lookup(name);
228 6478 }
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 224134 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 224098 times.
224134 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 62654 times.
✓ Branch 5 → 7 taken 161444 times.
224098 if (parent == nullptr)
243 62654 return nullptr;
244
245 161444 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 224134 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 224098 times.
224134 if (captures.contains(name))
257 36 return &captures.at(name);
258 // Otherwise, return nullptr
259 224098 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 2069 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 78395 nlohmann::json SymbolTable::toJSON() const {
291 // Collect all symbols
292 78395 std::vector<nlohmann::json> jsonSymbols;
293
1/2
✓ Branch 3 → 4 taken 78395 times.
✗ Branch 3 → 59 not taken.
78395 jsonSymbols.reserve(symbols.size());
294
5/8
✓ Branch 4 → 5 taken 78395 times.
✗ Branch 4 → 44 not taken.
✓ Branch 5 → 6 taken 78395 times.
✗ Branch 5 → 44 not taken.
✓ Branch 6 → 7 taken 78395 times.
✗ Branch 6 → 44 not taken.
✓ Branch 14 → 8 taken 174844 times.
✓ Branch 14 → 15 taken 78395 times.
253239 for (const SymbolTableEntry &symbol : symbols | std::views::values)
295
2/4
✓ Branch 9 → 10 taken 174844 times.
✗ Branch 9 → 43 not taken.
✓ Branch 10 → 11 taken 174844 times.
✗ Branch 10 → 41 not taken.
174844 jsonSymbols.emplace_back(symbol.toJSON());
296
297 // Collect all captures
298 78395 std::vector<nlohmann::json> jsonCaptures;
299
1/2
✓ Branch 16 → 17 taken 78395 times.
✗ Branch 16 → 57 not taken.
78395 jsonCaptures.reserve(captures.size());
300
5/8
✓ Branch 17 → 18 taken 78395 times.
✗ Branch 17 → 48 not taken.
✓ Branch 18 → 19 taken 78395 times.
✗ Branch 18 → 48 not taken.
✓ Branch 19 → 20 taken 78395 times.
✗ Branch 19 → 48 not taken.
✓ Branch 27 → 21 taken 26 times.
✓ Branch 27 → 28 taken 78395 times.
78421 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 78395 nlohmann::json result;
305
2/4
✓ Branch 29 → 30 taken 78395 times.
✗ Branch 29 → 51 not taken.
✓ Branch 30 → 31 taken 78395 times.
✗ Branch 30 → 49 not taken.
78395 result["symbols"] = jsonSymbols;
306
2/4
✓ Branch 33 → 34 taken 78395 times.
✗ Branch 33 → 54 not taken.
✓ Branch 34 → 35 taken 78395 times.
✗ Branch 34 → 52 not taken.
78395 result["captures"] = jsonCaptures;
307 78395 return result;
308 78395 }
309
310 } // namespace spice::compiler
311