GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 94.0% 358 / 0 / 381
Functions: 88.0% 22 / 0 / 25
Branches: 59.8% 487 / 0 / 814

src/typechecker/TypeCheckerImplicit.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "TypeChecker.h"
4
5 #include <unordered_set>
6
7 #include <SourceFile.h>
8 #include <ast/ASTBuilder.h>
9 #include <ast/ASTNodes.h>
10 #include <global/GlobalResourceManager.h>
11 #include <model/GenericType.h>
12 #include <model/Struct.h>
13 #include <symboltablebuilder/Scope.h>
14 #include <symboltablebuilder/SymbolTableBuilder.h>
15 #include <typechecker/FunctionManager.h>
16 #include <typechecker/TypeMatcher.h>
17
18 namespace spice::compiler {
19
20 static const char *const FCT_NAME_DEALLOC = "sDealloc";
21
22 /**
23 * Check whether the given struct and all of its by-value struct fields are manifested, transitively, and have already
24 * decided their own implicit default members. While a circular import is still being prepared, a by-value struct field
25 * may (transitively) reference a struct that is not manifested yet; generating implicit special members for such a
26 * struct recurses through isTriviallyConstructible and would dereference a null struct lookup. A genuine infinite-size
27 * containment cycle among these structs is reported separately by the infinite-size check during struct preparation,
28 * so a containment cycle here is treated as manifested.
29 *
30 * A field struct can also be manifested (the Struct object exists) but not decided yet: a generic struct like
31 * Result<T> can be substantiated for T = Url while checking a function body during Url's own file's one-shot sweep
32 * (TypeChecker::visitEntry), before that sweep has reached Url's struct declaration and decided that Url needs a
33 * dtor/copy ctor. Deciding Result<Url> right then would see Url as trivial (no dtor found yet) and permanently skip
34 * generating Result<Url>'s dtor, since createImplicitDefaultMembers only ever runs once per manifestation. Requiring
35 * the field struct to have decided its own members first defers Result<Url> the same way as the circular-import case
36 * above, to be picked up again by the fallback sweep in visitStructDefCheck once Url has decided.
37 */
38 25759 static bool structFullyManifested(const Struct *spiceStruct, const ASTNode *node, std::unordered_set<const Scope *> &visited) {
39
2/2
✓ Branch 30 → 4 taken 62207 times.
✓ Branch 30 → 31 taken 25519 times.
113485 for (const QualType &fieldType : spiceStruct->fieldTypes) {
40
3/4
✓ Branch 6 → 7 taken 62207 times.
✗ Branch 6 → 34 not taken.
✓ Branch 7 → 8 taken 48324 times.
✓ Branch 7 → 9 taken 13883 times.
62207 if (!fieldType.is(TY_STRUCT))
41 48324 continue;
42
1/2
✓ Branch 9 → 10 taken 13883 times.
✗ Branch 9 → 34 not taken.
13883 const Struct *fieldStruct = fieldType.getStruct(node);
43
4/4
✓ Branch 10 → 11 taken 13881 times.
✓ Branch 10 → 12 taken 2 times.
✓ Branch 11 → 12 taken 238 times.
✓ Branch 11 → 13 taken 13643 times.
13883 if (fieldStruct == nullptr || !fieldStruct->implicitDefaultMembersDecided)
44 240 return false;
45 // Recurse into each field struct once. A containment cycle (already visited) is fine here - it is reported
46 // separately as an infinite-size error. Leaf structs never touch the set, so the common case stays allocation-free.
47
6/10
✓ Branch 13 → 14 taken 13643 times.
✗ Branch 13 → 34 not taken.
✓ Branch 14 → 15 taken 13336 times.
✓ Branch 14 → 18 taken 307 times.
✓ Branch 15 → 16 taken 13336 times.
✗ Branch 15 → 34 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 13336 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 13643 times.
13643 if (visited.insert(fieldStruct->scope).second && !structFullyManifested(fieldStruct, node, visited))
48 return false;
49 }
50 25519 return true;
51 }
52
53 /**
54 * Decide which compiler-generated default members (ctor, copy ctor, move ctor, dtor) the given struct manifestation
55 * needs and create them.
56 *
57 * This decision depends on the concrete field types, so it has to be taken once per manifestation and not once per
58 * struct declaration: whether e.g. HashEntry<K, V> needs a dtor is unanswerable while K and V are still generic, but
59 * well-defined for HashEntry<int, String>. It is therefore taken as soon as a manifestation comes into existence -
60 * either by the one-shot sweep over the manifestations that already exist when a source file is prepared
61 * (TypeChecker::visitEntry), or, for manifestations that are substantiated later on, right at the point where
62 * StructManager::match creates them. Deciding any later would be too late for all the reactive lookups that ask a
63 * struct for its default members (FunctionManager::match/::lookup, doScopeCleanup, ...), since a function body is
64 * type-checked exactly once and would not pick up a dtor that appears afterward.
65 *
66 * The default move ctor is decided about only when the caller asks for it. A manifestation that is substantiated after
67 * its file was prepared does not get one, which keeps it at the behavior it had before this decision was taken per
68 * manifestation at all. Synthesizing one would require the move ctor of each struct-typed field to be resolvable for
69 * the concrete manifestation, which the raw manifestation scan behind it (FunctionManager::findMoveCtor) cannot do for
70 * a generic field type - it returns the generic preset that the manifestation scope inherited. Without a move ctor,
71 * moving such a struct falls back to copying it, which is correct, just less efficient.
72 *
73 * @param spiceStruct Struct manifestation to decide the default members for
74 * @param node AST node for error messages
75 * @param withMoveCtor Also decide about the default move ctor
76 */
77 32611 void TypeChecker::createImplicitDefaultMembers(Struct &spiceStruct, const ASTNode *node, bool withMoveCtor) {
78 // Take the decision only once per manifestation
79
2/2
✓ Branch 2 → 3 taken 20426 times.
✓ Branch 2 → 4 taken 12185 times.
32611 if (spiceStruct.implicitDefaultMembersDecided)
80 20478 return;
81
82 12185 GlobalResourceManager &resourceManager = spiceStruct.scope->sourceFile->resourceManager;
83
84 // Skip while a by-value struct field is (transitively) not manifested yet, or has not decided its own implicit
85 // default members yet (circular import still in progress, or a sibling struct's one-shot sweep has not reached it
86 // yet - see structFullyManifested). Relying solely on the fallback sweep in visitStructDefCheck to pick this back up
87 // can be too late: that sweep only runs during the type-checker POST pass, by which point some other function body
88 // elsewhere may already have looked up (and missed) this manifestation's dtor for good, since a function body is
89 // type-checked exactly once. Queue the manifestation instead, so it gets retried the moment something else's
90 // decision completes (see drainPendingImplicitDefaultMemberDecisions below) - typically still within the same PRE
91 // pass that first substantiated it.
92 12185 std::unordered_set<const Scope *> visitedScopes;
93
3/4
✓ Branch 5 → 6 taken 12185 times.
✗ Branch 5 → 33 not taken.
✓ Branch 6 → 7 taken 52 times.
✓ Branch 6 → 22 taken 12133 times.
12185 if (!structFullyManifested(&spiceStruct, node, visitedScopes)) {
94 52 std::vector<std::pair<Struct *, bool>> &pending = resourceManager.pendingStructDefaultMemberDecisions;
95 12 const auto pred = [&](const auto &entry) { return entry.first == &spiceStruct; };
96
1/2
✓ Branch 7 → 8 taken 52 times.
✗ Branch 7 → 32 not taken.
52 const auto it = std::ranges::find_if(pending, pred);
97
1/2
✓ Branch 15 → 16 taken 52 times.
✗ Branch 15 → 18 not taken.
104 if (it == pending.end())
98
1/2
✓ Branch 16 → 17 taken 52 times.
✗ Branch 16 → 31 not taken.
52 pending.emplace_back(&spiceStruct, withMoveCtor);
99 else
100 it->second |= withMoveCtor; // Upgrade to deciding the move ctor too if any deferred caller asked for it
101 52 return;
102 }
103
104
1/2
✓ Branch 22 → 23 taken 12133 times.
✗ Branch 22 → 33 not taken.
12133 decideDefaultMembers(spiceStruct, withMoveCtor);
105
106 // This struct just became decided - drain the pending list so it can pick up anything that was blocked on it, directly or
107 // transitively (a chain of Result<Outer<Inner>>-shaped dependencies).
108
1/2
✓ Branch 23 → 24 taken 12133 times.
✗ Branch 23 → 33 not taken.
12133 drainPendingImplicitDefaultMemberDecisions(resourceManager);
109
2/2
✓ Branch 26 → 27 taken 12133 times.
✓ Branch 26 → 29 taken 52 times.
12185 }
110
111 /**
112 * Set a struct manifestation's default members as decided and create whichever of them (ctor, copy ctor, move ctor,
113 * dtor) it actually needs. Only called once structFullyManifested has confirmed the struct is ready.
114 *
115 * @param spiceStruct Struct manifestation to decide the default members for
116 * @param withMoveCtor Also decide about the default move ctor
117 */
118 12183 void TypeChecker::decideDefaultMembers(Struct &spiceStruct, bool withMoveCtor) {
119 12183 spiceStruct.implicitDefaultMembersDecided = true;
120
121 12183 createDefaultCtorIfRequired(spiceStruct, spiceStruct.scope);
122 12183 createDefaultCopyCtorIfRequired(spiceStruct, spiceStruct.scope);
123
2/2
✓ Branch 4 → 5 taken 7511 times.
✓ Branch 4 → 6 taken 4672 times.
12183 if (withMoveCtor)
124 7511 createDefaultMoveCtorIfRequired(spiceStruct, spiceStruct.scope);
125 12183 createDefaultDtorIfRequired(spiceStruct, spiceStruct.scope);
126 12183 }
127
128 /**
129 * Drain every struct manifestation that createImplicitDefaultMembers previously had to defer because a by-value
130 * field struct had not decided its own default members yet. Called whenever a struct's decision completes, since
131 * that can unblock others - directly (a struct that was waiting on exactly this one) or transitively (a chain of
132 * Result<Outer<Inner>>-shaped dependencies).
133 *
134 * This calls decideDefaultMembers directly rather than createImplicitDefaultMembers, so draining one entry can never
135 * recurse back into this function - no reentrancy guard is needed, the loop below just keeps going until a full pass
136 * makes no further progress.
137 *
138 * @param resourceManager Resource manager that owns the pending list for this compilation
139 */
140 12133 void TypeChecker::drainPendingImplicitDefaultMemberDecisions(GlobalResourceManager &resourceManager) {
141 bool progressed;
142
2/2
✓ Branch 34 → 3 taken 50 times.
✓ Branch 34 → 35 taken 12133 times.
12183 do {
143 12183 progressed = false;
144 // Snapshot before iterating: deciding one pending manifestation can append newly-deferred entries to the very
145 // list being iterated (a struct that unblocks can itself be a field of another, still-blocked struct).
146
1/2
✓ Branch 3 → 4 taken 12183 times.
✗ Branch 3 → 43 not taken.
12183 const std::vector<std::pair<Struct *, bool>> snapshot = resourceManager.pendingStructDefaultMemberDecisions;
147
2/2
✓ Branch 32 → 6 taken 288 times.
✓ Branch 32 → 33 taken 12183 times.
24942 for (const auto &[pendingStruct, pendingWithMoveCtor] : snapshot) {
148
2/2
✓ Branch 10 → 11 taken 50 times.
✓ Branch 10 → 12 taken 238 times.
288 if (pendingStruct->implicitDefaultMembersDecided)
149 238 continue; // Already resolved by an earlier entry in this same pass
150
151 238 std::unordered_set<const Scope *> visitedScopes;
152
3/4
✓ Branch 13 → 14 taken 238 times.
✗ Branch 13 → 37 not taken.
✓ Branch 14 → 15 taken 188 times.
✓ Branch 14 → 16 taken 50 times.
238 if (!structFullyManifested(pendingStruct, pendingStruct->declNode, visitedScopes))
153 188 continue; // Still blocked on something else; leave it queued
154
155
1/2
✓ Branch 16 → 17 taken 50 times.
✗ Branch 16 → 37 not taken.
50 decideDefaultMembers(*pendingStruct, pendingWithMoveCtor);
156 50 progressed = true;
157
2/2
✓ Branch 19 → 20 taken 50 times.
✓ Branch 19 → 22 taken 188 times.
238 }
158 12183 } while (progressed);
159
160 // Drop everything that got resolved. Whatever remains is still genuinely blocked (e.g. a circular import still in
161 // progress) and stays queued to be retried again the next time some other struct's decision completes.
162 226 const auto pred = [](const std::pair<Struct *, bool> &entry) { return entry.first->implicitDefaultMembersDecided; };
163
1/2
✓ Branch 35 → 36 taken 12133 times.
✗ Branch 35 → 44 not taken.
12133 std::erase_if(resourceManager.pendingStructDefaultMemberDecisions, pred);
164 12133 }
165
166 /**
167 * Create a default struct method
168 * Checks if the given struct scope already has a user-defined constructor and creates a default one if not.
169 *
170 * @param spiceStruct Struct instance
171 * @param entryName Name of the symbol table entry
172 * @param name Name of the method to create
173 * @param params Parameter types of the method
174 */
175 8082 void TypeChecker::createDefaultStructMethod(const Struct &spiceStruct, const std::string &entryName, const std::string &name,
176 const ParamList &params) {
177 8082 Scope *structScope = spiceStruct.scope;
178 8082 SourceFile *sourceFile = structScope->sourceFile;
179 8082 ASTNode *node = spiceStruct.declNode;
180 8082 const SymbolTableEntry *structEntry = spiceStruct.entry;
181
1/2
✓ Branch 2 → 3 taken 8082 times.
✗ Branch 2 → 99 not taken.
8082 const QualType &structType = structEntry->getQualType();
182
3/6
✓ Branch 3 → 4 taken 8082 times.
✗ Branch 3 → 70 not taken.
✓ Branch 4 → 5 taken 8082 times.
✗ Branch 4 → 70 not taken.
✓ Branch 5 → 6 taken 8082 times.
✗ Branch 5 → 68 not taken.
8082 const std::string fqFctName = structType.getSubType() + MEMBER_ACCESS_TOKEN + name;
183
184 // Procedure type
185
1/2
✓ Branch 7 → 8 taken 8082 times.
✗ Branch 7 → 97 not taken.
8082 QualType procedureType(TY_PROCEDURE);
186
1/2
✓ Branch 8 → 9 taken 8082 times.
✗ Branch 8 → 97 not taken.
8082 procedureType.makePublic(); // Always public
187
188 // Insert symbol for function into the symbol table
189
1/2
✓ Branch 9 → 10 taken 8082 times.
✗ Branch 9 → 97 not taken.
8082 SymbolTableEntry *procEntry = structScope->insert(entryName, structEntry->declNode);
190
1/2
✓ Branch 12 → 13 taken 8082 times.
✗ Branch 12 → 97 not taken.
8082 procEntry->updateType(procedureType, true);
191
192 // Add to external name registry. Only the generic preset does this: the registry is keyed by the plain struct name,
193 // so an entry added for one manifestation would be indistinguishable from a user-defined member and would wrongly
194 // suppress the default member of every sibling manifestation (e.g. HashEntry<int, String> vs. HashEntry<int, int>).
195 // Manifestation-level default members are only ever looked up through their struct scope anyway.
196
3/4
✓ Branch 13 → 14 taken 8082 times.
✗ Branch 13 → 97 not taken.
✓ Branch 14 → 15 taken 7138 times.
✓ Branch 14 → 16 taken 944 times.
8082 if (!spiceStruct.isGenericSubstantiation())
197
1/2
✓ Branch 15 → 16 taken 7138 times.
✗ Branch 15 → 97 not taken.
7138 sourceFile->addNameRegistryEntry(fqFctName, TY_PROCEDURE, procEntry, structScope, true);
198
199 // Create the default method
200
1/2
✓ Branch 16 → 17 taken 8082 times.
✗ Branch 16 → 97 not taken.
8082 const std::vector<GenericType> templateTypes = spiceStruct.templateTypes;
201
1/2
✓ Branch 17 → 18 taken 8082 times.
✗ Branch 17 → 95 not taken.
8082 const QualType returnType(TY_DYN);
202
4/8
✓ Branch 18 → 19 taken 8082 times.
✗ Branch 18 → 79 not taken.
✓ Branch 19 → 20 taken 8082 times.
✗ Branch 19 → 76 not taken.
✓ Branch 20 → 21 taken 8082 times.
✗ Branch 20 → 73 not taken.
✓ Branch 21 → 22 taken 8082 times.
✗ Branch 21 → 71 not taken.
8082 Function defaultMethod(name, procEntry, structType, returnType, params, templateTypes, structEntry->declNode);
203 8082 defaultMethod.implicitDefault = true;
204
205 // Fill type mapping for the case, that the template type list contains non-generic types. Only struct, interface
206 // and enum types carry a sub type usable as a key here; a non-generic primitive template argument (e.g. the 'int'
207 // in BlockAllocator<int>) has no sub type, so getSubType() would assert on it - skip those.
208
2/2
✓ Branch 48 → 27 taken 2258 times.
✓ Branch 48 → 49 taken 8082 times.
18422 for (const GenericType &templateType : templateTypes)
209
8/10
✓ Branch 29 → 30 taken 2258 times.
✗ Branch 29 → 80 not taken.
✓ Branch 30 → 31 taken 1256 times.
✓ Branch 30 → 34 taken 1002 times.
✓ Branch 31 → 32 taken 1256 times.
✗ Branch 31 → 80 not taken.
✓ Branch 32 → 33 taken 608 times.
✓ Branch 32 → 34 taken 648 times.
✓ Branch 35 → 36 taken 608 times.
✓ Branch 35 → 39 taken 1650 times.
2258 if (!templateType.is(TY_GENERIC) && templateType.isOneOf({TY_STRUCT, TY_INTERFACE, TY_ENUM}))
210
2/4
✓ Branch 36 → 37 taken 608 times.
✗ Branch 36 → 81 not taken.
✓ Branch 37 → 38 taken 608 times.
✗ Branch 37 → 81 not taken.
608 defaultMethod.typeMapping[templateType.getSubType()] = static_cast<QualType>(templateType);
211
212 // Create function scope
213
2/4
✓ Branch 49 → 50 taken 8082 times.
✗ Branch 49 → 85 not taken.
✓ Branch 50 → 51 taken 8082 times.
✗ Branch 50 → 83 not taken.
8082 Scope *procScope = structScope->createChildScope(defaultMethod.getScopeName(), ScopeType::FUNC_PROC_BODY, &node->codeLoc);
214 8082 defaultMethod.bodyScope = procScope;
215
216 // Create 'this' symbol in the function scope
217
1/2
✓ Branch 54 → 55 taken 8082 times.
✗ Branch 54 → 88 not taken.
24246 SymbolTableEntry *thisEntry = procScope->insert(THIS_VARIABLE_NAME, node);
218
2/4
✓ Branch 60 → 61 taken 8082 times.
✗ Branch 60 → 92 not taken.
✓ Branch 61 → 62 taken 8082 times.
✗ Branch 61 → 92 not taken.
8082 thisEntry->updateType(structType.toPtr(node), true);
219 8082 thisEntry->used = true; // Always set to used to not print warnings for non-existing code
220
221 // Hand it off to the function manager to register the function
222
2/4
✓ Branch 62 → 63 taken 8082 times.
✗ Branch 62 → 93 not taken.
✓ Branch 63 → 64 taken 8082 times.
✗ Branch 63 → 93 not taken.
8082 FunctionManager::insert(structScope, defaultMethod, structEntry->declNode->getFctManifestations(name));
223
224 // The body of a default member is not prepared here, but in visitStructDefCheck, which runs over all manifestations
225 // of the struct. For a manifestation that was substantiated after its file was already checked, that run is long
226 // over, so ask for another one - otherwise the member would end up without a body preamble, and the IR generator
227 // would e.g. not find the copy ctor of a field it is supposed to copy. During the prepare stage the flag is set
228 // anyway, so this is a no-op there.
229 8082 sourceFile->reVisitRequested = true;
230 8082 }
231
232 /**
233 * Checks if the given struct scope already has a user-defined constructor and creates a default one if not.
234 *
235 * For generating a default ctor, the following conditions need to be met:
236 * - No user-defined constructors (incl. copy/move ctors)
237 *
238 * @param spiceStruct Struct instance
239 * @param structScope Scope of the struct
240 */
241 12183 void TypeChecker::createDefaultCtorIfRequired(const Struct &spiceStruct, Scope *structScope) {
242
1/2
✓ Branch 2 → 3 taken 12183 times.
✗ Branch 2 → 4 not taken.
12183 const auto node = spice_pointer_cast<StructDefNode *>(spiceStruct.declNode);
243
2/4
✓ Branch 11 → 12 taken 12183 times.
✗ Branch 11 → 14 not taken.
✓ Branch 12 → 13 taken 12183 times.
✗ Branch 12 → 14 not taken.
12183 assert(structScope != nullptr && structScope->type == ScopeType::STRUCT);
244 12183 const SourceFile *sourceFile = structScope->sourceFile;
245
246 // Abort if the struct already has a constructor. The name registry is the authoritative source here: it is filled by
247 // the symbol table builder, so it already knows about every user-defined ctor, even one that is declared further down
248 // in the file and therefore has not been registered in the struct scope yet. It also covers a ctor that the generic
249 // preset received, which every manifestation inherits through its copied scope. The scope scan on top of it only
250 // guards against creating a second ctor into a scope that visibly has one already.
251 12183 const SymbolTableEntry *structEntry = spiceStruct.entry;
252
1/2
✓ Branch 15 → 16 taken 12183 times.
✗ Branch 15 → 142 not taken.
12183 const QualType &structType = structEntry->getQualType();
253
3/6
✓ Branch 16 → 17 taken 12183 times.
✗ Branch 16 → 115 not taken.
✓ Branch 17 → 18 taken 12183 times.
✗ Branch 17 → 115 not taken.
✓ Branch 18 → 19 taken 12183 times.
✗ Branch 18 → 113 not taken.
12183 const std::string fqFctName = structType.getSubType() + MEMBER_ACCESS_TOKEN + CTOR_FUNCTION_NAME;
254
7/10
✓ Branch 20 → 21 taken 12183 times.
✗ Branch 20 → 140 not taken.
✓ Branch 21 → 22 taken 1973 times.
✓ Branch 21 → 24 taken 10210 times.
✓ Branch 22 → 23 taken 1973 times.
✗ Branch 22 → 140 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1973 times.
✓ Branch 26 → 27 taken 10210 times.
✓ Branch 26 → 28 taken 1973 times.
12183 if (sourceFile->getNameRegistryEntry(fqFctName) || FunctionManager::hasAnyCtor(structScope))
255 10210 return;
256
257 // Check if we have fields, that require us to do anything in the ctor
258
1/2
✓ Branch 28 → 29 taken 1973 times.
✗ Branch 28 → 140 not taken.
1973 const size_t fieldCount = structScope->getFieldCount();
259 1973 bool hasFieldsWithDefaultValue = false;
260 1973 bool hasFieldsToConstruct = false;
261
2/2
✓ Branch 91 → 30 taken 5021 times.
✓ Branch 91 → 92 taken 1895 times.
6916 for (size_t i = 0; i < fieldCount; i++) {
262
1/2
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 33 taken 5021 times.
5021 const SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
263
2/4
✓ Branch 36 → 37 taken 5021 times.
✗ Branch 36 → 39 not taken.
✓ Branch 37 → 38 taken 5021 times.
✗ Branch 37 → 39 not taken.
5021 assert(fieldSymbol != nullptr && fieldSymbol->declNode != nullptr);
264
265
1/2
✓ Branch 40 → 41 taken 5021 times.
✗ Branch 40 → 128 not taken.
5021 QualType fieldType = fieldSymbol->getQualType();
266
7/8
✓ Branch 41 → 42 taken 5021 times.
✗ Branch 41 → 128 not taken.
✓ Branch 42 → 43 taken 335 times.
✓ Branch 42 → 46 taken 4686 times.
✓ Branch 44 → 45 taken 26 times.
✓ Branch 44 → 46 taken 309 times.
✓ Branch 47 → 48 taken 26 times.
✓ Branch 47 → 49 taken 4995 times.
5021 if (fieldType.hasAnyGenericParts() && !spiceStruct.typeMapping.empty())
267
1/2
✓ Branch 48 → 49 taken 26 times.
✗ Branch 48 → 128 not taken.
26 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, spiceStruct.typeMapping, fieldSymbol->declNode);
268
269 // Abort if we have a field, that is a reference
270
3/4
✓ Branch 49 → 50 taken 5021 times.
✗ Branch 49 → 128 not taken.
✓ Branch 50 → 51 taken 16 times.
✓ Branch 50 → 52 taken 5005 times.
5021 if (fieldType.isRef())
271 78 return;
272
273
3/4
✓ Branch 52 → 53 taken 5005 times.
✗ Branch 52 → 54 not taken.
✓ Branch 55 → 56 taken 4897 times.
✓ Branch 55 → 57 taken 108 times.
5005 if (const auto fieldNode = dynamic_cast<FieldNode *>(fieldSymbol->declNode)) {
274 4897 hasFieldsWithDefaultValue |= fieldNode->defaultValue != nullptr;
275 } else {
276
2/4
✓ Branch 57 → 58 taken 108 times.
✗ Branch 57 → 59 not taken.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 64 taken 108 times.
216 assert(is<DataTypeNode *>(fieldSymbol->declNode));
277 }
278
279
3/4
✓ Branch 64 → 65 taken 5005 times.
✗ Branch 64 → 128 not taken.
✓ Branch 65 → 66 taken 295 times.
✓ Branch 65 → 89 taken 4710 times.
5005 if (fieldType.is(TY_STRUCT)) {
280
1/2
✓ Branch 66 → 67 taken 295 times.
✗ Branch 66 → 128 not taken.
295 Scope *bodyScope = fieldType.getBodyScope();
281 // Check if we are required to call a ctor
282
1/2
✓ Branch 67 → 68 taken 295 times.
✗ Branch 67 → 128 not taken.
295 const bool isCtorCallRequired = !fieldType.isTriviallyConstructible(node);
283 // While the outer struct is still a generic preset, a field type that is itself generic (e.g. Inner<T, bool>,
284 // the internal field of Outer<T>) cannot be matched to a concrete ctor yet. Since a default ctor takes no
285 // arguments, matching it against the still-generic 'this' type would partially substantiate a bogus
286 // manifestation (e.g. Inner<K, bool>, with K left generic) that is wrongly treated as fully substantiated
287 // later on and crashes name mangling during IR generation (see #1255). In that case we only record that the
288 // field has a default ctor via a direct scan; the concrete ctor is matched later, per manifestation, in
289 // createCtorBodyPreamble.
290
3/4
✓ Branch 68 → 69 taken 295 times.
✗ Branch 68 → 128 not taken.
✓ Branch 69 → 70 taken 22 times.
✓ Branch 69 → 75 taken 273 times.
295 if (fieldType.hasAnyGenericParts()) {
291
1/2
✓ Branch 70 → 71 taken 22 times.
✗ Branch 70 → 128 not taken.
22 const bool hasDefaultCtor = FunctionManager::hasDefaultCtor(bodyScope);
292
1/4
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 74 taken 22 times.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
22 if (!hasDefaultCtor && isCtorCallRequired)
293 return;
294 22 hasFieldsToConstruct |= hasDefaultCtor;
295 } else {
296 // Lookup ctor function
297
2/4
✓ Branch 79 → 80 taken 273 times.
✗ Branch 79 → 118 not taken.
✓ Branch 80 → 81 taken 273 times.
✗ Branch 80 → 116 not taken.
819 const Function *ctorFct = FunctionManager::match(bodyScope, CTOR_FUNCTION_NAME, fieldType, {}, {}, true, node);
298 // If we are required to construct, but no constructor is found, we can't generate a default ctor for the outer struct
299
4/4
✓ Branch 85 → 86 taken 94 times.
✓ Branch 85 → 88 taken 179 times.
✓ Branch 86 → 87 taken 62 times.
✓ Branch 86 → 88 taken 32 times.
273 if (!ctorFct && isCtorCallRequired)
300 62 return;
301 211 hasFieldsToConstruct |= ctorFct != nullptr;
302 }
303 }
304 }
305
306 // If we don't have any fields, that require us to do anything in the ctor, we can skip it
307
6/6
✓ Branch 92 → 93 taken 1757 times.
✓ Branch 92 → 96 taken 138 times.
✓ Branch 93 → 94 taken 1629 times.
✓ Branch 93 → 96 taken 128 times.
✓ Branch 94 → 95 taken 1563 times.
✓ Branch 94 → 96 taken 66 times.
1895 if (!hasFieldsWithDefaultValue && !hasFieldsToConstruct && !node->emitVTable)
308 1563 return;
309
310 // Create the default ctor function
311
1/2
✓ Branch 96 → 97 taken 332 times.
✗ Branch 96 → 140 not taken.
332 const std::string entryName = Function::getSymbolTableEntryNameDefaultCtor(node->codeLoc);
312
2/4
✓ Branch 100 → 101 taken 332 times.
✗ Branch 100 → 131 not taken.
✓ Branch 101 → 102 taken 332 times.
✗ Branch 101 → 129 not taken.
996 createDefaultStructMethod(spiceStruct, entryName, CTOR_FUNCTION_NAME, {});
313
2/2
✓ Branch 108 → 109 taken 332 times.
✓ Branch 108 → 111 taken 11851 times.
12183 }
314
315 /**
316 * Checks if the given struct scope already has a user-defined constructor and creates a default one if not.
317 *
318 * For generating a default copy ctor, the following conditions need to be met:
319 * - No user-defined copy ctor
320 *
321 * @param spiceStruct Struct instance
322 * @param structScope Scope of the struct
323 */
324 12183 void TypeChecker::createDefaultCopyCtorIfRequired(const Struct &spiceStruct, Scope *structScope) {
325
1/2
✓ Branch 2 → 3 taken 12183 times.
✗ Branch 2 → 4 not taken.
12183 const auto node = spice_pointer_cast<const StructDefNode *>(spiceStruct.declNode);
326
2/4
✓ Branch 11 → 12 taken 12183 times.
✗ Branch 11 → 14 not taken.
✓ Branch 12 → 13 taken 12183 times.
✗ Branch 12 → 14 not taken.
12183 assert(structScope != nullptr && structScope->type == ScopeType::STRUCT);
327
328 // Abort if the struct already has a user-defined copy constructor
329
1/2
✓ Branch 15 → 16 taken 12183 times.
✗ Branch 15 → 131 not taken.
12183 const QualType structType = spiceStruct.entry->getQualType();
330
3/4
✓ Branch 16 → 17 taken 12183 times.
✗ Branch 16 → 131 not taken.
✓ Branch 17 → 18 taken 3084 times.
✓ Branch 17 → 19 taken 9099 times.
12183 if (FunctionManager::hasCopyCtor(structScope))
331 7982 return;
332
333 // Check if we have fields, that require us to do anything in the ctor
334
1/2
✓ Branch 19 → 20 taken 9099 times.
✗ Branch 19 → 131 not taken.
9099 const size_t fieldCount = structScope->getFieldCount();
335 9099 bool copyCtorRequired = false;
336
2/2
✓ Branch 77 → 21 taken 17898 times.
✓ Branch 77 → 78 taken 9099 times.
26997 for (size_t i = 0; i < fieldCount; i++) {
337
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 17898 times.
17898 const SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
338
2/4
✓ Branch 27 → 28 taken 17898 times.
✗ Branch 27 → 30 not taken.
✓ Branch 28 → 29 taken 17898 times.
✗ Branch 28 → 30 not taken.
17898 assert(fieldSymbol != nullptr && fieldSymbol->declNode != nullptr);
339
340
1/2
✓ Branch 31 → 32 taken 17898 times.
✗ Branch 31 → 116 not taken.
17898 QualType fieldType = fieldSymbol->getQualType();
341
7/8
✓ Branch 32 → 33 taken 17898 times.
✗ Branch 32 → 116 not taken.
✓ Branch 33 → 34 taken 2327 times.
✓ Branch 33 → 37 taken 15571 times.
✓ Branch 35 → 36 taken 262 times.
✓ Branch 35 → 37 taken 2065 times.
✓ Branch 38 → 39 taken 262 times.
✓ Branch 38 → 40 taken 17636 times.
17898 if (fieldType.hasAnyGenericParts() && !spiceStruct.typeMapping.empty())
342
1/2
✓ Branch 39 → 40 taken 262 times.
✗ Branch 39 → 116 not taken.
262 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, spiceStruct.typeMapping, fieldSymbol->declNode);
343
344 // If the field is of type struct, check if this struct has a copy ctor that has to be called
345
3/4
✓ Branch 40 → 41 taken 17898 times.
✗ Branch 40 → 116 not taken.
✓ Branch 41 → 42 taken 5212 times.
✓ Branch 41 → 70 taken 12686 times.
17898 if (fieldType.is(TY_STRUCT)) {
346 // A field whose copy is non-trivial forces the outer struct to have a copy ctor as well.
347
1/2
✓ Branch 42 → 43 taken 5212 times.
✗ Branch 42 → 116 not taken.
5212 const bool fieldRequiresCopyCtor = !fieldType.isTriviallyCopyable(node);
348 // While the outer struct is still a generic preset, a field type that is itself generic (e.g. Inner<T, bool>,
349 // the internal field of Outer<T>) cannot be matched to a concrete copy ctor yet. Matching it via the
350 // substantiating FunctionManager::match() below can partially substantiate a bogus manifestation (e.g.
351 // Inner<K, bool>, with K left generic) that is wrongly treated as fully substantiated later on and crashes
352 // name mangling during IR generation (see #1255). This can happen even though the copy ctor call carries an
353 // argument of the same (still-generic) field type, whenever the field struct's own generic type name
354 // collides with the outer struct's generic type name (e.g. both named 'V', as in Set<V> wrapping
355 // RedBlackTree<K,V>). The concrete copy ctor is matched later, per manifestation, in
356 // createCopyCtorBodyPreamble, and its result here is not otherwise consulted while the field type is still
357 // generic (fieldRequiresCopyCtor already reflects copy-ctor existence via the non-substantiating
358 // isTriviallyCopyable check above) - so skip the substantiating match entirely in that case.
359
3/4
✓ Branch 43 → 44 taken 5212 times.
✗ Branch 43 → 116 not taken.
✓ Branch 44 → 45 taken 4938 times.
✓ Branch 44 → 69 taken 274 times.
5212 if (!fieldType.hasAnyGenericParts()) {
360
1/2
✓ Branch 45 → 46 taken 4938 times.
✗ Branch 45 → 115 not taken.
4938 Scope *bodyScope = fieldType.getBodyScope();
361
2/4
✓ Branch 46 → 47 taken 4938 times.
✗ Branch 46 → 102 not taken.
✓ Branch 50 → 51 taken 4938 times.
✗ Branch 50 → 98 not taken.
9876 const ArgList args = {{fieldType.toConstRef(node), false /* we always have the field as storage */}};
362
2/4
✓ Branch 55 → 56 taken 4938 times.
✗ Branch 55 → 106 not taken.
✓ Branch 56 → 57 taken 4938 times.
✗ Branch 56 → 104 not taken.
14814 const Function *ctorFct = FunctionManager::match(bodyScope, CTOR_FUNCTION_NAME, fieldType, args, {}, true, node);
363 // If the field requires a copy ctor, but we proved none exists, we cannot synthesize one for the outer struct.
364
3/4
✓ Branch 60 → 61 taken 1225 times.
✓ Branch 60 → 63 taken 3713 times.
✗ Branch 61 → 62 not taken.
✓ Branch 61 → 63 taken 1225 times.
4938 if (!ctorFct && fieldRequiresCopyCtor)
365 return;
366
1/2
✓ Branch 65 → 66 taken 4938 times.
✗ Branch 65 → 68 not taken.
4938 }
367 5212 copyCtorRequired |= fieldRequiresCopyCtor;
368 }
369
370 // If we have an owning heap pointer, we need to do a memcpy of the heap storage and therefore need a default copy ctor
371
3/4
✓ Branch 70 → 71 taken 17898 times.
✗ Branch 70 → 116 not taken.
✓ Branch 71 → 72 taken 315 times.
✓ Branch 71 → 76 taken 17583 times.
17898 if (fieldType.isHeap()) {
372
2/4
✓ Branch 72 → 73 taken 315 times.
✗ Branch 72 → 116 not taken.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 315 times.
315 assert(fieldType.isPtr());
373 315 copyCtorRequired = true;
374 }
375 }
376
377 // If we don't have any fields, that require us to do anything in the copy ctor, we can skip it
378
4/4
✓ Branch 78 → 79 taken 5485 times.
✓ Branch 78 → 81 taken 3614 times.
✓ Branch 79 → 80 taken 4898 times.
✓ Branch 79 → 81 taken 587 times.
9099 if (!copyCtorRequired && !node->emitVTable)
379 4898 return;
380
381 // Create the default copy ctor function
382
1/2
✓ Branch 81 → 82 taken 4201 times.
✗ Branch 81 → 131 not taken.
4201 const std::string entryName = Function::getSymbolTableEntryNameDefaultCopyCtor(node->codeLoc);
383
2/4
✓ Branch 82 → 83 taken 4201 times.
✗ Branch 82 → 120 not taken.
✓ Branch 85 → 86 taken 4201 times.
✗ Branch 85 → 117 not taken.
12603 const ParamList paramTypes = {{structType.toConstRef(node), false}};
384
2/4
✓ Branch 89 → 90 taken 4201 times.
✗ Branch 89 → 123 not taken.
✓ Branch 90 → 91 taken 4201 times.
✗ Branch 90 → 121 not taken.
4201 createDefaultStructMethod(spiceStruct, entryName, CTOR_FUNCTION_NAME, paramTypes);
385 4201 }
386
387 /**
388 * Checks if the given struct scope already has a user-defined move constructor and creates a default one if not.
389 *
390 * For generating a default move ctor, the following conditions need to be met:
391 * - No user-defined move ctor
392 * - At least one field requires non-trivial moving (heap pointer or struct field with move ctor)
393 *
394 * @param spiceStruct Struct instance
395 * @param structScope Scope of the struct
396 */
397 7511 void TypeChecker::createDefaultMoveCtorIfRequired(const Struct &spiceStruct, Scope *structScope) {
398
1/2
✓ Branch 2 → 3 taken 7511 times.
✗ Branch 2 → 4 not taken.
7511 const auto node = spice_pointer_cast<const StructDefNode *>(spiceStruct.declNode);
399
2/4
✓ Branch 11 → 12 taken 7511 times.
✗ Branch 11 → 14 not taken.
✓ Branch 12 → 13 taken 7511 times.
✗ Branch 12 → 14 not taken.
7511 assert(structScope != nullptr && structScope->type == ScopeType::STRUCT);
400
401 // Skip generic struct presets - we only synthesize a default move ctor for fully substantiated structs.
402 // Reason: each manifestation gets its own scope (deep-copied from the generic), and the implicit move ctor
403 // body's preamble writes to field lifecycle state via bodyScope->parent. If we created the move ctor on the
404 // generic struct, the deep-copied Function in the manifestation's scope would still carry a bodyScope pointer
405 // into the generic struct's scope, causing the preamble to update fields in the wrong scope.
406
3/4
✓ Branch 15 → 16 taken 7511 times.
✗ Branch 15 → 95 not taken.
✓ Branch 16 → 17 taken 1474 times.
✓ Branch 16 → 18 taken 6037 times.
7511 if (!spiceStruct.isFullySubstantiated())
407 7491 return;
408
409 // Abort if the struct already has a user-defined move constructor.
410 // We can't just call FunctionManager::lookup with a non-const ref arg here, since the lookup permits
411 // const-param-to-non-const-arg matching ("constify") and would return the copy ctor (if one exists) as
412 // a false positive. Instead, check the function manifestations directly for a single-self-non-const-ref ctor.
413
3/4
✓ Branch 18 → 19 taken 6037 times.
✗ Branch 18 → 95 not taken.
✓ Branch 19 → 20 taken 8 times.
✓ Branch 19 → 21 taken 6029 times.
6037 if (FunctionManager::hasMoveCtor(structScope))
414 8 return;
415
416 // Abort if the struct has a user-defined copy ctor. Reason: in Spice the move-vs-copy tie-breaker picks the
417 // non-const-ref (move) candidate for non-const lvalue arguments, so silently auto-generating a move ctor on
418 // top of a user-defined copy ctor would change the behavior of existing `T b = T(a)` call sites (a's heap
419 // contents would be stolen instead of deep-copied). Users that want a default move ctor in addition to their
420 // own copy ctor can write an empty `p T.ctor(T& other) {}` to opt in. We still synthesize the default move
421 // ctor when only the auto-generated copy ctor exists (heap-owning struct with no user ctors) - the user did
422 // not write any binding semantics in that case, so picking move for non-const lvalues is fine.
423
3/4
✓ Branch 21 → 22 taken 6029 times.
✗ Branch 21 → 95 not taken.
✓ Branch 22 → 23 taken 512 times.
✓ Branch 22 → 24 taken 5517 times.
6029 if (FunctionManager::hasUserCopyCtor(structScope))
424 512 return;
425
1/2
✓ Branch 24 → 25 taken 5517 times.
✗ Branch 24 → 95 not taken.
5517 const QualType structType = spiceStruct.entry->getQualType();
426
427 // Check if we have fields, that require us to do anything in the move ctor
428
1/2
✓ Branch 25 → 26 taken 5517 times.
✗ Branch 25 → 95 not taken.
5517 const size_t fieldCount = structScope->getFieldCount();
429 5517 bool moveCtorRequired = false;
430
2/2
✓ Branch 58 → 27 taken 10157 times.
✓ Branch 58 → 59 taken 5517 times.
15674 for (size_t i = 0; i < fieldCount; i++) {
431
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 30 taken 10157 times.
10157 const SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
432
2/4
✓ Branch 33 → 34 taken 10157 times.
✗ Branch 33 → 36 not taken.
✓ Branch 34 → 35 taken 10157 times.
✗ Branch 34 → 36 not taken.
10157 assert(fieldSymbol != nullptr && fieldSymbol->declNode != nullptr);
433
434
1/2
✓ Branch 37 → 38 taken 10157 times.
✗ Branch 37 → 79 not taken.
10157 QualType fieldType = fieldSymbol->getQualType();
435
3/8
✓ Branch 38 → 39 taken 10157 times.
✗ Branch 38 → 79 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 43 taken 10157 times.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 10157 times.
10157 if (fieldType.hasAnyGenericParts() && !spiceStruct.typeMapping.empty())
436 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, spiceStruct.typeMapping, fieldSymbol->declNode);
437
438 // If the field is of type struct, check whether the field's struct has its own move ctor. We use
439 // findMoveCtor (a direct scan of the manifestations) rather than FunctionManager::match here for two
440 // reasons: (1) match permits const-param-to-non-const-arg "constify" matching and would return the
441 // field's copy ctor as a false positive, and (2) match populates the lookup cache, which would inflate
442 // the per-struct cache-miss counts even for structs that have nothing to do with move ctors.
443
3/4
✓ Branch 46 → 47 taken 10157 times.
✗ Branch 46 → 79 not taken.
✓ Branch 47 → 48 taken 3525 times.
✓ Branch 47 → 51 taken 6632 times.
10157 if (fieldType.is(TY_STRUCT)) {
444
1/2
✓ Branch 48 → 49 taken 3525 times.
✗ Branch 48 → 79 not taken.
3525 Scope *bodyScope = fieldType.getBodyScope();
445
1/2
✓ Branch 49 → 50 taken 3525 times.
✗ Branch 49 → 79 not taken.
3525 moveCtorRequired |= FunctionManager::findMoveCtor(bodyScope) != nullptr;
446 }
447
448 // If we have an owning heap pointer, we transfer ownership of the heap storage
449
3/4
✓ Branch 51 → 52 taken 10157 times.
✗ Branch 51 → 79 not taken.
✓ Branch 52 → 53 taken 14 times.
✓ Branch 52 → 57 taken 10143 times.
10157 if (fieldType.isHeap()) {
450
2/4
✓ Branch 53 → 54 taken 14 times.
✗ Branch 53 → 79 not taken.
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 56 taken 14 times.
14 assert(fieldType.isPtr());
451 14 moveCtorRequired = true;
452 }
453 }
454
455 // If we don't have any fields that require us to do anything special in the move ctor, we can skip it.
456 // Unlike the copy ctor we do NOT generate a default move ctor just because the struct emits a vtable - the
457 // vtable pointer is part of the struct layout and gets shallow-copied along with everything else, no
458 // special move handling needed. Users that want to move a vtable-bearing struct fall back to the default
459 // copy ctor.
460
2/2
✓ Branch 59 → 60 taken 5497 times.
✓ Branch 59 → 61 taken 20 times.
5517 if (!moveCtorRequired)
461 5497 return;
462
463 // Create the default move ctor function
464
1/2
✓ Branch 61 → 62 taken 20 times.
✗ Branch 61 → 95 not taken.
20 const std::string entryName = Function::getSymbolTableEntryNameDefaultMoveCtor(node->codeLoc);
465
3/6
✓ Branch 62 → 63 taken 20 times.
✗ Branch 62 → 83 not taken.
✓ Branch 63 → 64 taken 20 times.
✗ Branch 63 → 83 not taken.
✓ Branch 66 → 67 taken 20 times.
✗ Branch 66 → 80 not taken.
60 const ParamList paramTypes = {{structType.toNonConst().toRef(node), false}};
466
2/4
✓ Branch 70 → 71 taken 20 times.
✗ Branch 70 → 87 not taken.
✓ Branch 71 → 72 taken 20 times.
✗ Branch 71 → 85 not taken.
20 createDefaultStructMethod(spiceStruct, entryName, CTOR_FUNCTION_NAME, paramTypes);
467 20 }
468
469 /**
470 * Checks if the given struct scope already has a user-defined destructor and creates a default one if not.
471 *
472 * For generating a default dtor, the following conditions need to be met:
473 * - No user-defined dtor
474 *
475 * @param spiceStruct Struct instance
476 * @param structScope Scope of the struct
477 */
478 12183 void TypeChecker::createDefaultDtorIfRequired(const Struct &spiceStruct, Scope *structScope) {
479 12183 const ASTNode *node = spiceStruct.declNode;
480 12183 const SourceFile *sourceFile = structScope->sourceFile;
481
2/4
✓ Branch 2 → 3 taken 12183 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 12183 times.
✗ Branch 3 → 5 not taken.
12183 assert(structScope != nullptr && structScope->type == ScopeType::STRUCT);
482
483 // Abort if the struct already has a destructor. See createDefaultCtorIfRequired for why the name registry is asked
484 // first and the struct scope only on top of it.
485 12183 const SymbolTableEntry *structEntry = spiceStruct.entry;
486
1/2
✓ Branch 6 → 7 taken 12183 times.
✗ Branch 6 → 110 not taken.
12183 const QualType &structType = structEntry->getQualType();
487
3/6
✓ Branch 7 → 8 taken 12183 times.
✗ Branch 7 → 83 not taken.
✓ Branch 8 → 9 taken 12183 times.
✗ Branch 8 → 83 not taken.
✓ Branch 9 → 10 taken 12183 times.
✗ Branch 9 → 81 not taken.
12183 const std::string fqFctName = structType.getSubType() + MEMBER_ACCESS_TOKEN + DTOR_FUNCTION_NAME;
488
7/10
✓ Branch 11 → 12 taken 12183 times.
✗ Branch 11 → 108 not taken.
✓ Branch 12 → 13 taken 9908 times.
✓ Branch 12 → 15 taken 2275 times.
✓ Branch 13 → 14 taken 9908 times.
✗ Branch 13 → 108 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 9908 times.
✓ Branch 17 → 18 taken 2275 times.
✓ Branch 17 → 19 taken 9908 times.
12183 if (sourceFile->getNameRegistryEntry(fqFctName) || FunctionManager::hasDtor(structScope))
489 2275 return;
490
491 // Check we have field types, that require use to do anything in the destructor
492
1/2
✓ Branch 19 → 20 taken 9908 times.
✗ Branch 19 → 108 not taken.
9908 const size_t fieldCount = structScope->getFieldCount();
493 9908 bool hasFieldsToDeAllocate = false;
494 9908 bool hasFieldsToDestruct = false;
495
2/2
✓ Branch 60 → 21 taken 20889 times.
✓ Branch 60 → 61 taken 9908 times.
30797 for (size_t i = 0; i < fieldCount; i++) {
496
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 20889 times.
20889 const SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
497
2/4
✓ Branch 27 → 28 taken 20889 times.
✗ Branch 27 → 30 not taken.
✓ Branch 28 → 29 taken 20889 times.
✗ Branch 28 → 30 not taken.
20889 assert(fieldSymbol != nullptr && fieldSymbol->declNode != nullptr);
498
499
1/2
✓ Branch 31 → 32 taken 20889 times.
✗ Branch 31 → 96 not taken.
20889 QualType fieldType = fieldSymbol->getQualType();
500
7/8
✓ Branch 32 → 33 taken 20889 times.
✗ Branch 32 → 96 not taken.
✓ Branch 33 → 34 taken 2490 times.
✓ Branch 33 → 37 taken 18399 times.
✓ Branch 35 → 36 taken 321 times.
✓ Branch 35 → 37 taken 2169 times.
✓ Branch 38 → 39 taken 321 times.
✓ Branch 38 → 40 taken 20568 times.
20889 if (fieldType.hasAnyGenericParts() && !spiceStruct.typeMapping.empty())
501
1/2
✓ Branch 39 → 40 taken 321 times.
✗ Branch 39 → 96 not taken.
321 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, spiceStruct.typeMapping, fieldSymbol->declNode);
502
503
1/2
✓ Branch 40 → 41 taken 20889 times.
✗ Branch 40 → 96 not taken.
20889 hasFieldsToDeAllocate |= fieldType.needsDeAllocation();
504
3/4
✓ Branch 41 → 42 taken 20889 times.
✗ Branch 41 → 96 not taken.
✓ Branch 42 → 43 taken 5354 times.
✓ Branch 42 → 59 taken 15535 times.
20889 if (fieldType.is(TY_STRUCT)) {
505
1/2
✓ Branch 43 → 44 taken 5354 times.
✗ Branch 43 → 96 not taken.
5354 Scope *fieldScope = fieldType.getBodyScope();
506 // While the outer struct is still a generic preset, a field type that is itself generic (e.g.
507 // RedBlackTree<V, bool>, the internal field of Set<V>) cannot be matched to a concrete dtor yet. Since a
508 // dtor takes no arguments, matching it against the still-generic 'this' type would partially substantiate a
509 // bogus manifestation (e.g. RedBlackTree<K, bool>, with K left generic) that is wrongly treated as fully
510 // substantiated later on and crashes name mangling during IR generation. In that case we only record that
511 // the field has a dtor via a direct scan; the concrete dtor is matched later, per manifestation, in
512 // createDtorBodyPreamble.
513
3/4
✓ Branch 44 → 45 taken 5354 times.
✗ Branch 44 → 96 not taken.
✓ Branch 45 → 46 taken 271 times.
✓ Branch 45 → 48 taken 5083 times.
5354 if (fieldType.hasAnyGenericParts()) {
514
1/2
✓ Branch 46 → 47 taken 271 times.
✗ Branch 46 → 96 not taken.
271 hasFieldsToDestruct |= FunctionManager::hasDtor(fieldScope);
515 } else {
516 // Lookup dtor function
517
2/4
✓ Branch 52 → 53 taken 5083 times.
✗ Branch 52 → 86 not taken.
✓ Branch 53 → 54 taken 5083 times.
✗ Branch 53 → 84 not taken.
15249 const Function *dtorFct = FunctionManager::match(fieldScope, DTOR_FUNCTION_NAME, fieldType, {}, {}, true, node);
518 5083 hasFieldsToDestruct |= dtorFct != nullptr;
519
1/2
✓ Branch 58 → 59 taken 5083 times.
✗ Branch 58 → 96 not taken.
5083 requestRevisitIfRequired(dtorFct);
520 }
521 }
522 }
523
524 // If we don't have any fields, that require us to do anything in the dtor, we can skip it
525
4/4
✓ Branch 61 → 62 taken 9541 times.
✓ Branch 61 → 64 taken 367 times.
✓ Branch 62 → 63 taken 6379 times.
✓ Branch 62 → 64 taken 3162 times.
9908 if (!hasFieldsToDeAllocate && !hasFieldsToDestruct)
526 6379 return;
527
528 // Create the default dtor function. The memory runtime that the generated body needs for its de-allocations is not
529 // requested here, but only when that body is prepared (see createDtorBodyPreamble) - see the comment there.
530
1/2
✓ Branch 64 → 65 taken 3529 times.
✗ Branch 64 → 108 not taken.
3529 const std::string entryName = Function::getSymbolTableEntryNameDefaultDtor(node->codeLoc);
531
2/4
✓ Branch 68 → 69 taken 3529 times.
✗ Branch 68 → 99 not taken.
✓ Branch 69 → 70 taken 3529 times.
✗ Branch 69 → 97 not taken.
10587 createDefaultStructMethod(spiceStruct, entryName, DTOR_FUNCTION_NAME, {});
532
2/2
✓ Branch 76 → 77 taken 3529 times.
✓ Branch 76 → 79 taken 8654 times.
12183 }
533
534 /**
535 * Prepare the generation of the ctor body preamble. This preamble is used to initialize the VTable, construct or initialize
536 * fields.
537 */
538 13534 void TypeChecker::createCtorBodyPreamble(const Scope *bodyScope) const {
539 // Retrieve struct scope
540 13534 Scope *structScope = bodyScope->parent;
541
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 13534 times.
13534 assert(structScope != nullptr);
542
543 13534 const size_t fieldCount = structScope->getFieldCount();
544
2/2
✓ Branch 60 → 6 taken 33014 times.
✓ Branch 60 → 61 taken 13534 times.
46548 for (size_t i = 0; i < fieldCount; i++) {
545
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 33014 times.
33014 SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
546
3/6
✓ Branch 12 → 13 taken 33014 times.
✗ Branch 12 → 16 not taken.
✓ Branch 13 → 14 taken 33014 times.
✗ Branch 13 → 76 not taken.
✓ Branch 14 → 15 taken 33014 times.
✗ Branch 14 → 16 not taken.
33014 assert(fieldSymbol != nullptr && fieldSymbol->isField());
547
2/2
✓ Branch 17 → 18 taken 2794 times.
✓ Branch 17 → 19 taken 30220 times.
33014 if (fieldSymbol->isImplicitField)
548 7738 continue;
549
550
1/2
✓ Branch 19 → 20 taken 30220 times.
✗ Branch 19 → 76 not taken.
30220 QualType fieldType = fieldSymbol->getQualType();
551
2/4
✓ Branch 20 → 21 taken 30220 times.
✗ Branch 20 → 76 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 30220 times.
30220 if (fieldType.hasAnyGenericParts())
552 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, typeMapping, fieldSymbol->declNode);
553
554 // A reference field has no default value to construct - it can only be bound by an explicit assignment in the
555 // ctor body (e.g. Pair<K, V&>'s 'this.second = second;'), and that assignment is what should mark the field's
556 // lifecycle as initialized (see the isDecl / initial-field-ref-assign logic in TypeChecker::visitAssignExpr and
557 // IRGenerator::doAssignment). Marking it initialized here, before that binding assignment runs, would make the
558 // compiler treat the binding as a reassignment through the reference instead of the initial binding - illegal
559 // for a const reference field, and wrong even for a non-const one.
560
3/4
✓ Branch 23 → 24 taken 30220 times.
✗ Branch 23 → 76 not taken.
✓ Branch 24 → 25 taken 2023 times.
✓ Branch 24 → 26 taken 28197 times.
30220 if (fieldType.isRef())
561 2023 continue;
562
563
3/4
✓ Branch 26 → 27 taken 28197 times.
✗ Branch 26 → 76 not taken.
✓ Branch 27 → 28 taken 7218 times.
✓ Branch 27 → 56 taken 20979 times.
28197 if (fieldType.is(TY_STRUCT)) {
564
1/2
✓ Branch 28 → 29 taken 7218 times.
✗ Branch 28 → 30 not taken.
7218 const auto fieldNode = spice_pointer_cast<const FieldNode *>(fieldSymbol->declNode);
565 // Match ctor function, create the concrete manifestation and set it to used
566
1/2
✓ Branch 37 → 38 taken 7218 times.
✗ Branch 37 → 76 not taken.
7218 Scope *matchScope = fieldType.getBodyScope();
567
2/4
✓ Branch 42 → 43 taken 7218 times.
✗ Branch 42 → 64 not taken.
✓ Branch 43 → 44 taken 7218 times.
✗ Branch 43 → 62 not taken.
21654 const Function *spiceFunc = FunctionManager::match(matchScope, CTOR_FUNCTION_NAME, fieldType, {}, {}, false, fieldNode);
568
2/2
✓ Branch 48 → 49 taken 4177 times.
✓ Branch 48 → 53 taken 3041 times.
7218 if (spiceFunc != nullptr)
569
3/6
✓ Branch 49 → 50 taken 4177 times.
✗ Branch 49 → 74 not taken.
✓ Branch 50 → 51 taken 4177 times.
✗ Branch 50 → 74 not taken.
✓ Branch 51 → 52 taken 4177 times.
✗ Branch 51 → 74 not taken.
4177 fieldSymbol->updateType(fieldType.getWithBodyScope(spiceFunc->thisType.getBodyScope()), true);
570
3/4
✓ Branch 53 → 54 taken 3041 times.
✗ Branch 53 → 76 not taken.
✓ Branch 54 → 55 taken 2921 times.
✓ Branch 54 → 56 taken 120 times.
3041 else if (!fieldType.isTriviallyConstructible(fieldNode))
571 2921 continue;
572 }
573
574
1/2
✓ Branch 56 → 57 taken 25276 times.
✗ Branch 56 → 75 not taken.
25276 fieldSymbol->updateState(INITIALIZED, fieldSymbol->declNode);
575 }
576 13534 }
577
578 /**
579 * Prepare the generation of the copy ctor body preamble. This preamble is used to initialize the VTable, construct or initialize
580 * fields.
581 */
582 7406 void TypeChecker::createCopyCtorBodyPreamble(const Scope *bodyScope) const {
583 // Retrieve struct scope
584 7406 Scope *structScope = bodyScope->parent;
585
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 7406 times.
7406 assert(structScope != nullptr);
586
587 7406 const size_t fieldCount = structScope->getFieldCount();
588
2/2
✓ Branch 67 → 6 taken 11830 times.
✓ Branch 67 → 68 taken 7406 times.
19236 for (size_t i = 0; i < fieldCount; i++) {
589
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 11830 times.
11830 SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
590
3/6
✓ Branch 12 → 13 taken 11830 times.
✗ Branch 12 → 16 not taken.
✓ Branch 13 → 14 taken 11830 times.
✗ Branch 13 → 89 not taken.
✓ Branch 14 → 15 taken 11830 times.
✗ Branch 14 → 16 not taken.
11830 assert(fieldSymbol != nullptr && fieldSymbol->isField());
591
2/2
✓ Branch 17 → 18 taken 352 times.
✓ Branch 17 → 19 taken 11478 times.
11830 if (fieldSymbol->isImplicitField)
592 352 continue;
593
594
1/2
✓ Branch 19 → 20 taken 11478 times.
✗ Branch 19 → 89 not taken.
11478 QualType fieldType = fieldSymbol->getQualType();
595
2/4
✓ Branch 20 → 21 taken 11478 times.
✗ Branch 20 → 89 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 11478 times.
11478 if (fieldType.hasAnyGenericParts())
596 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, typeMapping, fieldSymbol->declNode);
597
598
3/4
✓ Branch 23 → 24 taken 11478 times.
✗ Branch 23 → 89 not taken.
✓ Branch 24 → 25 taken 8542 times.
✓ Branch 24 → 63 taken 2936 times.
11478 if (fieldType.is(TY_STRUCT)) {
599
1/2
✓ Branch 25 → 26 taken 8542 times.
✗ Branch 25 → 27 not taken.
8542 const auto fieldNode = spice_pointer_cast<const FieldNode *>(fieldSymbol->declNode);
600 // Match ctor function, create the concrete manifestation and set it to used
601
1/2
✓ Branch 34 → 35 taken 8542 times.
✗ Branch 34 → 87 not taken.
8542 Scope *matchScope = fieldType.getBodyScope();
602
2/4
✓ Branch 35 → 36 taken 8542 times.
✗ Branch 35 → 73 not taken.
✓ Branch 39 → 40 taken 8542 times.
✗ Branch 39 → 69 not taken.
17084 const ArgList args = {{fieldType.toConstRef(fieldNode), false /* we always have the field as storage */}};
603
2/4
✓ Branch 44 → 45 taken 8542 times.
✗ Branch 44 → 77 not taken.
✓ Branch 45 → 46 taken 8542 times.
✗ Branch 45 → 75 not taken.
25626 const Function *copyCtorFct = FunctionManager::match(matchScope, CTOR_FUNCTION_NAME, fieldType, args, {}, false, fieldNode);
604
2/2
✓ Branch 49 → 50 taken 7728 times.
✓ Branch 49 → 54 taken 814 times.
8542 if (copyCtorFct != nullptr)
605
3/6
✓ Branch 50 → 51 taken 7728 times.
✗ Branch 50 → 84 not taken.
✓ Branch 51 → 52 taken 7728 times.
✗ Branch 51 → 84 not taken.
✓ Branch 52 → 53 taken 7728 times.
✗ Branch 52 → 84 not taken.
7728 fieldSymbol->updateType(fieldType.getWithBodyScope(copyCtorFct->thisType.getBodyScope()), true);
606
2/4
✓ Branch 54 → 55 taken 814 times.
✗ Branch 54 → 85 not taken.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 57 taken 814 times.
814 else if (!fieldType.isTriviallyCopyable(fieldNode))
607 continue;
608
1/2
✓ Branch 59 → 60 taken 8542 times.
✗ Branch 59 → 62 not taken.
8542 }
609
610
1/2
✓ Branch 63 → 64 taken 11478 times.
✗ Branch 63 → 88 not taken.
11478 fieldSymbol->updateState(INITIALIZED, fieldSymbol->declNode);
611 }
612 7406 }
613
614 /**
615 * Prepare the generation of the move ctor body preamble. This preamble is used to initialize the VTable, move-construct or
616 * shallow-copy fields and transfer ownership of heap allocations.
617 */
618 34 void TypeChecker::createMoveCtorBodyPreamble(const Scope *bodyScope) const {
619 // Retrieve struct scope
620 34 Scope *structScope = bodyScope->parent;
621
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 34 times.
34 assert(structScope != nullptr);
622
623 34 const size_t fieldCount = structScope->getFieldCount();
624
2/2
✓ Branch 38 → 6 taken 42 times.
✓ Branch 38 → 39 taken 34 times.
76 for (size_t i = 0; i < fieldCount; i++) {
625
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 42 times.
42 SymbolTableEntry *fieldSymbol = structScope->lookupField(i);
626
3/6
✓ Branch 12 → 13 taken 42 times.
✗ Branch 12 → 16 not taken.
✓ Branch 13 → 14 taken 42 times.
✗ Branch 13 → 42 not taken.
✓ Branch 14 → 15 taken 42 times.
✗ Branch 14 → 16 not taken.
42 assert(fieldSymbol != nullptr && fieldSymbol->isField());
627
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 42 times.
42 if (fieldSymbol->isImplicitField)
628 continue;
629
630
1/2
✓ Branch 19 → 20 taken 42 times.
✗ Branch 19 → 42 not taken.
42 QualType fieldType = fieldSymbol->getQualType();
631
2/4
✓ Branch 20 → 21 taken 42 times.
✗ Branch 20 → 42 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 42 times.
42 if (fieldType.hasAnyGenericParts())
632 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, typeMapping, fieldSymbol->declNode);
633
634
3/4
✓ Branch 23 → 24 taken 42 times.
✗ Branch 23 → 42 not taken.
✓ Branch 24 → 25 taken 12 times.
✓ Branch 24 → 34 taken 30 times.
42 if (fieldType.is(TY_STRUCT)) {
635 // Find a move ctor on the field's struct. We use findMoveCtor rather than FunctionManager::match to
636 // avoid a constify-based false positive returning the copy ctor. findMoveCtor doesn't mark the
637 // function as used, so do that explicitly to ensure the inner move ctor's body is actually emitted.
638
1/2
✓ Branch 25 → 26 taken 12 times.
✗ Branch 25 → 42 not taken.
12 Scope *matchScope = fieldType.getBodyScope();
639
2/4
✓ Branch 26 → 27 taken 12 times.
✗ Branch 26 → 42 not taken.
✓ Branch 27 → 28 taken 12 times.
✗ Branch 27 → 34 not taken.
12 if (Function *moveCtorFct = FunctionManager::findMoveCtor(matchScope)) {
640 12 moveCtorFct->used = true;
641
1/2
✓ Branch 28 → 29 taken 12 times.
✗ Branch 28 → 30 not taken.
12 if (moveCtorFct->entry)
642 12 moveCtorFct->entry->used = true;
643
3/6
✓ Branch 30 → 31 taken 12 times.
✗ Branch 30 → 40 not taken.
✓ Branch 31 → 32 taken 12 times.
✗ Branch 31 → 40 not taken.
✓ Branch 32 → 33 taken 12 times.
✗ Branch 32 → 40 not taken.
12 fieldSymbol->updateType(fieldType.getWithBodyScope(moveCtorFct->thisType.getBodyScope()), true);
644 }
645 }
646
647
1/2
✓ Branch 34 → 35 taken 42 times.
✗ Branch 34 → 41 not taken.
42 fieldSymbol->updateState(INITIALIZED, fieldSymbol->declNode);
648 }
649 34 }
650
651 /**
652 * Prepare the generation of the dtor body preamble. This preamble is used to destruct all fields and to free all heap fields.
653 *
654 * @param bodyScope Body scope of the dtor
655 * @param node Struct definition node for error messages
656 */
657 7261 void TypeChecker::createDtorBodyPreamble(const Scope *bodyScope, const ASTNode *node) const {
658 // Retrieve struct scope
659 7261 Scope *structScope = bodyScope->parent;
660
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 7261 times.
7261 assert(structScope != nullptr);
661
662 7261 const size_t fieldCount = structScope->getFieldCount();
663 7261 bool hasFieldsToDeAllocate = false;
664
2/2
✓ Branch 50 → 6 taken 12304 times.
✓ Branch 50 → 51 taken 7261 times.
19565 for (size_t i = 0; i < fieldCount; i++) {
665 12304 const size_t fieldIdx = fieldCount - 1 - i; // Destruct fields in reverse order
666
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 12304 times.
12304 const SymbolTableEntry *fieldSymbol = structScope->lookupField(fieldIdx);
667
3/6
✓ Branch 12 → 13 taken 12304 times.
✗ Branch 12 → 16 not taken.
✓ Branch 13 → 14 taken 12304 times.
✗ Branch 13 → 98 not taken.
✓ Branch 14 → 15 taken 12304 times.
✗ Branch 14 → 16 not taken.
12304 assert(fieldSymbol != nullptr && fieldSymbol->isField());
668
2/2
✓ Branch 17 → 18 taken 362 times.
✓ Branch 17 → 19 taken 11942 times.
12304 if (fieldSymbol->isImplicitField)
669 362 continue;
670
671
1/2
✓ Branch 19 → 20 taken 11942 times.
✗ Branch 19 → 98 not taken.
11942 QualType fieldType = fieldSymbol->getQualType();
672
2/4
✓ Branch 20 → 21 taken 11942 times.
✗ Branch 20 → 98 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 11942 times.
11942 if (fieldType.hasAnyGenericParts())
673 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, typeMapping, fieldSymbol->declNode);
674
675
1/2
✓ Branch 23 → 24 taken 11942 times.
✗ Branch 23 → 98 not taken.
11942 hasFieldsToDeAllocate |= fieldType.needsDeAllocation();
676
677
3/4
✓ Branch 24 → 25 taken 11942 times.
✗ Branch 24 → 98 not taken.
✓ Branch 25 → 26 taken 8648 times.
✓ Branch 25 → 47 taken 3294 times.
11942 if (fieldType.is(TY_STRUCT)) {
678
1/2
✓ Branch 26 → 27 taken 8648 times.
✗ Branch 26 → 28 not taken.
8648 const auto fieldNode = spice_pointer_cast<const FieldNode *>(fieldSymbol->declNode);
679 // Match ctor function, create the concrete manifestation and set it to used
680
1/2
✓ Branch 35 → 36 taken 8648 times.
✗ Branch 35 → 98 not taken.
8648 Scope *matchScope = fieldType.getBodyScope();
681
2/4
✓ Branch 40 → 41 taken 8648 times.
✗ Branch 40 → 88 not taken.
✓ Branch 41 → 42 taken 8648 times.
✗ Branch 41 → 86 not taken.
25944 FunctionManager::match(matchScope, DTOR_FUNCTION_NAME, fieldType, {}, {}, false, fieldNode);
682 }
683 }
684
685 // Request the memory runtime for the de-allocations that the generated body will contain, and mark its sDealloc as
686 // used so that a definition is emitted for it. This deliberately happens here and not where the dtor is created
687 // (createDefaultDtorIfRequired): a manifestation can come into existence while memory_rt.spice is itself still being
688 // pre-checked - sAlloc() returns Result<heap byte*>, so the very first Result manifestation is substantiated from
689 // memory_rt's own signature list. Asking that half-prepared file for sDealloc, which is declared further down in it,
690 // would come up empty. By the time a default member's body is prepared, every runtime module is fully pre-checked.
691 // The string runtime does not use sDealloc, but frees manually to avoid a circular dependency.
692 7261 SourceFile *structSourceFile = structScope->sourceFile;
693
5/6
✓ Branch 51 → 52 taken 765 times.
✓ Branch 51 → 57 taken 6496 times.
✓ Branch 55 → 56 taken 765 times.
✗ Branch 55 → 57 not taken.
✓ Branch 58 → 59 taken 765 times.
✓ Branch 58 → 85 taken 6496 times.
8026 if (hasFieldsToDeAllocate && !structSourceFile->isStringRT()) {
694
1/2
✓ Branch 59 → 60 taken 765 times.
✗ Branch 59 → 117 not taken.
765 const SourceFile *memoryRT = structSourceFile->requestRuntimeModule(MEMORY_RT);
695
1/2
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 765 times.
765 assert(memoryRT != nullptr);
696 765 Scope *matchScope = memoryRT->globalScope.get();
697 // Set dealloc function to used
698
1/2
✓ Branch 63 → 64 taken 765 times.
✗ Branch 63 → 117 not taken.
765 const QualType thisType(TY_DYN);
699
3/6
✓ Branch 64 → 65 taken 765 times.
✗ Branch 64 → 99 not taken.
✓ Branch 65 → 66 taken 765 times.
✗ Branch 65 → 99 not taken.
✓ Branch 66 → 67 taken 765 times.
✗ Branch 66 → 99 not taken.
765 QualType bytePtrRefType = QualType(TY_BYTE).toPtr(node).toRef(node);
700
1/2
✓ Branch 67 → 68 taken 765 times.
✗ Branch 67 → 117 not taken.
765 bytePtrRefType.makeHeap();
701
1/2
✓ Branch 71 → 72 taken 765 times.
✗ Branch 71 → 101 not taken.
1530 const ArgList args = {{bytePtrRefType, false /* we always have the field as storage */}};
702
2/4
✓ Branch 76 → 77 taken 765 times.
✗ Branch 76 → 108 not taken.
✓ Branch 77 → 78 taken 765 times.
✗ Branch 77 → 106 not taken.
2295 Function *deallocFct = FunctionManager::match(matchScope, FCT_NAME_DEALLOC, thisType, args, {}, true, node);
703
1/2
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 765 times.
765 assert(deallocFct != nullptr);
704 765 deallocFct->used = true;
705 765 }
706 7261 }
707
708 /**
709 * Prepare the generation of a call to a method of a given struct
710 *
711 * @param entry Symbol entry to use as 'this' pointer for the method call
712 * @param methodName Name of the method to call
713 * @param args Provided arguments by the caller
714 * @param node AST node
715 */
716 13431 Function *TypeChecker::implicitlyCallStructMethod(const SymbolTableEntry *entry, const std::string &methodName,
717 const ArgList &args, const ASTNode *node) const {
718
3/6
✓ Branch 2 → 3 taken 13431 times.
✗ Branch 2 → 9 not taken.
✓ Branch 3 → 4 taken 13431 times.
✗ Branch 3 → 9 not taken.
✓ Branch 4 → 5 taken 13431 times.
✗ Branch 4 → 9 not taken.
13431 const QualType thisType = entry->getQualType().removeReferenceWrapper().toNonConst();
719
1/2
✓ Branch 5 → 6 taken 13431 times.
✗ Branch 5 → 10 not taken.
26862 return implicitlyCallStructMethod(thisType, methodName, args, node);
720 }
721
722 /**
723 * Prepare the generation of a call to a method of a given struct
724 *
725 * @param thisType Struct type to call the method on
726 * @param methodName Name of the method to call
727 * @param args Provided arguments by the caller
728 * @param node AST node
729 */
730 15801 Function *TypeChecker::implicitlyCallStructMethod(QualType thisType, const std::string &methodName, const ArgList &args,
731 const ASTNode *node) const {
732
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 15801 times.
15801 assert(thisType.is(TY_STRUCT));
733 15801 Scope *matchScope = thisType.getBodyScope();
734
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 15801 times.
15801 assert(matchScope->type == ScopeType::STRUCT);
735
736 // Search for dtor
737
2/2
✓ Branch 9 → 10 taken 13562 times.
✓ Branch 9 → 12 taken 2239 times.
15801 if (matchScope->isImportedBy(rootScope))
738
1/2
✓ Branch 10 → 11 taken 13562 times.
✗ Branch 10 → 18 not taken.
13562 thisType = mapLocalTypeToImportedScopeType(matchScope, thisType);
739
1/2
✓ Branch 13 → 14 taken 15801 times.
✗ Branch 13 → 19 not taken.
15801 return FunctionManager::match(matchScope, methodName, thisType, args, {}, true, node);
740 }
741
742 /**
743 * Prepare the generation of a call to the copy ctor of a given struct
744 *
745 * @param entry Symbol entry to use as 'this' pointer for the copy ctor call
746 * @param node Current AST node
747 */
748 Function *TypeChecker::implicitlyCallStructCopyCtor(const SymbolTableEntry *entry, const ASTNode *node) const {
749 assert(entry != nullptr && entry->getQualType().is(TY_STRUCT));
750 return implicitlyCallStructCopyCtor(entry->getQualType(), node);
751 }
752
753 /**
754 * Prepare the generation of a call to the copy ctor of a given struct
755 *
756 * @param thisType Struct type to call the copy ctor on
757 * @param node Current AST node
758 */
759 2318 Function *TypeChecker::implicitlyCallStructCopyCtor(const QualType &thisType, const ASTNode *node) const {
760
2/4
✓ Branch 2 → 3 taken 2318 times.
✗ Branch 2 → 19 not taken.
✓ Branch 3 → 4 taken 2318 times.
✗ Branch 3 → 19 not taken.
2318 const QualType argType = thisType.removeReferenceWrapper().toConstRef(node);
761
1/2
✓ Branch 7 → 8 taken 2318 times.
✗ Branch 7 → 20 not taken.
6954 const ArgList args = {{argType, false /* we always have an entry here */}};
762
2/4
✓ Branch 11 → 12 taken 2318 times.
✗ Branch 11 → 27 not taken.
✓ Branch 12 → 13 taken 2318 times.
✗ Branch 12 → 25 not taken.
4636 return implicitlyCallStructMethod(thisType, CTOR_FUNCTION_NAME, args, node);
763 2318 }
764
765 /**
766 * Prepare the generation of a call to the move ctor of a given struct
767 *
768 * @param entry Symbol entry to use as 'this' pointer for the move ctor call
769 * @param node Current AST node
770 */
771 Function *TypeChecker::implicitlyCallStructMoveCtor(const SymbolTableEntry *entry, const ASTNode *node) const {
772 assert(entry != nullptr && entry->getQualType().is(TY_STRUCT));
773 return implicitlyCallStructMoveCtor(entry->getQualType(), node);
774 }
775
776 /**
777 * Prepare the generation of a call to the move ctor of a given struct
778 *
779 * @param thisType Struct type to call the move ctor on
780 * @param node Current AST node
781 */
782 Function *TypeChecker::implicitlyCallStructMoveCtor(const QualType &thisType, const ASTNode *node) const {
783 const QualType argType = thisType.removeReferenceWrapper().toNonConst().toRef(node);
784 const ArgList args = {{argType, false /* we always have an entry here */}};
785 return implicitlyCallStructMethod(thisType, CTOR_FUNCTION_NAME, args, node);
786 }
787
788 /**
789 * Prepare the generation of a call to the dtor of a given struct
790 *
791 * @param entry Symbol entry to use as 'this' pointer for the dtor call
792 * @param node StmtLstNode for the current scope
793 */
794 13431 void TypeChecker::implicitlyCallStructDtor(SymbolTableEntry *entry, StmtLstNode *node) const {
795 // Add the dtor to the stmt list node to call it later in codegen
796
4/6
✓ Branch 5 → 6 taken 13431 times.
✗ Branch 5 → 16 not taken.
✓ Branch 6 → 7 taken 13431 times.
✗ Branch 6 → 14 not taken.
✓ Branch 10 → 11 taken 11138 times.
✓ Branch 10 → 13 taken 2293 times.
40293 if (Function *dtor = implicitlyCallStructMethod(entry, DTOR_FUNCTION_NAME, {}, node))
797
2/4
✓ Branch 11 → 12 taken 11138 times.
✗ Branch 11 → 23 not taken.
✓ Branch 12 → 13 taken 11138 times.
✗ Branch 12 → 23 not taken.
11138 node->resourcesToCleanup.at(manIdx).dtorFunctionsToCall.emplace_back(entry, dtor);
798 13431 }
799
800 /**
801 * Prepare the generation of a call to the deallocate function for a heap-allocated variable
802 *
803 * @param node Current AST node for error messages
804 */
805 917 void TypeChecker::implicitlyCallDeallocate(const ASTNode *node) const {
806
1/2
✓ Branch 2 → 3 taken 917 times.
✗ Branch 2 → 46 not taken.
917 const SourceFile *memoryRT = sourceFile->requestRuntimeModule(MEMORY_RT);
807
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 917 times.
917 assert(memoryRT != nullptr);
808 917 Scope *matchScope = memoryRT->globalScope.get();
809 // Set dealloc function to used
810
1/2
✓ Branch 6 → 7 taken 917 times.
✗ Branch 6 → 46 not taken.
917 const QualType thisType(TY_DYN);
811
3/6
✓ Branch 7 → 8 taken 917 times.
✗ Branch 7 → 28 not taken.
✓ Branch 8 → 9 taken 917 times.
✗ Branch 8 → 28 not taken.
✓ Branch 9 → 10 taken 917 times.
✗ Branch 9 → 28 not taken.
917 QualType bytePtrRefType = QualType(TY_BYTE).toPtr(node).toRef(node);
812
1/2
✓ Branch 10 → 11 taken 917 times.
✗ Branch 10 → 46 not taken.
917 bytePtrRefType.makeHeap();
813
1/2
✓ Branch 14 → 15 taken 917 times.
✗ Branch 14 → 30 not taken.
1834 const ArgList args = {{bytePtrRefType, false /* we always have the field as storage */}};
814
2/4
✓ Branch 19 → 20 taken 917 times.
✗ Branch 19 → 37 not taken.
✓ Branch 20 → 21 taken 917 times.
✗ Branch 20 → 35 not taken.
2751 Function *deallocFct = FunctionManager::match(matchScope, FCT_NAME_DEALLOC, thisType, args, {}, true, node);
815
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 917 times.
917 assert(deallocFct != nullptr);
816 917 deallocFct->used = true;
817 917 }
818
819 /**
820 * Consider calls to destructors for the given scope
821 *
822 * @param node StmtLstNode for the current scope
823 */
824 168405 void TypeChecker::doScopeCleanup(StmtLstNode *node) const {
825 // Get all variables, that are approved for de-allocation
826
1/2
✓ Branch 2 → 3 taken 168405 times.
✗ Branch 2 → 69 not taken.
168405 std::vector<SymbolTableEntry *> vars = currentScope->getVarsGoingOutOfScope();
827 // Sort by reverse declaration order
828 231025 const auto comp = [this](const SymbolTableEntry *a, const SymbolTableEntry *b) {
829 62620 const ASTNode *aDeclNode = a->declNode;
830 62620 const ASTNode *bDeclNode = b->declNode;
831 // Primary sort criteria is the code location
832
3/4
✓ Branch 2 → 3 taken 62620 times.
✗ Branch 2 → 15 not taken.
✓ Branch 3 → 4 taken 62050 times.
✓ Branch 3 → 10 taken 570 times.
62620 if (aDeclNode->codeLoc != bDeclNode->codeLoc)
833
2/2
✓ Branch 4 → 5 taken 2112 times.
✓ Branch 4 → 6 taken 59938 times.
124100 return aDeclNode->codeLoc > bDeclNode->codeLoc;
834 // Secondary sort criteria is the node id
835
2/4
✓ Branch 10 → 11 taken 570 times.
✗ Branch 10 → 15 not taken.
✓ Branch 11 → 12 taken 570 times.
✗ Branch 11 → 15 not taken.
570 return resourceManager.nodeToNodeId[aDeclNode] > resourceManager.nodeToNodeId[bDeclNode];
836 168405 };
837
1/2
✓ Branch 3 → 4 taken 168405 times.
✗ Branch 3 → 67 not taken.
168405 std::ranges::stable_sort(vars, comp);
838 // Call the dtor of each variable. We call the dtor in reverse declaration order
839
2/2
✓ Branch 62 → 6 taken 69940 times.
✓ Branch 62 → 63 taken 168405 times.
406750 for (SymbolTableEntry *var : vars) {
840 // Check if we have a heap-allocated pointer
841
10/14
✓ Branch 8 → 9 taken 69940 times.
✗ Branch 8 → 65 not taken.
✓ Branch 9 → 10 taken 69940 times.
✗ Branch 9 → 65 not taken.
✓ Branch 10 → 11 taken 7079 times.
✓ Branch 10 → 15 taken 62861 times.
✓ Branch 11 → 12 taken 7079 times.
✗ Branch 11 → 65 not taken.
✓ Branch 12 → 13 taken 7079 times.
✗ Branch 12 → 65 not taken.
✓ Branch 13 → 14 taken 5718 times.
✓ Branch 13 → 15 taken 1361 times.
✓ Branch 16 → 17 taken 5718 times.
✓ Branch 16 → 36 taken 64222 times.
69940 if (var->getQualType().isHeap() && var->getQualType().isOneOf({TY_PTR, TY_STRING, TY_FUNCTION, TY_PROCEDURE})) {
842 // The memory runtime is ignored, because it manually allocates to avoid circular dependencies.
843 // Same goes for the string runtime.
844
8/10
✓ Branch 17 → 18 taken 5718 times.
✗ Branch 17 → 66 not taken.
✓ Branch 20 → 21 taken 4738 times.
✓ Branch 20 → 25 taken 980 times.
✓ Branch 21 → 22 taken 4738 times.
✗ Branch 21 → 66 not taken.
✓ Branch 24 → 25 taken 3674 times.
✓ Branch 24 → 26 taken 1064 times.
✓ Branch 27 → 28 taken 4654 times.
✓ Branch 27 → 29 taken 1064 times.
16174 if (sourceFile->isMemoryRT() || sourceFile->isStringRT())
845 4654 continue;
846 // If the local variable currently does not have the ownership, we must not deallocate its memory
847
3/4
✓ Branch 30 → 31 taken 1064 times.
✗ Branch 30 → 66 not taken.
✓ Branch 31 → 32 taken 147 times.
✓ Branch 31 → 33 taken 917 times.
1064 if (!var->getLifecycle().isInOwningState())
848 147 continue;
849
850
1/2
✓ Branch 33 → 34 taken 917 times.
✗ Branch 33 → 66 not taken.
917 implicitlyCallDeallocate(node); // Required to request the memory runtime
851
2/4
✓ Branch 34 → 35 taken 917 times.
✗ Branch 34 → 66 not taken.
✓ Branch 35 → 36 taken 917 times.
✗ Branch 35 → 66 not taken.
917 node->resourcesToCleanup.at(manIdx).heapVarsToFree.push_back(var);
852 }
853 // Only generate dtor call for structs and if not omitted
854
8/10
✓ Branch 36 → 37 taken 65139 times.
✗ Branch 36 → 66 not taken.
✓ Branch 37 → 38 taken 65139 times.
✗ Branch 37 → 66 not taken.
✓ Branch 38 → 39 taken 14227 times.
✓ Branch 38 → 40 taken 50912 times.
✓ Branch 39 → 40 taken 796 times.
✓ Branch 39 → 41 taken 13431 times.
✓ Branch 42 → 43 taken 51708 times.
✓ Branch 42 → 44 taken 13431 times.
65139 if (!var->getQualType().is(TY_STRUCT) || var->omitDtorCall)
855 51708 continue;
856 // Variable must be either initialized or a struct field
857
5/8
✓ Branch 45 → 46 taken 13431 times.
✗ Branch 45 → 66 not taken.
✓ Branch 46 → 47 taken 2 times.
✓ Branch 46 → 49 taken 13429 times.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 2 times.
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 52 taken 13431 times.
13431 if (!var->getLifecycle().isInitialized() && var->scope->type != ScopeType::STRUCT)
858 continue;
859 // Call dtor
860
1/2
✓ Branch 52 → 53 taken 13431 times.
✗ Branch 52 → 66 not taken.
13431 implicitlyCallStructDtor(var, node);
861 }
862 168405 }
863
864 } // namespace spice::compiler
865