GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 93.0% 119 / 0 / 128
Functions: 100.0% 11 / 0 / 11
Branches: 54.5% 116 / 0 / 213

src/typechecker/InterfaceManager.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "InterfaceManager.h"
4
5 #include <ast/ASTNodes.h>
6 #include <exception/SemanticError.h>
7 #include <model/GenericType.h>
8 #include <model/Interface.h>
9 #include <symboltablebuilder/Scope.h>
10 #include <typechecker/FunctionManager.h>
11 #include <typechecker/TypeMatcher.h>
12 #include <util/CustomHashFunctions.h>
13
14 namespace spice::compiler {
15
16 // Static member initialization
17 std::unordered_map<uint64_t, Interface *> InterfaceManager::lookupCache = {};
18 size_t InterfaceManager::lookupCacheHits = 0;
19 size_t InterfaceManager::lookupCacheMisses = 0;
20
21 104 Interface *InterfaceManager::insert(Scope *insertScope, Interface &spiceInterface, std::vector<Interface *> *nodeInterfaceList) {
22 // Open a new manifestation list. Which gets filled by the substantiated manifestations of the interface
23
1/2
✓ Branch 4 → 5 taken 104 times.
✗ Branch 4 → 11 not taken.
104 insertScope->interfaces.insert({spiceInterface.declNode->codeLoc, InterfaceManifestationList()});
24
25 // Save substantiation in declaration node
26
1/2
✓ Branch 7 → 8 taken 104 times.
✗ Branch 7 → 17 not taken.
104 Interface *substantiation = insertSubstantiation(insertScope, spiceInterface, spiceInterface.declNode);
27
1/2
✓ Branch 8 → 9 taken 104 times.
✗ Branch 8 → 17 not taken.
104 nodeInterfaceList->push_back(substantiation);
28
29 104 return substantiation;
30 }
31
32 269 Interface *InterfaceManager::insertSubstantiation(Scope *insertScope, Interface &newManifestation, const ASTNode *declNode) {
33
1/2
✓ Branch 2 → 3 taken 269 times.
✗ Branch 2 → 31 not taken.
269 const std::string signature = newManifestation.getSignature();
34
35 // Make sure that the manifestation does not exist already
36
2/2
✓ Branch 13 → 5 taken 269 times.
✓ Branch 13 → 14 taken 269 times.
538 for ([[maybe_unused]] const auto &manifestations : insertScope->interfaces)
37
3/6
✓ Branch 6 → 7 taken 269 times.
✗ Branch 6 → 27 not taken.
✓ Branch 7 → 8 taken 269 times.
✗ Branch 7 → 25 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 269 times.
269 assert(!manifestations.second.contains(newManifestation.getSignature()));
38
39 // Retrieve the matching manifestation list of the scope
40
2/4
✓ Branch 14 → 15 taken 269 times.
✗ Branch 14 → 29 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 269 times.
269 assert(insertScope->interfaces.contains(declNode->codeLoc));
41
1/2
✓ Branch 17 → 18 taken 269 times.
✗ Branch 17 → 29 not taken.
269 InterfaceManifestationList &manifestationList = insertScope->interfaces.at(declNode->codeLoc);
42
43 // Add substantiated interface
44 269 newManifestation.manifestationIndex = manifestationList.size();
45
1/2
✓ Branch 19 → 20 taken 269 times.
✗ Branch 19 → 29 not taken.
269 manifestationList.emplace(signature, newManifestation);
46
1/2
✓ Branch 20 → 21 taken 269 times.
✗ Branch 20 → 29 not taken.
538 return &manifestationList.at(signature);
47 269 }
48
49 /**
50 * Check if there is an interface in this scope, fulfilling all given requirements and if found, return it.
51 * If more than one interface matches the requirement, an error gets thrown
52 *
53 * @param matchScope Scope to match against
54 * @param reqName Interface name requirement
55 * @param reqTemplateTypes Template types to substantiate generic types
56 * @param node Instantiation AST node for printing error messages
57 * @return Matched interface or nullptr
58 */
59 1617 Interface *InterfaceManager::match(Scope *matchScope, const std::string &reqName, const QualTypeList &reqTemplateTypes,
60 const ASTNode *node) {
61 // Do cache lookup
62 1617 const uint64_t cacheKey = getCacheKey(matchScope, reqName, reqTemplateTypes);
63
3/4
✓ Branch 3 → 4 taken 1617 times.
✗ Branch 3 → 160 not taken.
✓ Branch 4 → 5 taken 1320 times.
✓ Branch 4 → 7 taken 297 times.
1617 if (lookupCache.contains(cacheKey)) {
64 1320 lookupCacheHits++;
65
1/2
✓ Branch 5 → 6 taken 1320 times.
✗ Branch 5 → 160 not taken.
1320 return lookupCache.at(cacheKey);
66 }
67 297 lookupCacheMisses++;
68
69 // Copy the registry to prevent iterating over items, that are created within the loop
70
1/2
✓ Branch 7 → 8 taken 297 times.
✗ Branch 7 → 160 not taken.
297 InterfaceRegistry interfaceRegistry = matchScope->interfaces;
71 // Loop over interface registry to find interfaces, that match the requirements of the instantiation
72 297 std::vector<Interface *> matches;
73
2/2
✓ Branch 99 → 10 taken 297 times.
✓ Branch 99 → 100 taken 297 times.
594 for (const auto &[defCodeLocStr, m] : interfaceRegistry) {
74 // Copy the manifestation list to prevent iterating over items, that are created within the loop
75
1/2
✓ Branch 13 → 14 taken 297 times.
✗ Branch 13 → 145 not taken.
297 const InterfaceManifestationList manifestations = m;
76
2/2
✓ Branch 95 → 16 taken 511 times.
✓ Branch 95 → 96 taken 188 times.
699 for (const auto &[mangledName, presetInterface] : manifestations) {
77 // Skip generic substantiations to prevent double matching of an interface
78
3/4
✓ Branch 19 → 20 taken 511 times.
✗ Branch 19 → 141 not taken.
✓ Branch 20 → 21 taken 214 times.
✓ Branch 20 → 22 taken 297 times.
511 if (presetInterface.isGenericSubstantiation())
79 237 continue;
80
81 // Copy the interface to be able to substantiate types
82
1/2
✓ Branch 22 → 23 taken 297 times.
✗ Branch 22 → 141 not taken.
297 Interface candidate = presetInterface;
83
84 // Check name requirement
85
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 297 times.
297 if (!matchName(candidate, reqName))
86 break; // Leave the whole manifestation list, because all manifestations in this list have the same name
87
88 // Prepare mapping table from generic type name to concrete type
89 297 TypeMapping &typeMapping = candidate.typeMapping;
90 297 typeMapping.clear();
91
1/2
✓ Branch 28 → 29 taken 297 times.
✗ Branch 28 → 139 not taken.
297 typeMapping.reserve(candidate.templateTypes.size());
92
93 // Check template types requirement
94
2/4
✓ Branch 29 → 30 taken 297 times.
✗ Branch 29 → 139 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 297 times.
297 if (!matchTemplateTypes(candidate, reqTemplateTypes, typeMapping, node))
95 continue; // Leave this manifestation and continue with the next one
96
97 // Map signatures from generic to concrete
98
1/2
✓ Branch 32 → 33 taken 297 times.
✗ Branch 32 → 139 not taken.
297 substantiateSignatures(candidate, typeMapping, node);
99
100 // We found a match! -> Set the actual candidate and its entry to used
101 297 candidate.used = true;
102 297 candidate.entry->used = true;
103
104 // Check if it needs to be substantiated
105
2/2
✓ Branch 34 → 35 taken 23 times.
✓ Branch 34 → 47 taken 274 times.
297 if (presetInterface.templateTypes.empty()) {
106
5/10
✓ Branch 35 → 36 taken 23 times.
✗ Branch 35 → 139 not taken.
✓ Branch 36 → 37 taken 23 times.
✗ Branch 36 → 41 not taken.
✓ Branch 37 → 38 taken 23 times.
✗ Branch 37 → 139 not taken.
✓ Branch 38 → 39 taken 23 times.
✗ Branch 38 → 139 not taken.
✓ Branch 39 → 40 taken 23 times.
✗ Branch 39 → 41 not taken.
23 assert(matchScope->interfaces.contains(defCodeLocStr) && matchScope->interfaces.at(defCodeLocStr).contains(mangledName));
107
3/6
✓ Branch 42 → 43 taken 23 times.
✗ Branch 42 → 121 not taken.
✓ Branch 43 → 44 taken 23 times.
✗ Branch 43 → 121 not taken.
✓ Branch 44 → 45 taken 23 times.
✗ Branch 44 → 121 not taken.
23 matches.push_back(&matchScope->interfaces.at(defCodeLocStr).at(mangledName));
108 23 matches.back()->used = true;
109 23 continue; // Match was successful -> match the next interface
110 }
111
112 // Check if we already have this manifestation and can simply re-use it
113
4/6
✓ Branch 47 → 48 taken 274 times.
✗ Branch 47 → 124 not taken.
✓ Branch 48 → 49 taken 274 times.
✗ Branch 48 → 122 not taken.
✓ Branch 50 → 51 taken 109 times.
✓ Branch 50 → 58 taken 165 times.
274 if (manifestations.contains(candidate.getSignature())) {
114
4/8
✓ Branch 51 → 52 taken 109 times.
✗ Branch 51 → 128 not taken.
✓ Branch 52 → 53 taken 109 times.
✗ Branch 52 → 127 not taken.
✓ Branch 53 → 54 taken 109 times.
✗ Branch 53 → 125 not taken.
✓ Branch 54 → 55 taken 109 times.
✗ Branch 54 → 125 not taken.
109 matches.push_back(&matchScope->interfaces.at(defCodeLocStr).at(candidate.getSignature()));
115 109 matches.back()->used = true;
116 109 break; // Leave the whole manifestation list to not double-match the manifestation
117 }
118
119 // Insert the substantiated version if required
120
1/2
✓ Branch 58 → 59 taken 165 times.
✗ Branch 58 → 139 not taken.
165 Interface *substantiatedInterface = insertSubstantiation(matchScope, candidate, presetInterface.declNode);
121
2/4
✓ Branch 59 → 60 taken 165 times.
✗ Branch 59 → 139 not taken.
✓ Branch 60 → 61 taken 165 times.
✗ Branch 60 → 139 not taken.
165 substantiatedInterface->genericPreset = &matchScope->interfaces.at(defCodeLocStr).at(mangledName);
122
2/4
✓ Branch 61 → 62 taken 165 times.
✗ Branch 61 → 139 not taken.
✓ Branch 62 → 63 taken 165 times.
✗ Branch 62 → 139 not taken.
165 substantiatedInterface->declNode->getInterfaceManifestations()->push_back(substantiatedInterface);
123
124 // Copy interface entry
125
1/2
✓ Branch 63 → 64 taken 165 times.
✗ Branch 63 → 139 not taken.
165 const std::string &newSignature = substantiatedInterface->getSignature();
126
1/2
✓ Branch 64 → 65 taken 165 times.
✗ Branch 64 → 137 not taken.
165 matchScope->lookupStrict(substantiatedInterface->name)->used = true;
127
1/2
✓ Branch 67 → 68 taken 165 times.
✗ Branch 67 → 137 not taken.
165 substantiatedInterface->entry = matchScope->symbolTable.copySymbol(substantiatedInterface->name, newSignature);
128
1/2
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 70 taken 165 times.
165 assert(substantiatedInterface->entry != nullptr);
129
130 // Copy interface scope
131
1/2
✓ Branch 70 → 71 taken 165 times.
✗ Branch 70 → 137 not taken.
165 const std::string &oldScopeName = presetInterface.getScopeName();
132
1/2
✓ Branch 71 → 72 taken 165 times.
✗ Branch 71 → 135 not taken.
165 const std::string &newScopeName = substantiatedInterface->getScopeName();
133
1/2
✓ Branch 72 → 73 taken 165 times.
✗ Branch 72 → 133 not taken.
165 matchScope->copyChildScope(oldScopeName, newScopeName);
134
1/2
✓ Branch 73 → 74 taken 165 times.
✗ Branch 73 → 133 not taken.
165 substantiatedInterface->scope = matchScope->getChildScope(newScopeName);
135
1/2
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 76 taken 165 times.
165 assert(substantiatedInterface->scope != nullptr);
136 165 substantiatedInterface->scope->isGenericScope = false;
137
138 // Attach the template types to the new interface entry
139
1/2
✓ Branch 76 → 77 taken 165 times.
✗ Branch 76 → 132 not taken.
165 QualType entryType = substantiatedInterface->entry->getQualType()
140
2/4
✓ Branch 77 → 78 taken 165 times.
✗ Branch 77 → 131 not taken.
✓ Branch 78 → 79 taken 165 times.
✗ Branch 78 → 129 not taken.
330 .getWithTemplateTypes(substantiatedInterface->getTemplateTypes())
141
1/2
✓ Branch 79 → 80 taken 165 times.
✗ Branch 79 → 129 not taken.
165 .getWithBodyScope(substantiatedInterface->scope);
142
1/2
✓ Branch 81 → 82 taken 165 times.
✗ Branch 81 → 133 not taken.
165 substantiatedInterface->entry->updateType(entryType, true);
143
144 // Add to matched interfaces
145
1/2
✓ Branch 82 → 83 taken 165 times.
✗ Branch 82 → 133 not taken.
165 matches.push_back(substantiatedInterface);
146
3/3
✓ Branch 88 → 89 taken 165 times.
✓ Branch 88 → 91 taken 23 times.
✓ Branch 88 → 92 taken 109 times.
297 }
147 297 }
148
149 // If no matches were found, return a nullptr
150
1/2
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 103 taken 297 times.
297 if (matches.empty())
151 return nullptr;
152
153 // Check if more than one interface matches the requirements
154
1/2
✗ Branch 104 → 105 not taken.
✓ Branch 104 → 113 taken 297 times.
297 if (matches.size() > 1)
155 throw SemanticError(node, INTERFACE_AMBIGUITY, "Multiple interfaces match the requested signature");
156
157 // Insert into cache
158
1/2
✓ Branch 114 → 115 taken 297 times.
✗ Branch 114 → 156 not taken.
297 lookupCache[cacheKey] = matches.front();
159
160 297 return matches.front();
161 297 }
162
163 /**
164 * Checks if the matching candidate fulfills the name requirement
165 *
166 * @param candidate Matching candidate interface
167 * @param reqName Requested interface name
168 * @return Fulfilled or not
169 */
170 297 bool InterfaceManager::matchName(const Interface &candidate, const std::string &reqName) { return candidate.name == reqName; }
171
172 /**
173 * Checks if the matching candidate fulfills the template types requirement
174 *
175 * @param candidate Matching candidate interface
176 * @param reqTemplateTypes Requested interface template types
177 * @param typeMapping Generic type mapping
178 * @param node Instantiation AST node for printing error messages
179 * @return Fulfilled or not
180 */
181 297 bool InterfaceManager::matchTemplateTypes(Interface &candidate, const QualTypeList &reqTemplateTypes, TypeMapping &typeMapping,
182 const ASTNode *node) {
183 // Check if the number of types match
184 297 const size_t typeCount = reqTemplateTypes.size();
185
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 297 times.
297 if (typeCount != candidate.templateTypes.size())
186 return false;
187
188 // Give the type matcher a way to retrieve instances of GenericType by their name
189 868 TypeMatcher::ResolverFct genericTypeResolver = [&](const std::string &genericTypeName) {
190 274 return getGenericTypeOfCandidateByName(candidate, genericTypeName);
191 297 };
192
193 // Loop over all template types
194
2/2
✓ Branch 17 → 8 taken 274 times.
✓ Branch 17 → 18 taken 297 times.
571 for (size_t i = 0; i < typeCount; i++) {
195
1/2
✓ Branch 8 → 9 taken 274 times.
✗ Branch 8 → 22 not taken.
274 const QualType &reqType = reqTemplateTypes.at(i);
196
1/2
✓ Branch 9 → 10 taken 274 times.
✗ Branch 9 → 22 not taken.
274 QualType &candidateType = candidate.templateTypes.at(i);
197
198 // Check if the requested template type matches the candidate template type. The type mapping may be extended
199
2/4
✓ Branch 10 → 11 taken 274 times.
✗ Branch 10 → 22 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 274 times.
274 if (!TypeMatcher::matchRequestedToCandidateType(candidateType, reqType, typeMapping, genericTypeResolver, false))
200 return false;
201
202 // Substantiate the candidate param type, based on the type mapping
203
2/4
✓ Branch 13 → 14 taken 274 times.
✗ Branch 13 → 22 not taken.
✓ Branch 14 → 15 taken 274 times.
✗ Branch 14 → 16 not taken.
274 if (candidateType.hasAnyGenericParts())
204
1/2
✓ Branch 15 → 16 taken 274 times.
✗ Branch 15 → 22 not taken.
274 TypeMatcher::substantiateTypeWithTypeMapping(candidateType, typeMapping, node);
205 }
206
207 297 return true;
208 297 }
209
210 /**
211 * Come up with the concrete signatures, by applying the type mapping onto the generic signatures
212 *
213 * @param candidate Candidate interface
214 * @param typeMapping Generic type mapping
215 * @param node Instantiation AST node for printing error messages
216 */
217 297 void InterfaceManager::substantiateSignatures(Interface &candidate, const TypeMapping &typeMapping, const ASTNode *node) {
218 // Loop over all signatures and substantiate the generic ones
219
2/2
✓ Branch 34 → 4 taken 775 times.
✓ Branch 34 → 35 taken 297 times.
1072 for (Function *method : candidate.methods) {
220 // Skip methods, that are already fully substantiated
221
3/4
✓ Branch 5 → 6 taken 775 times.
✗ Branch 5 → 37 not taken.
✓ Branch 6 → 7 taken 343 times.
✓ Branch 6 → 8 taken 432 times.
775 if (method->isFullySubstantiated())
222 343 continue;
223
224 // Substantiate return type
225
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 432 times.
432 if (method->isNormalFunction())
226 FunctionManager::substantiateReturnType(*method, typeMapping, node);
227
228 // Substantiate param types
229
2/2
✓ Branch 30 → 24 taken 2 times.
✓ Branch 30 → 31 taken 432 times.
434 for (auto &[qualType, isOptional] : method->paramList)
230
2/4
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 36 not taken.
✓ Branch 26 → 27 taken 2 times.
✗ Branch 26 → 28 not taken.
2 if (qualType.isBase(TY_GENERIC))
231
1/2
✓ Branch 27 → 28 taken 2 times.
✗ Branch 27 → 36 not taken.
2 TypeMatcher::substantiateTypeWithTypeMapping(qualType, typeMapping, node);
232 }
233 297 }
234
235 /**
236 * Searches the candidate template types for a generic type object with a certain name and return it
237 *
238 * @param candidate Matching candidate interface
239 * @param templateTypeName Template type name
240 * @return Generic type object
241 */
242 274 const GenericType *InterfaceManager::getGenericTypeOfCandidateByName(const Interface &candidate,
243 const std::string &templateTypeName) {
244
1/2
✓ Branch 14 → 4 taken 274 times.
✗ Branch 14 → 15 not taken.
274 for (const GenericType &templateType : candidate.templateTypes) {
245
2/4
✓ Branch 5 → 6 taken 274 times.
✗ Branch 5 → 17 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 274 times.
274 if (!templateType.is(TY_GENERIC))
246 continue;
247
2/4
✓ Branch 8 → 9 taken 274 times.
✗ Branch 8 → 17 not taken.
✓ Branch 10 → 11 taken 274 times.
✗ Branch 10 → 12 not taken.
274 if (templateType.getSubType() == templateTypeName)
248 274 return &templateType;
249 }
250 return nullptr;
251 }
252
253 /**
254 * Calculate the cache key for the interface lookup cache
255 *
256 * @param scope Scope to match against
257 * @param name Interface name requirement
258 * @param templateTypes Template types to substantiate generic types
259 * @return Cache key
260 */
261 1617 uint64_t InterfaceManager::getCacheKey(const Scope *scope, const std::string &name, const QualTypeList &templateTypes) {
262 1617 uint64_t hash = 0;
263 1617 hashCombine64(hash, hashPointer(scope));
264 1617 hashCombine64(hash, std::hash<std::string>{}(name));
265 1617 hashCombine64(hash, hashVector(templateTypes));
266 1617 return hash;
267 }
268
269 /**
270 * Clear the lookup cache
271 */
272 458 void InterfaceManager::cleanup() {
273 458 lookupCache.clear();
274 458 lookupCacheHits = 0;
275 458 lookupCacheMisses = 0;
276 458 }
277
278 /**
279 * Dump usage statistics for the lookup cache
280 */
281 223 std::string InterfaceManager::dumpLookupCacheStatistics() {
282
1/2
✓ Branch 2 → 3 taken 223 times.
✗ Branch 2 → 22 not taken.
223 std::stringstream stats;
283
2/4
✓ Branch 3 → 4 taken 223 times.
✗ Branch 3 → 20 not taken.
✓ Branch 4 → 5 taken 223 times.
✗ Branch 4 → 20 not taken.
223 stats << "InterfaceManager lookup cache statistics:" << std::endl;
284
3/6
✓ Branch 5 → 6 taken 223 times.
✗ Branch 5 → 20 not taken.
✓ Branch 7 → 8 taken 223 times.
✗ Branch 7 → 20 not taken.
✓ Branch 8 → 9 taken 223 times.
✗ Branch 8 → 20 not taken.
223 stats << " lookup cache entries: " << lookupCache.size() << std::endl;
285
3/6
✓ Branch 9 → 10 taken 223 times.
✗ Branch 9 → 20 not taken.
✓ Branch 10 → 11 taken 223 times.
✗ Branch 10 → 20 not taken.
✓ Branch 11 → 12 taken 223 times.
✗ Branch 11 → 20 not taken.
223 stats << " lookup cache hits: " << lookupCacheHits << std::endl;
286
3/6
✓ Branch 12 → 13 taken 223 times.
✗ Branch 12 → 20 not taken.
✓ Branch 13 → 14 taken 223 times.
✗ Branch 13 → 20 not taken.
✓ Branch 14 → 15 taken 223 times.
✗ Branch 14 → 20 not taken.
223 stats << " lookup cache misses: " << lookupCacheMisses << std::endl;
287
1/2
✓ Branch 15 → 16 taken 223 times.
✗ Branch 15 → 20 not taken.
446 return stats.str();
288 223 }
289
290 } // namespace spice::compiler
291