GCC Code Coverage Report


Directory: ../
File: src/symboltablebuilder/SymbolTable.cpp
Date: 2025-03-05 01:50:32
Exec Total Coverage
Lines: 120 122 98.4%
Functions: 15 15 100.0%
Branches: 137 210 65.2%

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 48676 SymbolTableEntry *SymbolTable::insert(const std::string &name, ASTNode *declNode, bool isAnonymousSymbol) {
22 48676 const bool isGlobal = parent == nullptr;
23 48676 size_t orderIndex = SIZE_MAX;
24
2/2
✓ Branch 0 (2→3) taken 46668 times.
✓ Branch 1 (2→5) taken 2008 times.
48676 if (!isAnonymousSymbol)
25 360645 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 0 (6→7) not taken.
✓ Branch 1 (6→8) taken 48676 times.
48676 assert(!symbols.contains(name));
28
5/10
✓ Branch 0 (8→9) taken 48676 times.
✗ Branch 1 (8→43) not taken.
✓ Branch 2 (9→10) taken 48676 times.
✗ Branch 3 (9→42) not taken.
✓ Branch 4 (10→11) taken 48676 times.
✗ Branch 5 (10→40) not taken.
✓ Branch 6 (11→12) taken 48676 times.
✗ Branch 7 (11→38) not taken.
✓ Branch 8 (12→13) taken 48676 times.
✗ Branch 9 (12→36) not taken.
48676 symbols.insert({name, SymbolTableEntry(name, QualType(TY_INVALID), scope, declNode, orderIndex, isGlobal)});
29 // Set entry to declared
30 48676 SymbolTableEntry *entry = &symbols.at(name);
31
1/2
✓ Branch 0 (17→18) taken 48676 times.
✗ Branch 1 (17→46) not taken.
48676 entry->updateState(DECLARED, declNode);
32
33 // Check if shadowed
34
8/8
✓ Branch 0 (18→19) taken 40082 times.
✓ Branch 1 (18→24) taken 8594 times.
✓ Branch 2 (20→21) taken 709 times.
✓ Branch 3 (20→24) taken 39373 times.
✓ Branch 4 (22→23) taken 14 times.
✓ Branch 5 (22→24) taken 695 times.
✓ Branch 6 (25→26) taken 14 times.
✓ Branch 7 (25→34) taken 48662 times.
48676 if (parent != nullptr && parent->lookup(name) != nullptr && !declNode->isParam()) {
35
2/4
✓ Branch 0 (26→27) taken 14 times.
✗ Branch 1 (26→49) not taken.
✓ Branch 2 (27→28) taken 14 times.
✗ Branch 3 (27→47) not taken.
14 const std::string warningMsg = "Variable '" + name + "' shadows a variable in a parent scope";
36
1/2
✓ Branch 0 (29→30) taken 14 times.
✗ Branch 1 (29→52) not taken.
14 const CompilerWarning warning(declNode->codeLoc, SHADOWED_VARIABLE, warningMsg);
37
1/2
✓ Branch 0 (30→31) taken 14 times.
✗ Branch 1 (30→50) not taken.
14 scope->sourceFile->compilerOutput.warnings.push_back(warning);
38 14 }
39
40 48676 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 2031 SymbolTableEntry *SymbolTable::insertAnonymous(const QualType &qualType, ASTNode *declNode, size_t numericSuffix) {
53 // Check if the anonymous entry already exists
54
3/4
✓ Branch 0 (2→3) taken 2031 times.
✗ Branch 1 (2→38) not taken.
✓ Branch 2 (3→4) taken 23 times.
✓ Branch 3 (3→5) taken 2008 times.
2031 if (SymbolTableEntry *anonSymbol = lookupAnonymous(declNode->codeLoc, numericSuffix))
55 23 return anonSymbol;
56 // Otherwise, create an anonymous entry
57
1/2
✓ Branch 0 (5→6) taken 2008 times.
✗ Branch 1 (5→38) not taken.
2008 std::stringstream name;
58
3/6
✓ Branch 0 (6→7) taken 2008 times.
✗ Branch 1 (6→36) not taken.
✓ Branch 2 (7→8) taken 2008 times.
✗ Branch 3 (7→27) not taken.
✓ Branch 4 (8→9) taken 2008 times.
✗ Branch 5 (8→25) not taken.
2008 name << "anon." << declNode->codeLoc.toString();
59
2/2
✓ Branch 0 (10→11) taken 38 times.
✓ Branch 1 (10→16) taken 1970 times.
2008 if (numericSuffix > 0)
60
3/6
✓ Branch 0 (11→12) taken 38 times.
✗ Branch 1 (11→36) not taken.
✓ Branch 2 (12→13) taken 38 times.
✗ Branch 3 (12→30) not taken.
✓ Branch 4 (13→14) taken 38 times.
✗ Branch 5 (13→28) not taken.
38 name << "." << std::to_string(numericSuffix);
61
2/4
✓ Branch 0 (16→17) taken 2008 times.
✗ Branch 1 (16→33) not taken.
✓ Branch 2 (17→18) taken 2008 times.
✗ Branch 3 (17→31) not taken.
2008 SymbolTableEntry *anonSymbol = insert(name.str(), declNode, true);
62
1/2
✓ Branch 0 (19→20) taken 2008 times.
✗ Branch 1 (19→36) not taken.
2008 anonSymbol->updateType(qualType, false);
63
1/2
✓ Branch 0 (20→21) taken 2008 times.
✗ Branch 1 (20→34) not taken.
2008 anonSymbol->updateState(DECLARED, declNode);
64
1/2
✓ Branch 0 (21→22) taken 2008 times.
✗ Branch 1 (21→35) not taken.
2008 anonSymbol->updateState(INITIALIZED, declNode);
65 2008 anonSymbol->anonymous = true;
66 2008 anonSymbol->used = true;
67 2008 return anonSymbol;
68 2008 }
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 2238 SymbolTableEntry *SymbolTable::copySymbol(const std::string &originalName, const std::string &newName) {
78
1/2
✓ Branch 0 (2→3) taken 2238 times.
✗ Branch 1 (2→19) not taken.
2238 SymbolTableEntry *entryToCopy = lookupStrict(originalName);
79
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 2238 times.
2238 assert(entryToCopy != nullptr);
80
2/4
✓ Branch 0 (5→6) taken 2238 times.
✗ Branch 1 (5→18) not taken.
✓ Branch 2 (6→7) taken 2238 times.
✗ Branch 3 (6→16) not taken.
2238 auto [it, success] = symbols.insert({newName, *entryToCopy});
81
1/2
✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→12) taken 2238 times.
2238 assert(success);
82 4476 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 241616 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 0 (3→4) taken 74161 times.
✓ Branch 1 (3→5) taken 167455 times.
241616 if (SymbolTableEntry *entry = lookupStrict(name))
94 74161 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 0 (5→6) taken 65356 times.
✓ Branch 1 (5→7) taken 102099 times.
167455 if (!parent)
99 65356 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 0 (8→9) taken 6227 times.
✓ Branch 1 (8→11) taken 95872 times.
✓ Branch 2 (9→10) taken 1 times.
✓ Branch 3 (9→11) taken 6226 times.
✓ Branch 4 (12→13) taken 1 times.
✓ Branch 5 (12→14) taken 102098 times.
102099 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 102098 SymbolTableEntry *entry = parent->lookup(name);
105 // Symbol was also not found in all the parent scopes, return nullptr
106
2/2
✓ Branch 0 (15→16) taken 50134 times.
✓ Branch 1 (15→17) taken 51964 times.
102098 if (!entry)
107 50134 return nullptr;
108 // Check if this scope requires capturing and capture the variable if appropriate
109
9/14
✓ Branch 0 (17→18) taken 24 times.
✓ Branch 1 (17→24) taken 51940 times.
✓ Branch 2 (18→19) taken 24 times.
✗ Branch 3 (18→37) not taken.
✓ Branch 4 (19→20) taken 24 times.
✗ Branch 5 (19→24) not taken.
✓ Branch 6 (20→21) taken 24 times.
✗ Branch 7 (20→37) not taken.
✓ Branch 8 (21→22) taken 24 times.
✗ Branch 9 (21→37) not taken.
✓ Branch 10 (22→23) taken 24 times.
✗ Branch 11 (22→24) not taken.
✓ Branch 12 (25→26) taken 24 times.
✓ Branch 13 (25→35) taken 51940 times.
51964 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 0 (27→28) taken 8 times.
✓ Branch 1 (27→31) taken 16 times.
✓ Branch 2 (29→30) taken 8 times.
✗ Branch 3 (29→31) not taken.
24 entry->isVolatile = scope->isInAsyncScope() && !entry->scope->isInAsyncScope();
112 // Add the capture to the current scope
113
2/4
✓ Branch 0 (32→33) taken 24 times.
✗ Branch 1 (32→38) not taken.
✓ Branch 2 (33→34) taken 24 times.
✗ Branch 3 (33→38) not taken.
24 captures.emplace(name, Capture(entry));
114 }
115 51964 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 13790 std::pair<SymbolTableEntry *, bool> SymbolTable::lookupWithAliasResolution(const std::string &name) {
126
1/2
✓ Branch 0 (2→3) taken 13790 times.
✗ Branch 1 (2→30) not taken.
13790 SymbolTableEntry *entry = lookup(name);
127
8/10
✓ Branch 0 (3→4) taken 3126 times.
✓ Branch 1 (3→7) taken 10664 times.
✓ Branch 2 (4→5) taken 3126 times.
✗ Branch 3 (4→30) not taken.
✓ Branch 4 (5→6) taken 3126 times.
✗ Branch 5 (5→30) not taken.
✓ Branch 6 (6→7) taken 3124 times.
✓ Branch 7 (6→8) taken 2 times.
✓ Branch 8 (9→10) taken 13788 times.
✓ Branch 9 (9→13) taken 2 times.
13790 if (!entry || !entry->getQualType().is(TY_ALIAS))
128 13788 return {entry, false};
129
130 // We have an alias type here, resolve it
131
1/2
✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 2 times.
4 assert(entry->scope->isRootScope());
132 2 entry->used = true;
133
1/2
✓ Branch 0 (17→18) taken 2 times.
✗ Branch 1 (17→30) not taken.
2 const std::string aliasedContainerEntryName = entry->name + ALIAS_CONTAINER_SUFFIX;
134
1/2
✓ Branch 0 (18→19) taken 2 times.
✗ Branch 1 (18→28) not taken.
2 SymbolTableEntry *aliasedTypeContainerEntry = entry->scope->lookupStrict(aliasedContainerEntryName);
135
1/2
✗ Branch 0 (21→22) not taken.
✓ Branch 1 (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 name Name of the desired symbol
143 * @return Desired symbol / nullptr if the symbol was not found
144 */
145 384501 SymbolTableEntry *SymbolTable::lookupStrict(const std::string &name) {
146
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 384501 times.
384501 if (name.empty())
147 return nullptr;
148 // Check if a symbol with this name exists in this scope
149
2/2
✓ Branch 0 (6→7) taken 192507 times.
✓ Branch 1 (6→9) taken 191994 times.
384501 if (symbols.contains(name))
150 192507 return &symbols.at(name);
151 // Check if a capture with this name exists in this scope
152
2/2
✓ Branch 0 (10→11) taken 17 times.
✓ Branch 1 (10→13) taken 191977 times.
191994 if (captures.contains(name))
153 17 return captures.at(name).capturedSymbol;
154 // Otherwise, return a nullptr
155 191977 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 28033 SymbolTableEntry *SymbolTable::lookupInComposedFields(const std::string &name, // NOLINT(misc-no-recursion)
167 std::vector<size_t> &indexPath) {
168
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 28033 times.
28033 assert(scope->type == ScopeType::STRUCT);
169
170 // Check if we have a symbol with this name in the current scope
171
2/2
✓ Branch 0 (5→6) taken 28004 times.
✓ Branch 1 (5→8) taken 29 times.
28033 if (SymbolTableEntry *result = lookupStrict(name)) {
172 28004 indexPath.push_back(result->orderIndex);
173 28004 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 0 (25→9) taken 41 times.
✓ Branch 1 (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 0 (12→13) taken 14 times.
✓ Branch 1 (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 0 (17→18) not taken.
✓ Branch 1 (17→19) taken 27 times.
27 assert(searchScope != nullptr);
190
2/2
✓ Branch 0 (20→21) taken 16 times.
✓ Branch 1 (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 30157 SymbolTableEntry *SymbolTable::lookupStrictByIndex(unsigned int orderIndex) {
209
4/8
✓ Branch 0 (2→3) taken 30157 times.
✗ Branch 1 (2→14) not taken.
✓ Branch 2 (3→4) taken 30157 times.
✗ Branch 3 (3→14) not taken.
✓ Branch 4 (4→5) taken 30157 times.
✗ Branch 5 (4→14) not taken.
✓ Branch 6 (11→6) taken 342032 times.
✗ Branch 7 (11→12) not taken.
342032 for (auto &val : symbols | std::views::values) {
210
2/2
✓ Branch 0 (7→8) taken 30157 times.
✓ Branch 1 (7→9) taken 311875 times.
342032 if (val.orderIndex == orderIndex)
211 30157 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 5418 SymbolTableEntry *SymbolTable::lookupAnonymous(const CodeLoc &codeLoc, size_t numericSuffix) {
224
2/4
✓ Branch 0 (2→3) taken 5418 times.
✗ Branch 1 (2→19) not taken.
✓ Branch 2 (3→4) taken 5418 times.
✗ Branch 3 (3→17) not taken.
5418 std::string name = "anon." + codeLoc.toString();
225
2/2
✓ Branch 0 (5→6) taken 76 times.
✓ Branch 1 (5→12) taken 5342 times.
5418 if (numericSuffix > 0)
226
3/6
✓ Branch 0 (6→7) taken 76 times.
✗ Branch 1 (6→24) not taken.
✓ Branch 2 (7→8) taken 76 times.
✗ Branch 3 (7→22) not taken.
✓ Branch 4 (8→9) taken 76 times.
✗ Branch 5 (8→20) not taken.
76 name += "." + std::to_string(numericSuffix);
227
1/2
✓ Branch 0 (12→13) taken 5418 times.
✗ Branch 1 (12→26) not taken.
10836 return lookup(name);
228 5418 }
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 194645 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 0 (3→4) taken 36 times.
✓ Branch 1 (3→5) taken 194609 times.
194645 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 0 (5→6) taken 53457 times.
✓ Branch 1 (5→7) taken 141152 times.
194609 if (parent == nullptr)
243 53457 return nullptr;
244
245 141152 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 194645 Capture *SymbolTable::lookupCaptureStrict(const std::string &name) {
255 // If available in the current scope, return it
256
2/2
✓ Branch 0 (3→4) taken 36 times.
✓ Branch 1 (3→6) taken 194609 times.
194645 if (captures.contains(name))
257 36 return &captures.at(name);
258 // Otherwise, return nullptr
259 194609 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 1717 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 59585 nlohmann::json SymbolTable::toJSON() const {
291 // Collect all symbols
292 59585 std::vector<nlohmann::json> jsonSymbols;
293
1/2
✓ Branch 0 (3→4) taken 59585 times.
✗ Branch 1 (3→59) not taken.
59585 jsonSymbols.reserve(symbols.size());
294
5/8
✓ Branch 0 (4→5) taken 59585 times.
✗ Branch 1 (4→44) not taken.
✓ Branch 2 (5→6) taken 59585 times.
✗ Branch 3 (5→44) not taken.
✓ Branch 4 (6→7) taken 59585 times.
✗ Branch 5 (6→44) not taken.
✓ Branch 6 (14→8) taken 132375 times.
✓ Branch 7 (14→15) taken 59585 times.
191960 for (const auto &symbol : symbols | std::views::values)
295
2/4
✓ Branch 0 (9→10) taken 132375 times.
✗ Branch 1 (9→43) not taken.
✓ Branch 2 (10→11) taken 132375 times.
✗ Branch 3 (10→41) not taken.
132375 jsonSymbols.emplace_back(symbol.toJSON());
296
297 // Collect all captures
298 59585 std::vector<nlohmann::json> jsonCaptures;
299
1/2
✓ Branch 0 (16→17) taken 59585 times.
✗ Branch 1 (16→57) not taken.
59585 jsonCaptures.reserve(captures.size());
300
5/8
✓ Branch 0 (17→18) taken 59585 times.
✗ Branch 1 (17→48) not taken.
✓ Branch 2 (18→19) taken 59585 times.
✗ Branch 3 (18→48) not taken.
✓ Branch 4 (19→20) taken 59585 times.
✗ Branch 5 (19→48) not taken.
✓ Branch 6 (27→21) taken 26 times.
✓ Branch 7 (27→28) taken 59585 times.
59611 for (const auto &capture : captures | std::views::values)
301
2/4
✓ Branch 0 (22→23) taken 26 times.
✗ Branch 1 (22→47) not taken.
✓ Branch 2 (23→24) taken 26 times.
✗ Branch 3 (23→45) not taken.
26 jsonCaptures.emplace_back(capture.toJSON());
302
303 // Generate json
304 59585 nlohmann::json result;
305
2/4
✓ Branch 0 (29→30) taken 59585 times.
✗ Branch 1 (29→51) not taken.
✓ Branch 2 (30→31) taken 59585 times.
✗ Branch 3 (30→49) not taken.
59585 result["symbols"] = jsonSymbols;
306
2/4
✓ Branch 0 (33→34) taken 59585 times.
✗ Branch 1 (33→54) not taken.
✓ Branch 2 (34→35) taken 59585 times.
✗ Branch 3 (34→52) not taken.
59585 result["captures"] = jsonCaptures;
307 59585 return result;
308 59585 }
309
310 } // namespace spice::compiler
311