GCC Code Coverage Report


Directory: ../
File: src/symboltablebuilder/SymbolTable.cpp
Date: 2025-02-05 01:09:36
Exec Total Coverage
Lines: 109 111 98.2%
Functions: 14 14 100.0%
Branches: 125 192 65.1%

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 46437 SymbolTableEntry *SymbolTable::insert(const std::string &name, ASTNode *declNode, bool isAnonymousSymbol) {
22 46437 const bool isGlobal = parent == nullptr;
23 46437 size_t orderIndex = SIZE_MAX;
24
2/2
✓ Branch 0 (2→3) taken 43240 times.
✓ Branch 1 (2→5) taken 3197 times.
46437 if (!isAnonymousSymbol)
25 283638 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 46437 times.
46437 assert(!symbols.contains(name));
28
5/10
✓ Branch 0 (8→9) taken 46437 times.
✗ Branch 1 (8→43) not taken.
✓ Branch 2 (9→10) taken 46437 times.
✗ Branch 3 (9→42) not taken.
✓ Branch 4 (10→11) taken 46437 times.
✗ Branch 5 (10→40) not taken.
✓ Branch 6 (11→12) taken 46437 times.
✗ Branch 7 (11→38) not taken.
✓ Branch 8 (12→13) taken 46437 times.
✗ Branch 9 (12→36) not taken.
46437 symbols.insert({name, SymbolTableEntry(name, QualType(TY_INVALID), scope, declNode, orderIndex, isGlobal)});
29 // Set entry to declared
30 46437 SymbolTableEntry *entry = &symbols.at(name);
31
1/2
✓ Branch 0 (17→18) taken 46437 times.
✗ Branch 1 (17→46) not taken.
46437 entry->updateState(DECLARED, declNode);
32
33 // Check if shadowed
34
8/8
✓ Branch 0 (18→19) taken 38568 times.
✓ Branch 1 (18→24) taken 7869 times.
✓ Branch 2 (20→21) taken 777 times.
✓ Branch 3 (20→24) taken 37791 times.
✓ Branch 4 (22→23) taken 30 times.
✓ Branch 5 (22→24) taken 747 times.
✓ Branch 6 (25→26) taken 30 times.
✓ Branch 7 (25→34) taken 46407 times.
46437 if (parent != nullptr && parent->lookup(name) != nullptr && !declNode->isParam()) {
35
2/4
✓ Branch 0 (26→27) taken 30 times.
✗ Branch 1 (26→49) not taken.
✓ Branch 2 (27→28) taken 30 times.
✗ Branch 3 (27→47) not taken.
30 const std::string warningMsg = "Variable '" + name + "' shadows a variable in a parent scope";
36
1/2
✓ Branch 0 (29→30) taken 30 times.
✗ Branch 1 (29→52) not taken.
30 const CompilerWarning warning(declNode->codeLoc, SHADOWED_VARIABLE, warningMsg);
37
1/2
✓ Branch 0 (30→31) taken 30 times.
✗ Branch 1 (30→50) not taken.
30 scope->sourceFile->compilerOutput.warnings.push_back(warning);
38 30 }
39
40 46437 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 3218 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 3218 times.
✗ Branch 1 (2→38) not taken.
✓ Branch 2 (3→4) taken 21 times.
✓ Branch 3 (3→5) taken 3197 times.
3218 if (SymbolTableEntry *anonSymbol = lookupAnonymous(declNode->codeLoc, numericSuffix))
55 21 return anonSymbol;
56 // Otherwise, create an anonymous entry
57
1/2
✓ Branch 0 (5→6) taken 3197 times.
✗ Branch 1 (5→38) not taken.
3197 std::stringstream name;
58
3/6
✓ Branch 0 (6→7) taken 3197 times.
✗ Branch 1 (6→36) not taken.
✓ Branch 2 (7→8) taken 3197 times.
✗ Branch 3 (7→27) not taken.
✓ Branch 4 (8→9) taken 3197 times.
✗ Branch 5 (8→25) not taken.
3197 name << "anon." << declNode->codeLoc.toString();
59
2/2
✓ Branch 0 (10→11) taken 42 times.
✓ Branch 1 (10→16) taken 3155 times.
3197 if (numericSuffix > 0)
60
3/6
✓ Branch 0 (11→12) taken 42 times.
✗ Branch 1 (11→36) not taken.
✓ Branch 2 (12→13) taken 42 times.
✗ Branch 3 (12→30) not taken.
✓ Branch 4 (13→14) taken 42 times.
✗ Branch 5 (13→28) not taken.
42 name << "." << std::to_string(numericSuffix);
61
2/4
✓ Branch 0 (16→17) taken 3197 times.
✗ Branch 1 (16→33) not taken.
✓ Branch 2 (17→18) taken 3197 times.
✗ Branch 3 (17→31) not taken.
3197 SymbolTableEntry *anonSymbol = insert(name.str(), declNode, true);
62
1/2
✓ Branch 0 (19→20) taken 3197 times.
✗ Branch 1 (19→36) not taken.
3197 anonSymbol->updateType(qualType, false);
63
1/2
✓ Branch 0 (20→21) taken 3197 times.
✗ Branch 1 (20→34) not taken.
3197 anonSymbol->updateState(DECLARED, declNode);
64
1/2
✓ Branch 0 (21→22) taken 3197 times.
✗ Branch 1 (21→35) not taken.
3197 anonSymbol->updateState(INITIALIZED, declNode);
65 3197 anonSymbol->anonymous = true;
66 3197 anonSymbol->used = true;
67 3197 return anonSymbol;
68 3197 }
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 2155 SymbolTableEntry *SymbolTable::copySymbol(const std::string &originalName, const std::string &newName) {
78
1/2
✓ Branch 0 (2→3) taken 2155 times.
✗ Branch 1 (2→19) not taken.
2155 SymbolTableEntry *entryToCopy = lookupStrict(originalName);
79
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 2155 times.
2155 assert(entryToCopy != nullptr);
80
2/4
✓ Branch 0 (5→6) taken 2155 times.
✗ Branch 1 (5→18) not taken.
✓ Branch 2 (6→7) taken 2155 times.
✗ Branch 3 (6→16) not taken.
2155 auto [it, success] = symbols.insert({newName, *entryToCopy});
81
1/2
✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→12) taken 2155 times.
2155 assert(success);
82 4310 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 222798 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 222798 SymbolTableEntry *entry = lookupStrict(name);
94
2/2
✓ Branch 0 (3→4) taken 152958 times.
✓ Branch 1 (3→36) taken 69840 times.
222798 if (!entry) { // Symbol was not found in the current scope
95 // We reached the root scope, the symbol does not exist at all
96
2/2
✓ Branch 0 (4→5) taken 53171 times.
✓ Branch 1 (4→6) taken 99787 times.
152958 if (parent == nullptr)
97 53171 return nullptr;
98 // If we search for the result variable, we want to stop the search when exiting a lambda body
99
6/6
✓ Branch 0 (7→8) taken 5959 times.
✓ Branch 1 (7→10) taken 93828 times.
✓ Branch 2 (8→9) taken 1 times.
✓ Branch 3 (8→10) taken 5958 times.
✓ Branch 4 (11→12) taken 1 times.
✓ Branch 5 (11→13) taken 99786 times.
99787 if (name == RETURN_VARIABLE_NAME && scope->type == ScopeType::LAMBDA_BODY)
100 1 return nullptr;
101 // If there is a parent scope, continue the search there
102 99786 entry = parent->lookup(name);
103 // Symbol was also not found in all the parent scopes, return nullptr
104
2/2
✓ Branch 0 (14→15) taken 49375 times.
✓ Branch 1 (14→16) taken 50411 times.
99786 if (!entry)
105 49375 return nullptr;
106
107 // Check if this scope requires capturing and capture the variable if appropriate
108
9/14
✓ Branch 0 (16→17) taken 24 times.
✓ Branch 1 (16→23) taken 50387 times.
✓ Branch 2 (17→18) taken 24 times.
✗ Branch 3 (17→38) not taken.
✓ Branch 4 (18→19) taken 24 times.
✗ Branch 5 (18→23) not taken.
✓ Branch 6 (19→20) taken 24 times.
✗ Branch 7 (19→38) not taken.
✓ Branch 8 (20→21) taken 24 times.
✗ Branch 9 (20→38) not taken.
✓ Branch 10 (21→22) taken 24 times.
✗ Branch 11 (21→23) not taken.
✓ Branch 12 (24→25) taken 24 times.
✓ Branch 13 (24→36) taken 50387 times.
50411 if (capturingRequired && !captures.contains(name) && !entry->getQualType().isOneOf({TY_IMPORT, TY_FUNCTION, TY_PROCEDURE})) {
109 // 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
110
3/4
✓ Branch 0 (26→27) taken 8 times.
✓ Branch 1 (26→30) taken 16 times.
✓ Branch 2 (28→29) taken 8 times.
✗ Branch 3 (28→30) not taken.
24 entry->isVolatile = scope->isInAsyncScope() && !entry->scope->isInAsyncScope();
111 // Add the capture to the current scope
112
3/6
✓ Branch 0 (31→32) taken 24 times.
✗ Branch 1 (31→41) not taken.
✓ Branch 2 (32→33) taken 24 times.
✗ Branch 3 (32→41) not taken.
✓ Branch 4 (33→34) taken 24 times.
✗ Branch 5 (33→39) not taken.
24 captures.insert({name, Capture(entry)});
113 }
114 }
115 120251 return entry;
116 }
117
118 /**
119 * Check if a symbol exists in the current scope and return it if possible
120 *
121 * @param name Name of the desired symbol
122 * @return Desired symbol / nullptr if the symbol was not found
123 */
124 372752 SymbolTableEntry *SymbolTable::lookupStrict(const std::string &name) {
125
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 372752 times.
372752 if (name.empty())
126 return nullptr;
127 // Check if a symbol with this name exists in this scope
128
2/2
✓ Branch 0 (6→7) taken 186920 times.
✓ Branch 1 (6→9) taken 185832 times.
372752 if (symbols.contains(name))
129 186920 return &symbols.at(name);
130 // Check if a capture with this name exists in this scope
131
2/2
✓ Branch 0 (10→11) taken 17 times.
✓ Branch 1 (10→13) taken 185815 times.
185832 if (captures.contains(name))
132 17 return captures.at(name).capturedSymbol;
133 // Otherwise, return a nullptr
134 185815 return nullptr;
135 }
136
137 /**
138 * Check if a symbol exists in one of the composed field scopes of the current scope and return it if possible.
139 * This only works if the current scope is a struct scope.
140 *
141 * @param name Name of the desired symbol
142 * @param indexPath How to index the found symbol using order indices (e.g. for GEP)
143 * @return Desired symbol / nullptr if the symbol was not found
144 */
145 27441 SymbolTableEntry *SymbolTable::lookupInComposedFields(const std::string &name, // NOLINT(misc-no-recursion)
146 std::vector<size_t> &indexPath) {
147
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 27441 times.
27441 assert(scope->type == ScopeType::STRUCT);
148
149 // Check if we have a symbol with this name in the current scope
150
2/2
✓ Branch 0 (5→6) taken 27412 times.
✓ Branch 1 (5→8) taken 29 times.
27441 if (SymbolTableEntry *result = lookupStrict(name)) {
151 27412 indexPath.push_back(result->orderIndex);
152 27412 return result;
153 }
154
155 // If it was not found in the current scope, loop through all composed fields in this scope
156
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++) {
157 41 const SymbolTableEntry *fieldEntry = lookupStrictByIndex(i);
158
159 // Skip all fields that are not composition fields
160
2/2
✓ Branch 0 (12→13) taken 14 times.
✓ Branch 1 (12→14) taken 27 times.
41 if (!fieldEntry->getQualType().isComposition())
161 14 continue;
162
163 // Add the current field's order index to the index path
164 27 indexPath.push_back(fieldEntry->orderIndex);
165
166 // Search in the composed field's body scope
167 27 Scope *searchScope = fieldEntry->getQualType().getBodyScope();
168
1/2
✗ Branch 0 (17→18) not taken.
✓ Branch 1 (17→19) taken 27 times.
27 assert(searchScope != nullptr);
169
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))
170 16 return result;
171
172 // Remove the current field's order index from the index path
173 11 indexPath.pop_back();
174 }
175
176 // Symbol was not found in current scope, return nullptr
177 13 return nullptr;
178 }
179
180 /**
181 * Check if an order index exists in the current or any parent scope and returns it if possible.
182 * Warning: Unlike the `lookup` method, this one doesn't consider the parent scopes
183 *
184 * @param orderIndex Order index of the desired symbol
185 * @return Desired symbol / nullptr if the symbol was not found
186 */
187 30871 SymbolTableEntry *SymbolTable::lookupStrictByIndex(unsigned int orderIndex) {
188
4/8
✓ Branch 0 (2→3) taken 30871 times.
✗ Branch 1 (2→14) not taken.
✓ Branch 2 (3→4) taken 30871 times.
✗ Branch 3 (3→14) not taken.
✓ Branch 4 (4→5) taken 30871 times.
✗ Branch 5 (4→14) not taken.
✓ Branch 6 (11→6) taken 336485 times.
✗ Branch 7 (11→12) not taken.
336485 for (auto &val : symbols | std::views::values) {
189
2/2
✓ Branch 0 (7→8) taken 30871 times.
✓ Branch 1 (7→9) taken 305614 times.
336485 if (val.orderIndex == orderIndex)
190 30871 return &val;
191 }
192 return nullptr;
193 }
194
195 /**
196 * Check if an anonymous symbol exists in the current scope and return it if possible
197 *
198 * @param codeLoc Definition code loc
199 * @param numericSuffix Numeric suffix of the anonymous symbol
200 * @return Anonymous symbol
201 */
202 6514 SymbolTableEntry *SymbolTable::lookupAnonymous(const CodeLoc &codeLoc, size_t numericSuffix) {
203
2/4
✓ Branch 0 (2→3) taken 6514 times.
✗ Branch 1 (2→19) not taken.
✓ Branch 2 (3→4) taken 6514 times.
✗ Branch 3 (3→17) not taken.
6514 std::string name = "anon." + codeLoc.toString();
204
2/2
✓ Branch 0 (5→6) taken 84 times.
✓ Branch 1 (5→12) taken 6430 times.
6514 if (numericSuffix > 0)
205
3/6
✓ Branch 0 (6→7) taken 84 times.
✗ Branch 1 (6→24) not taken.
✓ Branch 2 (7→8) taken 84 times.
✗ Branch 3 (7→22) not taken.
✓ Branch 4 (8→9) taken 84 times.
✗ Branch 5 (8→20) not taken.
84 name += "." + std::to_string(numericSuffix);
206
1/2
✓ Branch 0 (12→13) taken 6514 times.
✗ Branch 1 (12→26) not taken.
13028 return lookup(name);
207 6514 }
208
209 /**
210 * Check if a capture exists in the current or any parent scope and return it if possible
211 *
212 * @param name Name of the desired captured symbol
213 * @return Capture / nullptr if the capture was not found
214 */
215 189365 Capture *SymbolTable::lookupCapture(const std::string &name) { // NOLINT(misc-no-recursion)
216 // Check if the capture exists in the current scope. If yes, take it
217
2/2
✓ Branch 0 (3→4) taken 36 times.
✓ Branch 1 (3→5) taken 189329 times.
189365 if (Capture *capture = lookupCaptureStrict(name))
218 36 return capture;
219
220 // We reached the root scope, the symbol does not exist at all
221
2/2
✓ Branch 0 (5→6) taken 51995 times.
✓ Branch 1 (5→7) taken 137334 times.
189329 if (parent == nullptr)
222 51995 return nullptr;
223
224 137334 return parent->lookupCapture(name);
225 }
226
227 /**
228 * Check if a capture exists in the current scope and return it if possible
229 *
230 * @param name Name of the desired captured symbol
231 * @return Capture / nullptr if the capture was not found
232 */
233 189365 Capture *SymbolTable::lookupCaptureStrict(const std::string &name) {
234 // If available in the current scope, return it
235
2/2
✓ Branch 0 (3→4) taken 36 times.
✓ Branch 1 (3→6) taken 189329 times.
189365 if (captures.contains(name))
236 36 return &captures.at(name);
237 // Otherwise, return nullptr
238 189329 return nullptr;
239 }
240
241 /**
242 * Set capturing for this scope required.
243 */
244 40 void SymbolTable::setCapturingRequired() { capturingRequired = true; }
245
246 /**
247 * Deletes an existing anonymous symbol
248 *
249 * @param name Anonymous symbol name
250 */
251 2249 void SymbolTable::deleteAnonymous(const std::string &name) { symbols.erase(name); }
252
253 /**
254 * Stringify a symbol table to a human-readable form. This is used to realize dumps of symbol tables
255 *
256 * Example:
257 * {
258 * "name": "<SymbolTableName>"
259 * "symbols": [
260 * ... (SymbolTableEntry)
261 * ],
262 * "children": [
263 * ... (SymbolTable)
264 * ]
265 * }
266 *
267 * @return Symbol table if form of a string
268 */
269 53944 nlohmann::json SymbolTable::toJSON() const {
270 // Collect all symbols
271 53944 std::vector<nlohmann::json> jsonSymbols;
272
1/2
✓ Branch 0 (3→4) taken 53944 times.
✗ Branch 1 (3→59) not taken.
53944 jsonSymbols.reserve(symbols.size());
273
5/8
✓ Branch 0 (4→5) taken 53944 times.
✗ Branch 1 (4→44) not taken.
✓ Branch 2 (5→6) taken 53944 times.
✗ Branch 3 (5→44) not taken.
✓ Branch 4 (6→7) taken 53944 times.
✗ Branch 5 (6→44) not taken.
✓ Branch 6 (14→8) taken 121961 times.
✓ Branch 7 (14→15) taken 53944 times.
175905 for (const auto &symbol : symbols | std::views::values)
274
2/4
✓ Branch 0 (9→10) taken 121961 times.
✗ Branch 1 (9→43) not taken.
✓ Branch 2 (10→11) taken 121961 times.
✗ Branch 3 (10→41) not taken.
121961 jsonSymbols.emplace_back(symbol.toJSON());
275
276 // Collect all captures
277 53944 std::vector<nlohmann::json> jsonCaptures;
278
1/2
✓ Branch 0 (16→17) taken 53944 times.
✗ Branch 1 (16→57) not taken.
53944 jsonCaptures.reserve(captures.size());
279
5/8
✓ Branch 0 (17→18) taken 53944 times.
✗ Branch 1 (17→48) not taken.
✓ Branch 2 (18→19) taken 53944 times.
✗ Branch 3 (18→48) not taken.
✓ Branch 4 (19→20) taken 53944 times.
✗ Branch 5 (19→48) not taken.
✓ Branch 6 (27→21) taken 26 times.
✓ Branch 7 (27→28) taken 53944 times.
53970 for (const auto &capture : captures | std::views::values)
280
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());
281
282 // Generate json
283 53944 nlohmann::json result;
284
2/4
✓ Branch 0 (29→30) taken 53944 times.
✗ Branch 1 (29→51) not taken.
✓ Branch 2 (30→31) taken 53944 times.
✗ Branch 3 (30→49) not taken.
53944 result["symbols"] = jsonSymbols;
285
2/4
✓ Branch 0 (33→34) taken 53944 times.
✗ Branch 1 (33→54) not taken.
✓ Branch 2 (34→35) taken 53944 times.
✗ Branch 3 (34→52) not taken.
53944 result["captures"] = jsonCaptures;
286 53944 return result;
287 53944 }
288
289 } // namespace spice::compiler
290