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 105 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 105 times.
✗ Branch 4 → 11 not taken.
105 insertScope->interfaces.insert({spiceInterface.declNode->codeLoc, InterfaceManifestationList()});
24
25 // Save substantiation in declaration node
26
1/2
✓ Branch 7 → 8 taken 105 times.
✗ Branch 7 → 17 not taken.
105 Interface *substantiation = insertSubstantiation(insertScope, spiceInterface, spiceInterface.declNode);
27
1/2
✓ Branch 8 → 9 taken 105 times.
✗ Branch 8 → 17 not taken.
105 nodeInterfaceList->push_back(substantiation);
28
29 105 return substantiation;
30 }
31
32 272 Interface *InterfaceManager::insertSubstantiation(Scope *insertScope, Interface &newManifestation, const ASTNode *declNode) {
33
1/2
✓ Branch 2 → 3 taken 272 times.
✗ Branch 2 → 31 not taken.
272 const std::string signature = newManifestation.getSignature();
34
35 // Make sure that the manifestation does not exist already
36
2/2
✓ Branch 13 → 5 taken 272 times.
✓ Branch 13 → 14 taken 272 times.
544 for ([[maybe_unused]] const auto &manifestations : insertScope->interfaces)
37
3/6
✓ Branch 6 → 7 taken 272 times.
✗ Branch 6 → 27 not taken.
✓ Branch 7 → 8 taken 272 times.
✗ Branch 7 → 25 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 272 times.
272 assert(!manifestations.second.contains(newManifestation.getSignature()));
38
39 // Retrieve the matching manifestation list of the scope
40
2/4
✓ Branch 14 → 15 taken 272 times.
✗ Branch 14 → 29 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 272 times.
272 assert(insertScope->interfaces.contains(declNode->codeLoc));
41
1/2
✓ Branch 17 → 18 taken 272 times.
✗ Branch 17 → 29 not taken.
272 InterfaceManifestationList &manifestationList = insertScope->interfaces.at(declNode->codeLoc);
42
43 // Add substantiated interface
44 272 newManifestation.manifestationIndex = manifestationList.size();
45
1/2
✓ Branch 19 → 20 taken 272 times.
✗ Branch 19 → 29 not taken.
272 manifestationList.emplace(signature, newManifestation);
46
1/2
✓ Branch 20 → 21 taken 272 times.
✗ Branch 20 → 29 not taken.
544 return &manifestationList.at(signature);
47 272 }
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 1631 Interface *InterfaceManager::match(Scope *matchScope, const std::string &reqName, const QualTypeList &reqTemplateTypes,
60 const ASTNode *node) {
61 // Do cache lookup
62 1631 const uint64_t cacheKey = getCacheKey(matchScope, reqName, reqTemplateTypes);
63
3/4
✓ Branch 3 → 4 taken 1631 times.
✗ Branch 3 → 160 not taken.
✓ Branch 4 → 5 taken 1331 times.
✓ Branch 4 → 7 taken 300 times.
1631 if (lookupCache.contains(cacheKey)) {
64 1331 lookupCacheHits++;
65
1/2
✓ Branch 5 → 6 taken 1331 times.
✗ Branch 5 → 160 not taken.
1331 return lookupCache.at(cacheKey);
66 }
67 300 lookupCacheMisses++;
68
69 // Copy the registry to prevent iterating over items, that are created within the loop
70
1/2
✓ Branch 7 → 8 taken 300 times.
✗ Branch 7 → 160 not taken.
300 InterfaceRegistry interfaceRegistry = matchScope->interfaces;
71 // Loop over interface registry to find interfaces, that match the requirements of the instantiation
72 300 std::vector<Interface *> matches;
73
2/2
✓ Branch 99 → 10 taken 300 times.
✓ Branch 99 → 100 taken 300 times.
600 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 300 times.
✗ Branch 13 → 145 not taken.
300 const InterfaceManifestationList manifestations = m;
76
2/2
✓ Branch 95 → 16 taken 514 times.
✓ Branch 95 → 96 taken 189 times.
703 for (const auto &[mangledName, presetInterface] : manifestations) {
77 // Skip generic substantiations to prevent double matching of an interface
78
3/4
✓ Branch 19 → 20 taken 514 times.
✗ Branch 19 → 141 not taken.
✓ Branch 20 → 21 taken 214 times.
✓ Branch 20 → 22 taken 300 times.
514 if (presetInterface.isGenericSubstantiation())
79 236 continue;
80
81 // Copy the interface to be able to substantiate types
82
1/2
✓ Branch 22 → 23 taken 300 times.
✗ Branch 22 → 141 not taken.
300 Interface candidate = presetInterface;
83
84 // Check name requirement
85
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 300 times.
300 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 300 TypeMapping &typeMapping = candidate.typeMapping;
90 300 typeMapping.clear();
91
1/2
✓ Branch 28 → 29 taken 300 times.
✗ Branch 28 → 139 not taken.
300 typeMapping.reserve(candidate.templateTypes.size());
92
93 // Check template types requirement
94
2/4
✓ Branch 29 → 30 taken 300 times.
✗ Branch 29 → 139 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 300 times.
300 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 300 times.
✗ Branch 32 → 139 not taken.
300 substantiateSignatures(candidate, typeMapping, node);
99
100 // We found a match! -> Set the actual candidate and its entry to used
101 300 candidate.used = true;
102 300 candidate.entry->used = true;
103
104 // Check if it needs to be substantiated
105
2/2
✓ Branch 34 → 35 taken 22 times.
✓ Branch 34 → 47 taken 278 times.
300 if (presetInterface.templateTypes.empty()) {
106
5/10
✓ Branch 35 → 36 taken 22 times.
✗ Branch 35 → 139 not taken.
✓ Branch 36 → 37 taken 22 times.
✗ Branch 36 → 41 not taken.
✓ Branch 37 → 38 taken 22 times.
✗ Branch 37 → 139 not taken.
✓ Branch 38 → 39 taken 22 times.
✗ Branch 38 → 139 not taken.
✓ Branch 39 → 40 taken 22 times.
✗ Branch 39 → 41 not taken.
22 assert(matchScope->interfaces.contains(defCodeLocStr) && matchScope->interfaces.at(defCodeLocStr).contains(mangledName));
107
3/6
✓ Branch 42 → 43 taken 22 times.
✗ Branch 42 → 121 not taken.
✓ Branch 43 → 44 taken 22 times.
✗ Branch 43 → 121 not taken.
✓ Branch 44 → 45 taken 22 times.
✗ Branch 44 → 121 not taken.
22 matches.push_back(&matchScope->interfaces.at(defCodeLocStr).at(mangledName));
108 22 matches.back()->used = true;
109 22 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 278 times.
✗ Branch 47 → 124 not taken.
✓ Branch 48 → 49 taken 278 times.
✗ Branch 48 → 122 not taken.
✓ Branch 50 → 51 taken 111 times.
✓ Branch 50 → 58 taken 167 times.
278 if (manifestations.contains(candidate.getSignature())) {
114
4/8
✓ Branch 51 → 52 taken 111 times.
✗ Branch 51 → 128 not taken.
✓ Branch 52 → 53 taken 111 times.
✗ Branch 52 → 127 not taken.
✓ Branch 53 → 54 taken 111 times.
✗ Branch 53 → 125 not taken.
✓ Branch 54 → 55 taken 111 times.
✗ Branch 54 → 125 not taken.
111 matches.push_back(&matchScope->interfaces.at(defCodeLocStr).at(candidate.getSignature()));
115 111 matches.back()->used = true;
116 111 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 167 times.
✗ Branch 58 → 139 not taken.
167 Interface *substantiatedInterface = insertSubstantiation(matchScope, candidate, presetInterface.declNode);
121
2/4
✓ Branch 59 → 60 taken 167 times.
✗ Branch 59 → 139 not taken.
✓ Branch 60 → 61 taken 167 times.
✗ Branch 60 → 139 not taken.
167 substantiatedInterface->genericPreset = &matchScope->interfaces.at(defCodeLocStr).at(mangledName);
122
2/4
✓ Branch 61 → 62 taken 167 times.
✗ Branch 61 → 139 not taken.
✓ Branch 62 → 63 taken 167 times.
✗ Branch 62 → 139 not taken.
167 substantiatedInterface->declNode->getInterfaceManifestations()->push_back(substantiatedInterface);
123
124 // Copy interface entry
125
1/2
✓ Branch 63 → 64 taken 167 times.
✗ Branch 63 → 139 not taken.
167 const std::string &newSignature = substantiatedInterface->getSignature();
126
1/2
✓ Branch 64 → 65 taken 167 times.
✗ Branch 64 → 137 not taken.
167 matchScope->lookupStrict(substantiatedInterface->name)->used = true;
127
1/2
✓ Branch 67 → 68 taken 167 times.
✗ Branch 67 → 137 not taken.
167 substantiatedInterface->entry = matchScope->symbolTable.copySymbol(substantiatedInterface->name, newSignature);
128
1/2
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 70 taken 167 times.
167 assert(substantiatedInterface->entry != nullptr);
129
130 // Copy interface scope
131
1/2
✓ Branch 70 → 71 taken 167 times.
✗ Branch 70 → 137 not taken.
167 const std::string &oldScopeName = presetInterface.getScopeName();
132
1/2
✓ Branch 71 → 72 taken 167 times.
✗ Branch 71 → 135 not taken.
167 const std::string &newScopeName = substantiatedInterface->getScopeName();
133
1/2
✓ Branch 72 → 73 taken 167 times.
✗ Branch 72 → 133 not taken.
167 matchScope->copyChildScope(oldScopeName, newScopeName);
134
1/2
✓ Branch 73 → 74 taken 167 times.
✗ Branch 73 → 133 not taken.
167 substantiatedInterface->scope = matchScope->getChildScope(newScopeName);
135
1/2
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 76 taken 167 times.
167 assert(substantiatedInterface->scope != nullptr);
136 167 substantiatedInterface->scope->isGenericScope = false;
137
138 // Attach the template types to the new interface entry
139
1/2
✓ Branch 76 → 77 taken 167 times.
✗ Branch 76 → 132 not taken.
167 QualType entryType = substantiatedInterface->entry->getQualType()
140
2/4
✓ Branch 77 → 78 taken 167 times.
✗ Branch 77 → 131 not taken.
✓ Branch 78 → 79 taken 167 times.
✗ Branch 78 → 129 not taken.
334 .getWithTemplateTypes(substantiatedInterface->getTemplateTypes())
141
1/2
✓ Branch 79 → 80 taken 167 times.
✗ Branch 79 → 129 not taken.
167 .getWithBodyScope(substantiatedInterface->scope);
142
1/2
✓ Branch 81 → 82 taken 167 times.
✗ Branch 81 → 133 not taken.
167 substantiatedInterface->entry->updateType(entryType, true);
143
144 // Add to matched interfaces
145
1/2
✓ Branch 82 → 83 taken 167 times.
✗ Branch 82 → 133 not taken.
167 matches.push_back(substantiatedInterface);
146
3/3
✓ Branch 88 → 89 taken 167 times.
✓ Branch 88 → 91 taken 22 times.
✓ Branch 88 → 92 taken 111 times.
300 }
147 300 }
148
149 // If no matches were found, return a nullptr
150
1/2
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 103 taken 300 times.
300 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 300 times.
300 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 300 times.
✗ Branch 114 → 156 not taken.
300 lookupCache[cacheKey] = matches.front();
159
160 300 return matches.front();
161 300 }
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 300 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 300 bool InterfaceManager::matchTemplateTypes(Interface &candidate, const QualTypeList &reqTemplateTypes, TypeMapping &typeMapping,
182 const ASTNode *node) {
183 // Check if the number of types match
184 300 const size_t typeCount = reqTemplateTypes.size();
185
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 300 times.
300 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 878 TypeMatcher::ResolverFct genericTypeResolver = [&](const std::string &genericTypeName) {
190 278 return getGenericTypeOfCandidateByName(candidate, genericTypeName);
191 300 };
192
193 // Loop over all template types
194
2/2
✓ Branch 17 → 8 taken 278 times.
✓ Branch 17 → 18 taken 300 times.
578 for (size_t i = 0; i < typeCount; i++) {
195
1/2
✓ Branch 8 → 9 taken 278 times.
✗ Branch 8 → 22 not taken.
278 const QualType &reqType = reqTemplateTypes.at(i);
196
1/2
✓ Branch 9 → 10 taken 278 times.
✗ Branch 9 → 22 not taken.
278 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 278 times.
✗ Branch 10 → 22 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 278 times.
278 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 278 times.
✗ Branch 13 → 22 not taken.
✓ Branch 14 → 15 taken 278 times.
✗ Branch 14 → 16 not taken.
278 if (candidateType.hasAnyGenericParts())
204
1/2
✓ Branch 15 → 16 taken 278 times.
✗ Branch 15 → 22 not taken.
278 TypeMatcher::substantiateTypeWithTypeMapping(candidateType, typeMapping, node);
205 }
206
207 300 return true;
208 300 }
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 300 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 784 times.
✓ Branch 34 → 35 taken 300 times.
1084 for (Function *method : candidate.methods) {
220 // Skip methods, that are already fully substantiated
221
3/4
✓ Branch 5 → 6 taken 784 times.
✗ Branch 5 → 37 not taken.
✓ Branch 6 → 7 taken 346 times.
✓ Branch 6 → 8 taken 438 times.
784 if (method->isFullySubstantiated())
222 346 continue;
223
224 // Substantiate return type
225
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 438 times.
438 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 438 times.
440 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 300 }
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 278 const GenericType *InterfaceManager::getGenericTypeOfCandidateByName(const Interface &candidate,
243 const std::string &templateTypeName) {
244
1/2
✓ Branch 14 → 4 taken 278 times.
✗ Branch 14 → 15 not taken.
278 for (const GenericType &templateType : candidate.templateTypes) {
245
2/4
✓ Branch 5 → 6 taken 278 times.
✗ Branch 5 → 17 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 278 times.
278 if (!templateType.is(TY_GENERIC))
246 continue;
247
2/4
✓ Branch 8 → 9 taken 278 times.
✗ Branch 8 → 17 not taken.
✓ Branch 10 → 11 taken 278 times.
✗ Branch 10 → 12 not taken.
278 if (templateType.getSubType() == templateTypeName)
248 278 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 1631 uint64_t InterfaceManager::getCacheKey(const Scope *scope, const std::string &name, const QualTypeList &templateTypes) {
262 1631 uint64_t hash = 0;
263 1631 hashCombine64(hash, hashPointer(scope));
264 1631 hashCombine64(hash, std::hash<std::string>{}(name));
265 1631 hashCombine64(hash, hashVector(templateTypes));
266 1631 return hash;
267 }
268
269 /**
270 * Clear the lookup cache
271 */
272 448 void InterfaceManager::cleanup() {
273 448 lookupCache.clear();
274 448 lookupCacheHits = 0;
275 448 lookupCacheMisses = 0;
276 448 }
277
278 /**
279 * Dump usage statistics for the lookup cache
280 */
281 206 std::string InterfaceManager::dumpLookupCacheStatistics() {
282
1/2
✓ Branch 2 → 3 taken 206 times.
✗ Branch 2 → 22 not taken.
206 std::stringstream stats;
283
2/4
✓ Branch 3 → 4 taken 206 times.
✗ Branch 3 → 20 not taken.
✓ Branch 4 → 5 taken 206 times.
✗ Branch 4 → 20 not taken.
206 stats << "InterfaceManager lookup cache statistics:" << std::endl;
284
3/6
✓ Branch 5 → 6 taken 206 times.
✗ Branch 5 → 20 not taken.
✓ Branch 7 → 8 taken 206 times.
✗ Branch 7 → 20 not taken.
✓ Branch 8 → 9 taken 206 times.
✗ Branch 8 → 20 not taken.
206 stats << " lookup cache entries: " << lookupCache.size() << std::endl;
285
3/6
✓ Branch 9 → 10 taken 206 times.
✗ Branch 9 → 20 not taken.
✓ Branch 10 → 11 taken 206 times.
✗ Branch 10 → 20 not taken.
✓ Branch 11 → 12 taken 206 times.
✗ Branch 11 → 20 not taken.
206 stats << " lookup cache hits: " << lookupCacheHits << std::endl;
286
3/6
✓ Branch 12 → 13 taken 206 times.
✗ Branch 12 → 20 not taken.
✓ Branch 13 → 14 taken 206 times.
✗ Branch 13 → 20 not taken.
✓ Branch 14 → 15 taken 206 times.
✗ Branch 14 → 20 not taken.
206 stats << " lookup cache misses: " << lookupCacheMisses << std::endl;
287
1/2
✓ Branch 15 → 16 taken 206 times.
✗ Branch 15 → 20 not taken.
412 return stats.str();
288 206 }
289
290 } // namespace spice::compiler
291