GCC Code Coverage Report


Directory: ../
File: src/typechecker/FunctionManager.cpp
Date: 2025-11-03 22:22:45
Coverage Exec Excl Total
Lines: 97.6% 248 0 254
Functions: 100.0% 18 0 18
Branches: 64.2% 294 0 458

Line Branch Exec Source
1 // Copyright (c) 2021-2025 ChilliBits. All rights reserved.
2
3 #include "FunctionManager.h"
4
5 #include <ast/ASTNodes.h>
6 #include <exception/SemanticError.h>
7 #include <model/GenericType.h>
8 #include <symboltablebuilder/Scope.h>
9 #include <symboltablebuilder/SymbolTableBuilder.h>
10 #include <typechecker/TypeChecker.h>
11 #include <typechecker/TypeMatcher.h>
12 #include <util/CodeLoc.h>
13 #include <util/CustomHashFunctions.h>
14
15 namespace spice::compiler {
16
17 // Static member initialization
18 std::unordered_map<uint64_t, Function *> FunctionManager::lookupCache = {};
19 size_t FunctionManager::lookupCacheHits = 0;
20 size_t FunctionManager::lookupCacheMisses = 0;
21
22 12919 Function *FunctionManager::insert(Scope *insertScope, const Function &baseFunction, std::vector<Function *> *nodeFunctionList) {
23 // Open a new manifestation list for the function definition
24
3/6
✓ Branch 2 → 3 taken 12919 times.
✗ Branch 2 → 43 not taken.
✓ Branch 3 → 4 taken 12919 times.
✗ Branch 3 → 40 not taken.
✓ Branch 4 → 5 taken 12919 times.
✗ Branch 4 → 38 not taken.
12919 const std::string fctId = baseFunction.name + ":" + baseFunction.declNode->codeLoc.toPrettyLineAndColumn();
25
2/4
✓ Branch 8 → 9 taken 12919 times.
✗ Branch 8 → 46 not taken.
✓ Branch 9 → 10 taken 12919 times.
✗ Branch 9 → 44 not taken.
12919 insertScope->functions.insert({fctId, FunctionManifestationList()});
26
27 // Collect substantiations
28 12919 std::vector<Function> manifestations;
29
1/2
✓ Branch 12 → 13 taken 12919 times.
✗ Branch 12 → 51 not taken.
12919 substantiateOptionalParams(baseFunction, manifestations);
30
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 12919 times.
12919 assert(!manifestations.empty());
31
32 // Save substantiations in declaration node
33 12919 Function *manifestationPtr = nullptr;
34
2/2
✓ Branch 26 → 18 taken 13789 times.
✓ Branch 26 → 27 taken 12917 times.
26706 for (const Function &manifestation : manifestations) {
35
2/2
✓ Branch 19 → 20 taken 13787 times.
✓ Branch 19 → 50 taken 2 times.
13789 manifestationPtr = insertSubstantiation(insertScope, manifestation, baseFunction.declNode);
36
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 13787 times.
13787 assert(manifestationPtr != nullptr);
37
1/2
✓ Branch 22 → 23 taken 13787 times.
✗ Branch 22 → 24 not taken.
13787 if (nodeFunctionList)
38
1/2
✓ Branch 23 → 24 taken 13787 times.
✗ Branch 23 → 50 not taken.
13787 nodeFunctionList->push_back(manifestationPtr);
39 }
40
41
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 12917 times.
12917 if (!nodeFunctionList)
42 return manifestationPtr;
43
44
1/2
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 12917 times.
12917 assert(!nodeFunctionList->empty());
45 12917 return nodeFunctionList->front();
46 12921 }
47
48 /**
49 * Create definite functions from ambiguous ones, in regard to optional arguments.
50 *
51 * Example:
52 * int testFunc(string, int?, double?)
53 * gets
54 * int testFunc(string)
55 * int testFunc(string, int)
56 * int testFunc(string, int, double)
57 *
58 * This method also accepts functions, that are already definite, but does nothing to them.
59 *
60 * @param baseFunction Ambiguous base function
61 * @param manifestations Vector to store the definite manifestations
62 * @return True, if there were optional arguments found
63 */
64 12919 void FunctionManager::substantiateOptionalParams(const Function &baseFunction, std::vector<Function> &manifestations) {
65 // Handle the case of no parameters -> simply return the base function
66
2/2
✓ Branch 3 → 4 taken 3113 times.
✓ Branch 3 → 6 taken 9806 times.
12919 if (baseFunction.paramList.empty()) {
67
1/2
✓ Branch 4 → 5 taken 3113 times.
✗ Branch 4 → 39 not taken.
3113 manifestations.push_back(baseFunction);
68 3113 return;
69 }
70
71 9806 ParamList currentFunctionParamTypes;
72
1/2
✓ Branch 7 → 8 taken 9806 times.
✗ Branch 7 → 37 not taken.
9806 currentFunctionParamTypes.reserve(baseFunction.paramList.size());
73 9806 bool metFirstOptionalParam = false;
74
1/2
✓ Branch 8 → 9 taken 9806 times.
✗ Branch 8 → 37 not taken.
9806 Function manifestation = baseFunction;
75
76 // Loop over all parameters
77
2/2
✓ Branch 24 → 11 taken 15163 times.
✓ Branch 24 → 25 taken 9806 times.
24969 for (const auto &[qualType, isOptional] : baseFunction.paramList) {
78 // Check if we have a mandatory parameter
79
2/2
✓ Branch 12 → 13 taken 14293 times.
✓ Branch 12 → 15 taken 870 times.
15163 if (!isOptional) {
80
1/2
✓ Branch 13 → 14 taken 14293 times.
✗ Branch 13 → 32 not taken.
14293 currentFunctionParamTypes.push_back({qualType, /*optional=*/false});
81 14293 continue;
82 }
83
84 // Add substantiation without the optional parameter
85
2/2
✓ Branch 15 → 16 taken 864 times.
✓ Branch 15 → 19 taken 6 times.
870 if (!metFirstOptionalParam) {
86
1/2
✓ Branch 16 → 17 taken 864 times.
✗ Branch 16 → 34 not taken.
864 manifestation.paramList = currentFunctionParamTypes;
87
1/2
✓ Branch 17 → 18 taken 864 times.
✗ Branch 17 → 34 not taken.
864 manifestations.push_back(manifestation);
88 // Now we cannot accept mandatory parameters anymore
89 864 metFirstOptionalParam = true;
90 }
91
92 // Add substantiation with the optional parameter
93
1/2
✓ Branch 19 → 20 taken 870 times.
✗ Branch 19 → 33 not taken.
870 currentFunctionParamTypes.push_back({qualType, /*optional=*/false});
94
1/2
✓ Branch 20 → 21 taken 870 times.
✗ Branch 20 → 34 not taken.
870 manifestation.paramList = currentFunctionParamTypes;
95
1/2
✓ Branch 21 → 22 taken 870 times.
✗ Branch 21 → 34 not taken.
870 manifestations.push_back(manifestation);
96 }
97
98 // Ensure at least once manifestation
99
2/2
✓ Branch 26 → 27 taken 8942 times.
✓ Branch 26 → 28 taken 864 times.
9806 if (manifestations.empty())
100
1/2
✓ Branch 27 → 28 taken 8942 times.
✗ Branch 27 → 35 not taken.
8942 manifestations.push_back(baseFunction);
101 9806 }
102
103 3 Function FunctionManager::createMainFunction(SymbolTableEntry *entry, const QualTypeList &paramTypes, ASTNode *declNode) {
104 3 ParamList paramList;
105
2/2
✓ Branch 8 → 4 taken 2 times.
✓ Branch 8 → 9 taken 3 times.
5 for (const QualType &paramType : paramTypes)
106
1/2
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 24 not taken.
2 paramList.push_back({paramType, false});
107
4/8
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 31 not taken.
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 28 not taken.
✓ Branch 13 → 14 taken 3 times.
✗ Branch 13 → 27 not taken.
✓ Branch 14 → 15 taken 3 times.
✗ Branch 14 → 26 not taken.
6 return {MAIN_FUNCTION_NAME, entry, QualType(TY_DYN), QualType(TY_INT), paramList, {}, declNode};
108 3 }
109
110 16002 Function *FunctionManager::insertSubstantiation(Scope *insertScope, const Function &newManifestation, const ASTNode *declNode) {
111
2/4
✓ Branch 2 → 3 taken 16002 times.
✗ Branch 2 → 65 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 16002 times.
16002 assert(newManifestation.hasSubstantiatedParams());
112
113
1/2
✓ Branch 5 → 6 taken 16002 times.
✗ Branch 5 → 65 not taken.
16002 const std::string signature = newManifestation.getSignature(true, false);
114
115 // Check if the function exists already
116
5/8
✓ Branch 6 → 7 taken 16002 times.
✗ Branch 6 → 54 not taken.
✓ Branch 7 → 8 taken 16002 times.
✗ Branch 7 → 54 not taken.
✓ Branch 8 → 9 taken 16002 times.
✗ Branch 8 → 54 not taken.
✓ Branch 28 → 10 taken 222043 times.
✓ Branch 28 → 29 taken 15999 times.
238042 for (const auto &manifestations : insertScope->functions | std::views::values) {
117
3/4
✓ Branch 11 → 12 taken 222043 times.
✗ Branch 11 → 54 not taken.
✓ Branch 12 → 13 taken 3 times.
✓ Branch 12 → 26 taken 222040 times.
222043 if (manifestations.contains(signature)) {
118
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 2 times.
3 const SemanticErrorType errorType = newManifestation.isFunction() ? FUNCTION_DECLARED_TWICE : PROCEDURE_DECLARED_TWICE;
119
3/6
✓ Branch 20 → 21 taken 3 times.
✗ Branch 20 → 49 not taken.
✓ Branch 21 → 22 taken 3 times.
✗ Branch 21 → 47 not taken.
✓ Branch 22 → 23 taken 3 times.
✗ Branch 22 → 45 not taken.
3 throw SemanticError(declNode, errorType, "'" + signature + "' is declared twice");
120 }
121 }
122
123 // Retrieve the matching manifestation list of the scope
124
3/6
✓ Branch 29 → 30 taken 15999 times.
✗ Branch 29 → 60 not taken.
✓ Branch 30 → 31 taken 15999 times.
✗ Branch 30 → 57 not taken.
✓ Branch 31 → 32 taken 15999 times.
✗ Branch 31 → 55 not taken.
15999 const std::string fctId = newManifestation.name + ":" + declNode->codeLoc.toPrettyLineAndColumn();
125
2/4
✓ Branch 34 → 35 taken 15999 times.
✗ Branch 34 → 61 not taken.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 15999 times.
15999 assert(insertScope->functions.contains(fctId));
126
1/2
✓ Branch 37 → 38 taken 15999 times.
✗ Branch 37 → 61 not taken.
15999 FunctionManifestationList &manifestationList = insertScope->functions.at(fctId);
127
128 // Add substantiated function
129
1/2
✓ Branch 38 → 39 taken 15999 times.
✗ Branch 38 → 61 not taken.
15999 manifestationList.emplace(signature, newManifestation);
130
1/2
✓ Branch 39 → 40 taken 15999 times.
✗ Branch 39 → 61 not taken.
31998 return &manifestationList.at(signature);
131 16002 }
132
133 /**
134 * Checks if a function exists by matching it, but not setting it to used
135 *
136 * @param matchScope Scope to match against
137 * @param reqName Function name requirement
138 * @param reqThisType This type requirement
139 * @param reqArgs Argument requirement
140 * @param strictQualifierMatching Match argument and this type qualifiers strictly
141 * @return Found function or nullptr
142 */
143 11954 const Function *FunctionManager::lookup(Scope *matchScope, const std::string &reqName, const QualType &reqThisType,
144 const ArgList &reqArgs, bool strictQualifierMatching) {
145
2/4
✓ Branch 2 → 3 taken 11954 times.
✗ Branch 2 → 66 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 11954 times.
11954 assert(reqThisType.isOneOf({TY_DYN, TY_STRUCT}));
146
147 // Do cache lookup
148
1/2
✓ Branch 6 → 7 taken 11954 times.
✗ Branch 6 → 67 not taken.
11954 const uint64_t cacheKey = getCacheKey(matchScope, reqName, reqThisType, reqArgs, {});
149
3/4
✓ Branch 8 → 9 taken 11954 times.
✗ Branch 8 → 83 not taken.
✓ Branch 9 → 10 taken 3316 times.
✓ Branch 9 → 12 taken 8638 times.
11954 if (lookupCache.contains(cacheKey)) {
150 3316 lookupCacheHits++;
151
1/2
✓ Branch 10 → 11 taken 3316 times.
✗ Branch 10 → 83 not taken.
3316 return lookupCache.at(cacheKey);
152 }
153 8638 lookupCacheMisses++;
154
155 2554 const auto pred = [&](const Arg &arg) { return arg.first.hasAnyGenericParts(); };
156
5/8
✓ Branch 12 → 13 taken 8638 times.
✗ Branch 12 → 83 not taken.
✓ Branch 13 → 14 taken 8398 times.
✓ Branch 13 → 17 taken 240 times.
✓ Branch 14 → 15 taken 8398 times.
✗ Branch 14 → 83 not taken.
✓ Branch 15 → 16 taken 8398 times.
✗ Branch 15 → 17 not taken.
8638 const bool requestedFullySubstantiated = !reqThisType.hasAnyGenericParts() && std::ranges::none_of(reqArgs, pred);
157
158 // Copy the registry to prevent iterating over items, that are created within the loop
159
1/2
✓ Branch 18 → 19 taken 8638 times.
✗ Branch 18 → 83 not taken.
8638 const FunctionRegistry functionRegistry = matchScope->functions;
160 // Loop over function registry to find functions, that match the requirements of the call
161 8638 std::vector<const Function *> matches;
162
2/2
✓ Branch 55 → 21 taken 71143 times.
✓ Branch 55 → 56 taken 8638 times.
79781 for (const auto &[defCodeLocStr, m] : functionRegistry) {
163 // Copy the manifestation list to prevent iterating over items, that are created within the loop
164
1/2
✓ Branch 24 → 25 taken 71143 times.
✗ Branch 24 → 77 not taken.
71143 const FunctionManifestationList manifestations = m;
165
2/2
✓ Branch 51 → 27 taken 80789 times.
✓ Branch 51 → 52 taken 25940 times.
106729 for (const auto &[signature, presetFunction] : manifestations) {
166
2/4
✓ Branch 30 → 31 taken 80789 times.
✗ Branch 30 → 73 not taken.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 80789 times.
80789 assert(presetFunction.hasSubstantiatedParams()); // No optional params are allowed at this point
167
168 // - search for concrete fct: Only match against fully substantiated versions to prevent double matching of a function
169 // - search for generic fct: Only match against generic preset functions
170
3/4
✓ Branch 33 → 34 taken 80789 times.
✗ Branch 33 → 73 not taken.
✓ Branch 34 → 35 taken 29595 times.
✓ Branch 34 → 36 taken 51194 times.
80789 if (presetFunction.isFullySubstantiated() != requestedFullySubstantiated)
171 35586 continue;
172
173 // Copy the function to be able to substantiate types
174
1/2
✓ Branch 36 → 37 taken 51194 times.
✗ Branch 36 → 73 not taken.
51194 Function candidate = presetFunction;
175
176 // Create empty type mapping
177 51194 TypeMapping &typeMapping = candidate.typeMapping;
178
179 51194 bool forceSubstantiation = false;
180
1/2
✓ Branch 37 → 38 taken 51194 times.
✗ Branch 37 → 71 not taken.
51194 const MatchResult matchResult = matchManifestation(candidate, matchScope, reqName, reqThisType, reqArgs, typeMapping,
181 strictQualifierMatching, forceSubstantiation, nullptr);
182
2/2
✓ Branch 38 → 39 taken 43918 times.
✓ Branch 38 → 40 taken 7276 times.
51194 if (matchResult == MatchResult::SKIP_FUNCTION)
183 43918 break; // Leave the whole function
184
2/2
✓ Branch 40 → 41 taken 5991 times.
✓ Branch 40 → 42 taken 1285 times.
7276 if (matchResult == MatchResult::SKIP_MANIFESTATION)
185 5991 continue; // Leave this manifestation and try the next one
186
187 // Add to matches
188
3/6
✓ Branch 42 → 43 taken 1285 times.
✗ Branch 42 → 70 not taken.
✓ Branch 43 → 44 taken 1285 times.
✗ Branch 43 → 70 not taken.
✓ Branch 44 → 45 taken 1285 times.
✗ Branch 44 → 70 not taken.
1285 matches.push_back(&matchScope->functions.at(defCodeLocStr).at(signature));
189
190 1285 break; // Leave the whole manifestation list to not double-match the manifestation
191
2/2
✓ Branch 47 → 48 taken 5991 times.
✓ Branch 47 → 49 taken 45203 times.
51194 }
192 71143 }
193
194 // Return the very match or a nullptr
195
2/2
✓ Branch 57 → 58 taken 1285 times.
✓ Branch 57 → 60 taken 7353 times.
8638 return !matches.empty() ? matches.front() : nullptr;
196 8638 }
197
198 /**
199 * Check if there is a function in the scope, fulfilling all given requirements and if found, return it.
200 * If more than one function matches the requirement, an error gets thrown.
201 *
202 * @param matchScope Scope to match against
203 * @param reqName Function name requirement
204 * @param reqThisType This type requirement
205 * @param reqArgs Argument requirement
206 * @param templateTypeHints Template type requirement
207 * @param strictQualifierMatching Match argument and this type qualifiers strictly
208 * @param callNode Call AST node for printing error messages
209 * @return Matched function or nullptr
210 */
211 71812 Function *FunctionManager::match(Scope *matchScope, const std::string &reqName, const QualType &reqThisType,
212 const ArgList &reqArgs, const QualTypeList &templateTypeHints, bool strictQualifierMatching,
213 const ASTNode *callNode) {
214
2/4
✓ Branch 2 → 3 taken 71812 times.
✗ Branch 2 → 152 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 71812 times.
71812 assert(reqThisType.isOneOf({TY_DYN, TY_STRUCT, TY_INTERFACE}));
215
216 // Do cache lookup
217
1/2
✓ Branch 5 → 6 taken 71812 times.
✗ Branch 5 → 187 not taken.
71812 const uint64_t cacheKey = getCacheKey(matchScope, reqName, reqThisType, reqArgs, templateTypeHints);
218
3/4
✓ Branch 6 → 7 taken 71812 times.
✗ Branch 6 → 187 not taken.
✓ Branch 7 → 8 taken 11656 times.
✓ Branch 7 → 10 taken 60156 times.
71812 if (lookupCache.contains(cacheKey)) {
219 11656 lookupCacheHits++;
220
1/2
✓ Branch 8 → 9 taken 11656 times.
✗ Branch 8 → 187 not taken.
11656 return lookupCache.at(cacheKey);
221 }
222 60156 lookupCacheMisses++;
223
224 // Copy the registry to prevent iterating over items, that are created within the loop
225
1/2
✓ Branch 10 → 11 taken 60156 times.
✗ Branch 10 → 187 not taken.
60156 FunctionRegistry functionRegistry = matchScope->functions;
226 // Loop over function registry to find functions, that match the requirements of the call
227 60156 std::vector<Function *> matches;
228
2/2
✓ Branch 137 → 13 taken 732572 times.
✓ Branch 137 → 138 taken 60154 times.
792726 for (const auto &[fctId, m] : functionRegistry) {
229 // Copy the manifestation list to prevent iterating over items, that are created within the loop
230
1/2
✓ Branch 16 → 17 taken 732572 times.
✗ Branch 16 → 181 not taken.
732572 const FunctionManifestationList manifestations = m;
231
2/2
✓ Branch 133 → 19 taken 742144 times.
✓ Branch 133 → 134 taken 44307 times.
786451 for (const auto &[signature, presetFunction] : manifestations) {
232
2/4
✓ Branch 22 → 23 taken 742144 times.
✗ Branch 22 → 177 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 742144 times.
742144 assert(presetFunction.hasSubstantiatedParams()); // No optional params are allowed at this point
233
234 // Skip generic substantiations to prevent double matching of a function
235
3/4
✓ Branch 25 → 26 taken 742144 times.
✗ Branch 25 → 177 not taken.
✓ Branch 26 → 27 taken 7571 times.
✓ Branch 26 → 28 taken 734573 times.
742144 if (presetFunction.isGenericSubstantiation())
236 53879 continue;
237
238 // Copy the function to be able to substantiate types
239
1/2
✓ Branch 28 → 29 taken 734573 times.
✗ Branch 28 → 177 not taken.
734573 Function candidate = presetFunction;
240
241 // Prepare type mapping, based on the given initial type mapping
242 734573 TypeMapping &typeMapping = candidate.typeMapping;
243 734573 typeMapping.clear();
244
2/2
✓ Branch 41 → 31 taken 3613 times.
✓ Branch 41 → 42 taken 734573 times.
738186 for (size_t i = 0; i < std::min(templateTypeHints.size(), candidate.templateTypes.size()); i++) {
245
2/4
✓ Branch 31 → 32 taken 3613 times.
✗ Branch 31 → 175 not taken.
✓ Branch 32 → 33 taken 3613 times.
✗ Branch 32 → 175 not taken.
3613 const std::string &typeName = candidate.templateTypes.at(i).getSubType();
246
1/2
✓ Branch 33 → 34 taken 3613 times.
✗ Branch 33 → 175 not taken.
3613 const QualType &templateType = templateTypeHints.at(i);
247
2/4
✓ Branch 34 → 35 taken 3613 times.
✗ Branch 34 → 155 not taken.
✓ Branch 35 → 36 taken 3613 times.
✗ Branch 35 → 153 not taken.
3613 typeMapping.insert({typeName, templateType});
248 }
249
250 734573 bool forceSubstantiation = false;
251
2/2
✓ Branch 42 → 43 taken 734572 times.
✓ Branch 42 → 175 taken 1 time.
734573 const MatchResult matchResult = matchManifestation(candidate, matchScope, reqName, reqThisType, reqArgs, typeMapping,
252 strictQualifierMatching, forceSubstantiation, callNode);
253
2/2
✓ Branch 43 → 44 taken 685887 times.
✓ Branch 43 → 45 taken 48685 times.
734572 if (matchResult == MatchResult::SKIP_FUNCTION)
254 685887 break; // Leave the whole function
255
2/2
✓ Branch 45 → 46 taken 40919 times.
✓ Branch 45 → 47 taken 7766 times.
48685 if (matchResult == MatchResult::SKIP_MANIFESTATION)
256 40919 continue; // Leave this manifestation and try the next one
257
258 // We found a match! -> Set the actual candidate and its entry to used
259 7766 candidate.used = true;
260 7766 candidate.entry->used = true;
261
262 // Check if the function is generic needs to be substantiated
263
6/6
✓ Branch 48 → 49 taken 5467 times.
✓ Branch 48 → 51 taken 2299 times.
✓ Branch 49 → 50 taken 5389 times.
✓ Branch 49 → 51 taken 78 times.
✓ Branch 52 → 53 taken 5389 times.
✓ Branch 52 → 65 taken 2377 times.
7766 if (presetFunction.templateTypes.empty() && !forceSubstantiation) {
264
5/10
✓ Branch 53 → 54 taken 5389 times.
✗ Branch 53 → 175 not taken.
✓ Branch 54 → 55 taken 5389 times.
✗ Branch 54 → 59 not taken.
✓ Branch 55 → 56 taken 5389 times.
✗ Branch 55 → 175 not taken.
✓ Branch 56 → 57 taken 5389 times.
✗ Branch 56 → 175 not taken.
✓ Branch 57 → 58 taken 5389 times.
✗ Branch 57 → 59 not taken.
5389 assert(matchScope->functions.contains(fctId) && matchScope->functions.at(fctId).contains(signature));
265
3/6
✓ Branch 60 → 61 taken 5389 times.
✗ Branch 60 → 156 not taken.
✓ Branch 61 → 62 taken 5389 times.
✗ Branch 61 → 156 not taken.
✓ Branch 62 → 63 taken 5389 times.
✗ Branch 62 → 156 not taken.
5389 matches.push_back(&matchScope->functions.at(fctId).at(signature));
266 5389 matches.back()->used = true;
267 5389 continue; // Match was successful -> match the next function
268 }
269
270 // Check if we already have this manifestation and can simply re-use it
271
1/2
✓ Branch 65 → 66 taken 2377 times.
✗ Branch 65 → 175 not taken.
2377 const std::string nonGenericSignature = candidate.getSignature(true, false);
272
4/6
✓ Branch 66 → 67 taken 2377 times.
✗ Branch 66 → 173 not taken.
✓ Branch 67 → 68 taken 2377 times.
✗ Branch 67 → 173 not taken.
✓ Branch 68 → 69 taken 164 times.
✓ Branch 68 → 73 taken 2213 times.
2377 if (matchScope->functions.at(fctId).contains(nonGenericSignature)) {
273
3/6
✓ Branch 69 → 70 taken 164 times.
✗ Branch 69 → 157 not taken.
✓ Branch 70 → 71 taken 164 times.
✗ Branch 70 → 157 not taken.
✓ Branch 71 → 72 taken 164 times.
✗ Branch 71 → 157 not taken.
164 matches.push_back(&matchScope->functions.at(fctId).at(nonGenericSignature));
274 164 break; // Leave the whole manifestation list to not double-match the manifestation
275 }
276
277 // Insert the substantiated version if required
278
2/2
✓ Branch 73 → 74 taken 2212 times.
✓ Branch 73 → 173 taken 1 time.
2213 Function *substantiatedFunction = insertSubstantiation(matchScope, candidate, presetFunction.declNode);
279
2/4
✓ Branch 74 → 75 taken 2212 times.
✗ Branch 74 → 173 not taken.
✓ Branch 75 → 76 taken 2212 times.
✗ Branch 75 → 173 not taken.
2212 substantiatedFunction->genericPreset = &matchScope->functions.at(fctId).at(signature);
280 2212 substantiatedFunction->alreadyTypeChecked = false;
281
2/4
✓ Branch 76 → 77 taken 2212 times.
✗ Branch 76 → 173 not taken.
✓ Branch 77 → 78 taken 2212 times.
✗ Branch 77 → 173 not taken.
2212 substantiatedFunction->declNode->getFctManifestations(reqName)->push_back(substantiatedFunction);
282
283 // Copy function entry
284
1/2
✓ Branch 78 → 79 taken 2212 times.
✗ Branch 78 → 173 not taken.
2212 const std::string newSignature = substantiatedFunction->getSignature(false);
285
1/2
✓ Branch 79 → 80 taken 2212 times.
✗ Branch 79 → 171 not taken.
2212 matchScope->lookupStrict(presetFunction.entry->name)->used = true;
286
1/2
✓ Branch 82 → 83 taken 2212 times.
✗ Branch 82 → 171 not taken.
2212 substantiatedFunction->entry = matchScope->symbolTable.copySymbol(presetFunction.entry->name, newSignature);
287
1/2
✗ Branch 83 → 84 not taken.
✓ Branch 83 → 85 taken 2212 times.
2212 assert(substantiatedFunction->entry != nullptr);
288
289 // Copy function scope
290
1/2
✓ Branch 85 → 86 taken 2212 times.
✗ Branch 85 → 171 not taken.
2212 const std::string oldSignature = presetFunction.getSignature(false);
291
1/2
✓ Branch 86 → 87 taken 2212 times.
✗ Branch 86 → 169 not taken.
2212 Scope *childScope = matchScope->copyChildScope(oldSignature, newSignature);
292
1/2
✗ Branch 87 → 88 not taken.
✓ Branch 87 → 89 taken 2212 times.
2212 assert(childScope != nullptr);
293 2212 childScope->isGenericScope = false;
294 2212 substantiatedFunction->bodyScope = childScope;
295
296 // Insert symbols for generic type names with concrete types into the child block
297
2/2
✓ Branch 99 → 91 taken 2607 times.
✓ Branch 99 → 100 taken 2212 times.
4819 for (const auto &[typeName, concreteType] : substantiatedFunction->typeMapping)
298
2/4
✓ Branch 94 → 95 taken 2607 times.
✗ Branch 94 → 160 not taken.
✓ Branch 95 → 96 taken 2607 times.
✗ Branch 95 → 158 not taken.
2607 childScope->insertGenericType(typeName, GenericType(concreteType));
299
300 // Substantiate the 'this' entry in the new function scope
301
6/6
✓ Branch 103 → 104 taken 1792 times.
✓ Branch 103 → 107 taken 420 times.
✓ Branch 105 → 106 taken 1791 times.
✓ Branch 105 → 107 taken 1 time.
✓ Branch 108 → 109 taken 1791 times.
✓ Branch 108 → 122 taken 421 times.
2212 if (presetFunction.isMethod() && !presetFunction.templateTypes.empty()) {
302
1/2
✓ Branch 111 → 112 taken 1791 times.
✗ Branch 111 → 164 not taken.
5373 SymbolTableEntry *thisEntry = childScope->lookupStrict(THIS_VARIABLE_NAME);
303
1/2
✗ Branch 117 → 118 not taken.
✓ Branch 117 → 119 taken 1791 times.
1791 assert(thisEntry != nullptr);
304
2/4
✓ Branch 119 → 120 taken 1791 times.
✗ Branch 119 → 168 not taken.
✓ Branch 120 → 121 taken 1791 times.
✗ Branch 120 → 168 not taken.
1791 thisEntry->updateType(candidate.thisType.toPtr(callNode), /*overwriteExistingType=*/true);
305 }
306
307 // Add to matched functions
308
1/2
✓ Branch 122 → 123 taken 2212 times.
✗ Branch 122 → 169 not taken.
2212 matches.push_back(substantiatedFunction);
309
310 2212 break; // Leave the whole manifestation list to not double-match the manifestation
311
2/2
✓ Branch 129 → 130 taken 46308 times.
✓ Branch 129 → 131 taken 688263 times.
736950 }
312 732572 }
313
314 // If no matches were found, return a nullptr
315
2/2
✓ Branch 139 → 140 taken 52390 times.
✓ Branch 139 → 141 taken 7764 times.
60154 if (matches.empty())
316 52390 return nullptr;
317
318
1/2
✗ Branch 142 → 143 not taken.
✓ Branch 142 → 144 taken 7764 times.
7764 assert(matches.size() == 1);
319 7764 Function *matchedFunction = matches.front();
320
321 // Insert into cache
322
1/2
✓ Branch 145 → 146 taken 7764 times.
✗ Branch 145 → 183 not taken.
7764 lookupCache[cacheKey] = matchedFunction;
323
324 // Trigger revisit in type checker if required
325
1/2
✓ Branch 146 → 147 taken 7764 times.
✗ Branch 146 → 183 not taken.
7764 TypeChecker::requestRevisitIfRequired(matchedFunction);
326
327 // Return the very match
328 7764 return matchedFunction;
329 60158 }
330
331 785767 MatchResult FunctionManager::matchManifestation(Function &candidate, Scope *&matchScope, const std::string &reqName,
332 const QualType &reqThisType, const ArgList &reqArgs, TypeMapping &typeMapping,
333 bool strictQualifierMatching, bool &forceSubstantiation,
334 const ASTNode *callNode) {
335 // Check name requirement
336
2/2
✓ Branch 3 → 4 taken 729805 times.
✓ Branch 3 → 5 taken 55962 times.
785767 if (!matchName(candidate, reqName))
337 729805 return MatchResult::SKIP_FUNCTION; // Leave the whole manifestation list, because all have the same name
338
339 // Check 'this' type requirement
340
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 55962 times.
55962 if (!matchThisType(candidate, reqThisType, typeMapping, strictQualifierMatching, callNode))
341 return MatchResult::SKIP_MANIFESTATION; // Leave this manifestation and try the next one
342
343 // Check arg types requirement
344
2/2
✓ Branch 9 → 10 taken 46909 times.
✓ Branch 9 → 11 taken 9052 times.
55962 if (!matchArgTypes(candidate, reqArgs, typeMapping, strictQualifierMatching, forceSubstantiation, callNode))
345 46909 return MatchResult::SKIP_MANIFESTATION; // Leave this manifestation and try the next one
346
347 // Check if there are unresolved generic types
348
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 9051 times.
9052 if (typeMapping.size() < candidate.templateTypes.size())
349 1 return MatchResult::SKIP_MANIFESTATION; // Leave this manifestation and try the next one
350
351 // Substantiate return type
352 9051 substantiateReturnType(candidate, typeMapping, callNode);
353
354 // Set the match scope to the scope of the concrete substantiation
355 9051 const QualType &thisType = candidate.thisType;
356
2/2
✓ Branch 17 → 18 taken 5575 times.
✓ Branch 17 → 25 taken 3476 times.
9051 if (!thisType.is(TY_DYN)) {
357 // If we only have the generic struct scope, lookup the concrete manifestation scope
358
2/2
✓ Branch 18 → 19 taken 62 times.
✓ Branch 18 → 23 taken 5513 times.
5575 if (matchScope->isGenericScope) {
359 62 const Struct *spiceStruct = thisType.getStruct(candidate.declNode);
360
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 62 times.
62 assert(spiceStruct != nullptr);
361 62 matchScope = spiceStruct->scope;
362 }
363
1/2
✓ Branch 23 → 24 taken 5575 times.
✗ Branch 23 → 27 not taken.
5575 candidate.thisType = candidate.thisType.getWithBodyScope(matchScope);
364 }
365
366 9051 return MatchResult::MATCHED;
367 }
368
369 /**
370 * Checks if the matching candidate fulfills the name requirement
371 *
372 * @param candidate Matching candidate function
373 * @param reqName Requested function name
374 * @return Fulfilled or not
375 */
376 785767 bool FunctionManager::matchName(const Function &candidate, const std::string &reqName) { return candidate.name == reqName; }
377
378 /**
379 * Checks if the matching candidate fulfills the 'this' type requirement
380 *
381 * @param candidate Matching candidate function
382 * @param reqThisType Requested 'this' type
383 * @param typeMapping Concrete template type mapping
384 * @param strictQualifierMatching Match qualifiers strictly
385 * @param callNode Call AST node for printing error messages
386 * @return Fulfilled or not
387 */
388 55962 bool FunctionManager::matchThisType(Function &candidate, const QualType &reqThisType, TypeMapping &typeMapping,
389 bool strictQualifierMatching, const ASTNode *callNode) {
390 55962 QualType &candidateThisType = candidate.thisType;
391
392 // Shortcut for procedures
393
7/10
✓ Branch 2 → 3 taken 55962 times.
✗ Branch 2 → 23 not taken.
✓ Branch 3 → 4 taken 38394 times.
✓ Branch 3 → 7 taken 17568 times.
✓ Branch 4 → 5 taken 38394 times.
✗ Branch 4 → 23 not taken.
✓ Branch 5 → 6 taken 38394 times.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 38394 times.
✓ Branch 8 → 10 taken 17568 times.
55962 if (candidateThisType.is(TY_DYN) && reqThisType.is(TY_DYN))
394 38394 return true;
395
396 // Give the type matcher a way to retrieve instances of GenericType by their name
397 37881 TypeMatcher::ResolverFct genericTypeResolver = [&](const std::string &genericTypeName) {
398 2745 return getGenericTypeOfCandidateByName(candidate, genericTypeName);
399 17568 };
400
401 // Check if the requested 'this' type matches the candidate 'this' type. The type mapping may be extended
402
2/4
✓ Branch 11 → 12 taken 17568 times.
✗ Branch 11 → 21 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 17568 times.
17568 if (!TypeMatcher::matchRequestedToCandidateType(candidateThisType, reqThisType, typeMapping, genericTypeResolver,
403 strictQualifierMatching))
404 return false;
405
406 // Substantiate the candidate param type, based on the type mapping
407
3/4
✓ Branch 14 → 15 taken 17568 times.
✗ Branch 14 → 21 not taken.
✓ Branch 15 → 16 taken 2901 times.
✓ Branch 15 → 17 taken 14667 times.
17568 if (candidateThisType.hasAnyGenericParts())
408
1/2
✓ Branch 16 → 17 taken 2901 times.
✗ Branch 16 → 21 not taken.
2901 TypeMatcher::substantiateTypeWithTypeMapping(candidateThisType, typeMapping, callNode);
409
410 17568 return true;
411 17568 }
412
413 /**
414 * Checks if the matching candidate fulfills the argument types requirement
415 *
416 * @param candidate Matching candidate function
417 * @param reqArgs Requested argument types
418 * @param typeMapping Concrete template type mapping
419 * @param strictQualifierMatching Match qualifiers strictly
420 * @param needsSubstantiation We want to create a substantiation after successfully matching
421 * @param callNode Call AST node for printing error messages
422 * @return Fulfilled or not
423 */
424 55962 bool FunctionManager::matchArgTypes(Function &candidate, const ArgList &reqArgs, TypeMapping &typeMapping,
425 bool strictQualifierMatching, bool &needsSubstantiation, const ASTNode *callNode) {
426 55962 std::vector<Param> &candidateParamList = candidate.paramList;
427
428 // If the number of arguments does not match with the number of params, the matching fails
429
6/6
✓ Branch 2 → 3 taken 55887 times.
✓ Branch 2 → 7 taken 75 times.
✓ Branch 5 → 6 taken 7322 times.
✓ Branch 5 → 7 taken 48565 times.
✓ Branch 8 → 9 taken 7322 times.
✓ Branch 8 → 10 taken 48640 times.
55962 if (!candidate.isVararg && reqArgs.size() != candidateParamList.size())
430 7322 return false;
431 // In the case of a vararg function, we only disallow fewer arguments than parameters
432
4/6
✓ Branch 10 → 11 taken 75 times.
✓ Branch 10 → 15 taken 48565 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 75 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 48640 times.
48640 if (candidate.isVararg && reqArgs.size() < candidateParamList.size())
433 return false;
434
435 // Give the type matcher a way to retrieve instances of GenericType by their name
436 101493 TypeMatcher::ResolverFct genericTypeResolver = [&](const std::string &genericTypeName) {
437 4213 return getGenericTypeOfCandidateByName(candidate, genericTypeName);
438 48640 };
439
440 // Loop over all parameters
441
2/2
✓ Branch 66 → 20 taken 51504 times.
✓ Branch 66 → 67 taken 9052 times.
60556 for (size_t i = 0; i < reqArgs.size(); i++) {
442 // In the case of a vararg function candidate, we can accept additional arguments, that are not defined in the candidate,
443 // but we need to modify the candidate param list to accept them
444
6/6
✓ Branch 20 → 21 taken 300 times.
✓ Branch 20 → 24 taken 51204 times.
✓ Branch 22 → 23 taken 75 times.
✓ Branch 22 → 24 taken 225 times.
✓ Branch 25 → 26 taken 75 times.
✓ Branch 25 → 29 taken 51429 times.
51504 if (candidate.isVararg && i >= candidateParamList.size()) {
445
2/4
✓ Branch 26 → 27 taken 75 times.
✗ Branch 26 → 71 not taken.
✓ Branch 27 → 28 taken 75 times.
✗ Branch 27 → 71 not taken.
75 candidateParamList.push_back(Param(reqArgs.at(i).first, false));
446 75 needsSubstantiation = true; // We need to modify the candidate param types
447 75 continue;
448 }
449
450 // Retrieve actual and requested types
451
2/4
✓ Branch 29 → 30 taken 51429 times.
✗ Branch 29 → 84 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 51429 times.
51429 assert(!candidateParamList.at(i).isOptional);
452
1/2
✓ Branch 32 → 33 taken 51429 times.
✗ Branch 32 → 84 not taken.
51429 QualType &candidateType = candidateParamList.at(i).qualType;
453
1/2
✓ Branch 33 → 34 taken 51429 times.
✗ Branch 33 → 84 not taken.
51429 const auto &[requestedType, isArgTemporary] = reqArgs.at(i);
454
455 // Check if the requested param type matches the candidate param type. The type mapping may be extended
456
3/4
✓ Branch 36 → 37 taken 51429 times.
✗ Branch 36 → 84 not taken.
✓ Branch 37 → 38 taken 39587 times.
✓ Branch 37 → 39 taken 11842 times.
51429 if (!TypeMatcher::matchRequestedToCandidateType(candidateType, requestedType, typeMapping, genericTypeResolver,
457 strictQualifierMatching))
458 39587 return false;
459
460 // Substantiate the candidate param type, based on the type mapping
461
3/4
✓ Branch 39 → 40 taken 11842 times.
✗ Branch 39 → 84 not taken.
✓ Branch 40 → 41 taken 1942 times.
✓ Branch 40 → 42 taken 9900 times.
11842 if (candidateType.hasAnyGenericParts())
462
1/2
✓ Branch 41 → 42 taken 1942 times.
✗ Branch 41 → 84 not taken.
1942 TypeMatcher::substantiateTypeWithTypeMapping(candidateType, typeMapping, callNode);
463
464 // Check if we try to bind a non-ref temporary to a non-const ref parameter
465
3/4
✓ Branch 42 → 43 taken 11842 times.
✗ Branch 42 → 84 not taken.
✓ Branch 43 → 44 taken 1 time.
✓ Branch 43 → 54 taken 11841 times.
11842 if (!candidateType.canBind(requestedType, isArgTemporary)) {
466
1/2
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 53 not taken.
1 if (callNode)
467
2/4
✓ Branch 48 → 49 taken 1 time.
✗ Branch 48 → 75 not taken.
✓ Branch 49 → 50 taken 1 time.
✗ Branch 49 → 72 not taken.
3 throw SemanticError(callNode, TEMP_TO_NON_CONST_REF, "Temporary values can only be bound to const reference parameters");
468 return false;
469 }
470
471 // If we have a function/procedure type we need to take care of the information, if it takes captures
472
9/12
✓ Branch 54 → 55 taken 11841 times.
✗ Branch 54 → 81 not taken.
✓ Branch 55 → 56 taken 11841 times.
✗ Branch 55 → 81 not taken.
✓ Branch 56 → 57 taken 23 times.
✓ Branch 56 → 60 taken 11818 times.
✓ Branch 57 → 58 taken 23 times.
✗ Branch 57 → 81 not taken.
✓ Branch 58 → 59 taken 4 times.
✓ Branch 58 → 60 taken 19 times.
✓ Branch 61 → 62 taken 4 times.
✓ Branch 61 → 64 taken 11837 times.
11841 if (requestedType.getBase().isOneOf({TY_FUNCTION, TY_PROCEDURE}) && requestedType.hasLambdaCaptures()) {
473
1/2
✓ Branch 62 → 63 taken 4 times.
✗ Branch 62 → 83 not taken.
4 candidateType = candidateType.getWithLambdaCaptures();
474 4 needsSubstantiation = true;
475 }
476 }
477
478 9052 return true;
479 48640 }
480
481 /**
482 * Substantiates the candidate return type, based on the given type mapping
483 *
484 * @param candidate Matching candidate function
485 * @param typeMapping Concrete template type mapping
486 * @param callNode AST node for error messages
487 */
488 9051 void FunctionManager::substantiateReturnType(Function &candidate, const TypeMapping &typeMapping, const ASTNode *callNode) {
489
2/2
✓ Branch 3 → 4 taken 686 times.
✓ Branch 3 → 5 taken 8365 times.
9051 if (candidate.returnType.hasAnyGenericParts())
490 686 TypeMatcher::substantiateTypeWithTypeMapping(candidate.returnType, typeMapping, callNode);
491 9051 }
492
493 /**
494 * Searches the candidate template types for a generic type object with a certain name and return it
495 *
496 * @param candidate Matching candidate function
497 * @param templateTypeName Template type name
498 * @return Generic type object
499 */
500 6958 const GenericType *FunctionManager::getGenericTypeOfCandidateByName(const Function &candidate,
501 const std::string &templateTypeName) {
502
1/2
✓ Branch 11 → 4 taken 7560 times.
✗ Branch 11 → 12 not taken.
7560 for (const GenericType &templateType : candidate.templateTypes) {
503
3/4
✓ Branch 5 → 6 taken 7560 times.
✗ Branch 5 → 14 not taken.
✓ Branch 7 → 8 taken 6958 times.
✓ Branch 7 → 9 taken 602 times.
7560 if (templateType.getSubType() == templateTypeName)
504 6958 return &templateType;
505 }
506 return nullptr;
507 }
508
509 /**
510 * Calculate the cache key for the function lookup cache
511 *
512 * @param scope Scope to match against
513 * @param name Function name requirement
514 * @param thisType This type requirement
515 * @param args Argument requirement
516 * @param templateTypes Template type requirement
517 * @return Cache key
518 */
519 83766 uint64_t FunctionManager::getCacheKey(Scope *scope, const std::string &name, const QualType &thisType, const ArgList &args,
520 const QualTypeList &templateTypes) {
521 83766 uint64_t acc = 1469598103934665603ull;
522
1/2
✓ Branch 3 → 4 taken 83766 times.
✗ Branch 3 → 30 not taken.
83766 acc = hash_combine64(acc, std::hash<Scope *>{}(scope));
523
1/2
✓ Branch 5 → 6 taken 83766 times.
✗ Branch 5 → 31 not taken.
83766 acc = hash_combine64(acc, std::hash<std::string>{}(name));
524
1/2
✓ Branch 7 → 8 taken 83766 times.
✗ Branch 7 → 32 not taken.
83766 acc = hash_combine64(acc, std::hash<QualType>{}(thisType));
525
2/2
✓ Branch 19 → 10 taken 124414 times.
✓ Branch 19 → 20 taken 83766 times.
208180 for (const auto &[first, second] : args) {
526
1/2
✓ Branch 14 → 15 taken 124414 times.
✗ Branch 14 → 33 not taken.
124414 acc = hash_combine64(acc, std::hash<QualType>{}(first));
527
1/2
✓ Branch 16 → 17 taken 124414 times.
✗ Branch 16 → 34 not taken.
124414 acc = hash_combine64(acc, std::hash<bool>{}(second));
528 }
529
2/2
✓ Branch 27 → 22 taken 744 times.
✓ Branch 27 → 28 taken 83766 times.
84510 for (const QualType &qt : templateTypes)
530
1/2
✓ Branch 24 → 25 taken 744 times.
✗ Branch 24 → 36 not taken.
744 acc = hash_combine64(acc, std::hash<QualType>{}(qt));
531 83766 return acc;
532 }
533
534 /**
535 * Clear the lookup cache
536 */
537 422 void FunctionManager::cleanup() {
538 422 lookupCache.clear();
539 422 lookupCacheHits = 0;
540 422 lookupCacheMisses = 0;
541 422 }
542
543 /**
544 * Dump usage statistics for the lookup cache
545 */
546 198 std::string FunctionManager::dumpLookupCacheStatistics() {
547
1/2
✓ Branch 2 → 3 taken 198 times.
✗ Branch 2 → 22 not taken.
198 std::stringstream stats;
548
2/4
✓ Branch 3 → 4 taken 198 times.
✗ Branch 3 → 20 not taken.
✓ Branch 4 → 5 taken 198 times.
✗ Branch 4 → 20 not taken.
198 stats << "FunctionManager lookup cache statistics:" << std::endl;
549
3/6
✓ Branch 5 → 6 taken 198 times.
✗ Branch 5 → 20 not taken.
✓ Branch 7 → 8 taken 198 times.
✗ Branch 7 → 20 not taken.
✓ Branch 8 → 9 taken 198 times.
✗ Branch 8 → 20 not taken.
198 stats << " lookup cache entries: " << lookupCache.size() << std::endl;
550
3/6
✓ Branch 9 → 10 taken 198 times.
✗ Branch 9 → 20 not taken.
✓ Branch 10 → 11 taken 198 times.
✗ Branch 10 → 20 not taken.
✓ Branch 11 → 12 taken 198 times.
✗ Branch 11 → 20 not taken.
198 stats << " lookup cache hits: " << lookupCacheHits << std::endl;
551
3/6
✓ Branch 12 → 13 taken 198 times.
✗ Branch 12 → 20 not taken.
✓ Branch 13 → 14 taken 198 times.
✗ Branch 13 → 20 not taken.
✓ Branch 14 → 15 taken 198 times.
✗ Branch 14 → 20 not taken.
198 stats << " lookup cache misses: " << lookupCacheMisses << std::endl;
552
1/2
✓ Branch 15 → 16 taken 198 times.
✗ Branch 15 → 20 not taken.
396 return stats.str();
553 198 }
554
555 } // namespace spice::compiler
556