ucn 5.5.1 → 5.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +30 -21
- package/cli/index.js +1 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +35 -15
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +1 -0
- package/core/output/reporting.js +6 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +217 -112
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/rust-macro-expansion.js +151 -19
- package/core/rust-modules.js +1 -1
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +124 -37
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { langTraits } = require('../languages');
|
|
5
|
+
const { ownerDefinitionOf } = require('./class-identity');
|
|
6
|
+
const { declarationIdentity, identityKey, sameDeclaration, validateConfirmation } = require('./provenance');
|
|
7
|
+
const { captureOverload, projectFact, refusedProof } = require('./provenance-overload');
|
|
8
|
+
const { codeUnitCompare, NON_CALLABLE_TYPES } = require('./shared');
|
|
9
|
+
|
|
10
|
+
// Modules read lazily (load cycles), each resolved once: a require() per
|
|
11
|
+
// call in hot paths was measurable (fix #436).
|
|
12
|
+
let lazyProvenanceFactsModuleCache = null;
|
|
13
|
+
const lazyProvenanceFactsModule = () => lazyProvenanceFactsModuleCache || (lazyProvenanceFactsModuleCache = require('./provenance-facts'));
|
|
14
|
+
let lazyProvenanceTypeModuleCache = null;
|
|
15
|
+
const lazyProvenanceTypeModule = () => lazyProvenanceTypeModuleCache || (lazyProvenanceTypeModuleCache = require('./provenance-type'));
|
|
16
|
+
|
|
17
|
+
const isConstructor = (def, index) => !def?.modifiers?.includes('destructor') &&
|
|
18
|
+
(def?.type === 'constructor' && !def.modifiers?.includes('static') || !!(def?.className && index && !NON_CALLABLE_TYPES.has(def.type) &&
|
|
19
|
+
langTraits(index.files.get(def.file)?.language)?.constructorProtocol?.initializer === def.name));
|
|
20
|
+
const position = site => site.provenance?.facts?.site || site.callSite || site;
|
|
21
|
+
const siteKey = site => `${site.file}\0${site.line}\0${position(site).column ?? ''}`;
|
|
22
|
+
const sameSite = (call, site) => {
|
|
23
|
+
if (call.line !== site.line) return false;
|
|
24
|
+
const a = { start: call.callStart ?? call.callSite?.start, column: call.column ?? call.callSite?.column };
|
|
25
|
+
const b = position(site);
|
|
26
|
+
if (a.start != null && b.start != null) return a.start === b.start;
|
|
27
|
+
if (b.start != null && a.column == null) return false;
|
|
28
|
+
return a.column == null || b.column == null || a.column === b.column;
|
|
29
|
+
};
|
|
30
|
+
const order = (a, b) => codeUnitCompare(a.relativePath || a.file, b.relativePath || b.file) ||
|
|
31
|
+
a.line - b.line || (position(a).column ?? 0) - (position(b).column ?? 0);
|
|
32
|
+
|
|
33
|
+
// Call records of one line, in record order (fix #418: every projected site
|
|
34
|
+
// scanned all of its file's records).
|
|
35
|
+
function callsOnLine(index, file, line) {
|
|
36
|
+
const calls = index.getCachedCalls(file) || [];
|
|
37
|
+
const memo = index._opMemo?.('constructionCallsByLine', () => new Map());
|
|
38
|
+
let saved = memo?.get(file);
|
|
39
|
+
if (!saved || saved.calls !== calls) {
|
|
40
|
+
const byLine = new Map();
|
|
41
|
+
for (const call of calls) {
|
|
42
|
+
let list = byLine.get(call.line);
|
|
43
|
+
if (!list) byLine.set(call.line, list = []);
|
|
44
|
+
list.push(call);
|
|
45
|
+
}
|
|
46
|
+
saved = { calls, byLine };
|
|
47
|
+
memo?.set(file, saved);
|
|
48
|
+
}
|
|
49
|
+
return saved.byLine.get(line) || [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ownMembers(index, owner) {
|
|
53
|
+
// One owner-identity scan of the file per class and operation (fix #418:
|
|
54
|
+
// every construction site of a class rescanned its whole file). A
|
|
55
|
+
// witness view records the scan's reads itself.
|
|
56
|
+
const memo = index.isProvenanceFactIndex ? null : index._opMemo?.('constructorOwnMembers', () => new WeakMap());
|
|
57
|
+
const saved = memo?.get(owner);
|
|
58
|
+
const symbols = index.files.get(owner.file)?.symbols || [];
|
|
59
|
+
if (saved && saved.symbols === symbols) return saved.members;
|
|
60
|
+
// The parts of a C# partial type are one type (fix #430): a part
|
|
61
|
+
// without constructors (`partial class JValue` in JValue.Async.cs) has
|
|
62
|
+
// the constructors the other parts declare.
|
|
63
|
+
const parts = lazyProvenanceFactsModule().partialTypeParts(index, owner) || [owner];
|
|
64
|
+
const members = parts.flatMap(part => {
|
|
65
|
+
const identity = declarationIdentity(part);
|
|
66
|
+
return (index.files.get(part.file)?.symbols || []).filter(def =>
|
|
67
|
+
sameDeclaration(declarationIdentity(ownerDefinitionOf(index, def)), identity));
|
|
68
|
+
});
|
|
69
|
+
memo?.set(owner, { symbols, members });
|
|
70
|
+
return members;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function ownConstructors(index, owner) { return ownMembers(index, owner).filter(def => isConstructor(def, index)); }
|
|
74
|
+
|
|
75
|
+
// The constructor a type's header declares without a body declaration of
|
|
76
|
+
// its own (fix #419): a C# primary constructor, a Java record's implicit
|
|
77
|
+
// canonical constructor (parser `primaryParams`). It is a member of the
|
|
78
|
+
// constructor family that overload selection can choose, never a target:
|
|
79
|
+
// a construction it receives is a class dependency only. Its identity
|
|
80
|
+
// spans the type's declaration; the binding id keeps it apart from a
|
|
81
|
+
// declared constructor on the same lines.
|
|
82
|
+
const HEADER_CONSTRUCTORS = new WeakMap();
|
|
83
|
+
function headerConstructor(owner, declared) {
|
|
84
|
+
if (!Array.isArray(owner?.primaryParams) || !declared?.length) return null;
|
|
85
|
+
const saved = HEADER_CONSTRUCTORS.get(owner);
|
|
86
|
+
if (saved && saved.params === owner.primaryParams && saved.sibling === declared[0]) return saved.definition;
|
|
87
|
+
const sibling = declared[0];
|
|
88
|
+
const definition = {
|
|
89
|
+
name: owner.name, type: 'constructor', className: owner.name, file: owner.file,
|
|
90
|
+
relativePath: owner.relativePath, startLine: owner.startLine, endLine: owner.endLine,
|
|
91
|
+
...(sibling.namespace && { namespace: sibling.namespace }),
|
|
92
|
+
...(sibling.enclosingType && { enclosingType: sibling.enclosingType }),
|
|
93
|
+
...(sibling.lexicalScopeStartLine && { lexicalScopeStartLine: sibling.lexicalScopeStartLine,
|
|
94
|
+
lexicalScopeEndLine: sibling.lexicalScopeEndLine }),
|
|
95
|
+
params: owner.primaryParams.map(param => [param.type, param.name].filter(Boolean).join(' ')).join(', '),
|
|
96
|
+
paramsStructured: owner.primaryParams,
|
|
97
|
+
modifiers: (owner.modifiers || []).filter(modifier => ACCESS_MODIFIERS.has(modifier)),
|
|
98
|
+
isConstructor: true, headerConstructor: true,
|
|
99
|
+
bindingId: `${owner.relativePath}:constructor:${owner.startLine}:header`,
|
|
100
|
+
};
|
|
101
|
+
HEADER_CONSTRUCTORS.set(owner, { params: owner.primaryParams, sibling, definition });
|
|
102
|
+
return definition;
|
|
103
|
+
}
|
|
104
|
+
const ACCESS_MODIFIERS = new Set(['public', 'protected', 'private', 'internal']);
|
|
105
|
+
|
|
106
|
+
// The constructor a `this(...)` delegation runs in: the declared
|
|
107
|
+
// constructor of the owner whose source extent holds the site. A
|
|
108
|
+
// constructor never delegates to itself (Java JLS 8.8.7, C# CS0516, C++
|
|
109
|
+
// [class.base.init]), so it leaves the candidate family (fix #419). A site
|
|
110
|
+
// in an out-of-class definition runs in the declaration it defines (fix
|
|
111
|
+
// #421): `{ member, definition }` names both.
|
|
112
|
+
function delegatingConstructor(index, owner, declared, call, file) {
|
|
113
|
+
const start = call.callStart ?? call.callSite?.start;
|
|
114
|
+
const sameFile = def => !file || def.file === file;
|
|
115
|
+
const holding = declared.filter(def => sameFile(def) && (Number.isFinite(start) && def.sourceSpan
|
|
116
|
+
? def.sourceSpan.start <= start && start < def.sourceSpan.end
|
|
117
|
+
: def.startLine === call.enclosingFunction?.startLine && def.endLine === call.enclosingFunction?.endLine));
|
|
118
|
+
if (holding.length === 1) return { member: holding[0] };
|
|
119
|
+
if (holding.length || !file) return null;
|
|
120
|
+
const { enclosingDefinition, declarationOf } = require('./out-of-class-members');
|
|
121
|
+
const definition = enclosingDefinition(index, file, call, owner);
|
|
122
|
+
const member = definition && declarationOf(index, definition);
|
|
123
|
+
return member && declared.includes(member) ? { member, definition } : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// These spellings only select candidates. Every site must replay the
|
|
127
|
+
// language's value/type binding before it can reach a class definition.
|
|
128
|
+
function constructionAliases(index) {
|
|
129
|
+
const build = () => {
|
|
130
|
+
const aliases = new Map();
|
|
131
|
+
const add = (name, target) => {
|
|
132
|
+
if (!name || !target) return;
|
|
133
|
+
let plain = '', depth = 0;
|
|
134
|
+
for (const char of target) {
|
|
135
|
+
if (char === '<') depth++;
|
|
136
|
+
else if (char === '>') depth--;
|
|
137
|
+
else if (depth === 0) plain += char;
|
|
138
|
+
}
|
|
139
|
+
const terminal = plain.split('::').pop().split('.').pop().trim();
|
|
140
|
+
if (!aliases.has(name)) aliases.set(name, new Set());
|
|
141
|
+
aliases.get(name).add(terminal);
|
|
142
|
+
};
|
|
143
|
+
for (const [, entry] of index.files) {
|
|
144
|
+
for (const alias of entry.moduleValueAliases || []) {
|
|
145
|
+
add(alias.name, alias.target);
|
|
146
|
+
for (const alternative of alias.alternatives || []) add(alias.name, alternative.target || alternative.imported);
|
|
147
|
+
}
|
|
148
|
+
for (const def of entry.symbols || []) if (def.type === 'type') add(def.name, def.aliasOf);
|
|
149
|
+
for (const binding of entry.importBindings || []) {
|
|
150
|
+
if (binding.kind === 'using' && !binding.alias) add(binding.name, binding.module);
|
|
151
|
+
else if (binding.alias) add(binding.alias, binding.name);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return aliases;
|
|
155
|
+
};
|
|
156
|
+
// Only indexed declarations and import bindings contribute; rebuild and
|
|
157
|
+
// incremental updates invalidate this inventory. Witnesses read their facts.
|
|
158
|
+
if (index.isProvenanceFactIndex) return build();
|
|
159
|
+
return index._constructionAliases || (index._constructionAliases = build());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// The type names a spelling can reach through the alias dictionary. A
|
|
163
|
+
// witness records this closure as one fact rather than every file the
|
|
164
|
+
// dictionary scans (fix #418: a scan per witness was quadratic).
|
|
165
|
+
function constructionTypeNames(index, name) {
|
|
166
|
+
const memo = index.isProvenanceFactIndex ? null : index._opMemo?.('constructionTypeNames');
|
|
167
|
+
const saved = memo?.get(name);
|
|
168
|
+
if (saved) return saved.slice();
|
|
169
|
+
const aliases = constructionAliases(index);
|
|
170
|
+
const names = new Set([name]);
|
|
171
|
+
for (let round = 0; round < 12; round++) {
|
|
172
|
+
const size = names.size;
|
|
173
|
+
for (const current of [...names]) for (const target of aliases.get(current) || []) names.add(target);
|
|
174
|
+
if (size === names.size) break;
|
|
175
|
+
}
|
|
176
|
+
const result = [...names];
|
|
177
|
+
memo?.set(name, result);
|
|
178
|
+
return result.slice();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Every spelling whose alias closure (`constructionTypeNames`) holds one of
|
|
182
|
+
// `typeNames`: the closure read backwards, to the same depth.
|
|
183
|
+
function constructionSpellingsReaching(index, typeNames) {
|
|
184
|
+
const aliases = constructionAliases(index);
|
|
185
|
+
const reverse = new Map();
|
|
186
|
+
for (const [spelling, targets] of aliases) {
|
|
187
|
+
for (const target of targets) {
|
|
188
|
+
if (!reverse.has(target)) reverse.set(target, []);
|
|
189
|
+
reverse.get(target).push(spelling);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const reached = new Set(typeNames);
|
|
193
|
+
let frontier = [...reached];
|
|
194
|
+
for (let round = 0; round < 12 && frontier.length; round++) {
|
|
195
|
+
const next = [];
|
|
196
|
+
for (const current of frontier) {
|
|
197
|
+
for (const spelling of reverse.get(current) || []) {
|
|
198
|
+
if (!reached.has(spelling)) { reached.add(spelling); next.push(spelling); }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
frontier = next;
|
|
202
|
+
}
|
|
203
|
+
return reached;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function constructionTypeCandidates(index, name) {
|
|
207
|
+
return projectFact(index, `construction-type-names:${name}`, live => constructionTypeNames(live, name))
|
|
208
|
+
.flatMap(current => index.symbols.get(current) || [])
|
|
209
|
+
.filter(def => ['class', 'struct', 'record', 'enum', 'interface'].includes(def.type));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function constructionAliasNames(index, targets) {
|
|
213
|
+
const names = new Set(targets.filter(def => ['class', 'struct', 'record', 'enum'].includes(def.type) &&
|
|
214
|
+
langTraits(index.files.get(def.file)?.language)?.hasConstructors).map(def => def.name));
|
|
215
|
+
if (!names.size) return new Set();
|
|
216
|
+
// Targets with the same constructor-capable names have the same alias
|
|
217
|
+
// surfaces. Binding to a particular definition is still proved later.
|
|
218
|
+
const memo = index.isProvenanceFactIndex ? null : index._opMemo?.('constructionAliasNames');
|
|
219
|
+
const key = JSON.stringify([...names].sort(codeUnitCompare));
|
|
220
|
+
const saved = memo?.get(key);
|
|
221
|
+
if (saved) return new Set(saved);
|
|
222
|
+
const aliases = constructionAliases(index), surfaces = new Set();
|
|
223
|
+
for (let round = 0; round < 12; round++) {
|
|
224
|
+
let changed = false;
|
|
225
|
+
for (const [name, spelled] of aliases) {
|
|
226
|
+
if (!surfaces.has(name) && [...spelled].some(target => names.has(target))) {
|
|
227
|
+
names.add(name); surfaces.add(name); changed = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!changed) break;
|
|
231
|
+
}
|
|
232
|
+
memo?.set(key, surfaces);
|
|
233
|
+
return new Set(surfaces);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function constructionAliasPossibilities(index, file, call) {
|
|
237
|
+
const language = index.files.get(file)?.language, traits = langTraits(language);
|
|
238
|
+
if (!traits?.hasConstructors || !isConstruction(call, null, language) || call.localShadow ||
|
|
239
|
+
call.isMethod && !call.receiverIsModule && !call.isConstructor && !(call.isPathCall && traits.functionalTypeConstruction) ||
|
|
240
|
+
!constructionAliases(index).has(call.name)) return [];
|
|
241
|
+
const family = traits.nameReferenceFamily || language;
|
|
242
|
+
const candidates = constructionTypeCandidates(index, call.name).filter(def =>
|
|
243
|
+
(langTraits(index.files.get(def.file)?.language)?.nameReferenceFamily || index.files.get(def.file)?.language) === family);
|
|
244
|
+
if (!candidates.length) return [];
|
|
245
|
+
if (traits.functionalTypeConstruction) {
|
|
246
|
+
const { TypeReferenceResolver } = require('./type-references');
|
|
247
|
+
const { resolveQualifier, includeClosure } = require('./cpp-scope');
|
|
248
|
+
const context = new TypeReferenceResolver(index, candidates[0], candidates[0].name).cppSiteContext(file, call.line);
|
|
249
|
+
const qualifier = call.constructorQualifier || call.receiver;
|
|
250
|
+
const found = resolveQualifier(index, { file, line: call.line, ...context },
|
|
251
|
+
qualifier ? `${qualifier}::${call.name}` : call.name, includeClosure(index, file), 0, { declarations: true });
|
|
252
|
+
if (found.kind === 'namespace' || found.kind === 'type' && !found.visible ||
|
|
253
|
+
found.kind === 'unknown' && ['no-declaration', 'not-visible'].includes(found.why)) return [];
|
|
254
|
+
} else if (traits.classesCallableWithoutNew && !valueAliasInScope(index, file, call.name, call.receiver)) return [];
|
|
255
|
+
return candidates;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// The project-wide alias dictionary discovers spellings, not bindings. A
|
|
259
|
+
// module's own values and imports decide whether this call can use one.
|
|
260
|
+
function valueAliasInScope(index, file, name, qualifier, seen = new Set()) {
|
|
261
|
+
const key = `${file}\0${qualifier || ''}\0${name}`;
|
|
262
|
+
if (seen.has(key) || seen.size >= 12) return true;
|
|
263
|
+
const entry = index.files.get(file);
|
|
264
|
+
if (!entry) return true;
|
|
265
|
+
const visited = new Set(seen).add(key);
|
|
266
|
+
if (!qualifier && ((entry.moduleValueAliases || []).some(alias => alias.name === name) ||
|
|
267
|
+
(entry.symbols || []).some(def => def.name === name && !def.className &&
|
|
268
|
+
['class', 'struct', 'record', 'enum', 'type'].includes(def.type)))) return true;
|
|
269
|
+
const parts = qualifier?.split('.') || [];
|
|
270
|
+
const bound = parts.shift() || name;
|
|
271
|
+
const links = (entry.importBindings || []).filter(binding => (binding.alias || binding.name) === bound);
|
|
272
|
+
if (!links.length) return false;
|
|
273
|
+
return links.some(binding => {
|
|
274
|
+
const relative = entry.moduleResolved?.[`${binding.module}.${binding.name}`] || entry.moduleResolved?.[binding.module];
|
|
275
|
+
if (!relative) return true;
|
|
276
|
+
const target = path.resolve(index.root, relative);
|
|
277
|
+
return valueAliasInScope(index, target, qualifier ? name : binding.name,
|
|
278
|
+
parts.join('.') || null, visited);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function constructionAliasBinding(index, file, call, targets) {
|
|
283
|
+
const language = index.files.get(file)?.language;
|
|
284
|
+
// An expansion's argument token can declare a constructor as well as
|
|
285
|
+
// supply a call. Its ordinary source type lookup does not prove which
|
|
286
|
+
// role the expanded record has; retain the expansion's existing path.
|
|
287
|
+
const functional = langTraits(language)?.functionalTypeConstruction && !call.macroExpansion;
|
|
288
|
+
if (!langTraits(language)?.hasConstructors || !isConstruction(call, null, language) || call.localShadow || call.isMethod && !call.receiverIsModule &&
|
|
289
|
+
!call.isConstructor && !(call.isPathCall && langTraits(language)?.functionalTypeConstruction) ||
|
|
290
|
+
!constructionAliases(index).has(call.name) && !call.constructorQualifier && !call.implicitBase &&
|
|
291
|
+
!call.isConstructor && !functional) return null;
|
|
292
|
+
let candidates = targets;
|
|
293
|
+
if (!candidates) {
|
|
294
|
+
candidates = constructionTypeCandidates(index, call.name);
|
|
295
|
+
} else {
|
|
296
|
+
// Only a type the call's spelling can name (its own name, or one an
|
|
297
|
+
// alias of it reaches) can be the construction's class (fix #421: a
|
|
298
|
+
// type query looked every call record of a file up at its site).
|
|
299
|
+
const spelled = projectFact(index, `construction-type-names:${call.name}`, live => constructionTypeNames(live, call.name));
|
|
300
|
+
candidates = candidates.filter(def => spelled.includes(def.name));
|
|
301
|
+
}
|
|
302
|
+
if (!candidates.length) return null;
|
|
303
|
+
if (call.implicitBase) return require('./provenance-constructor-base').implicitBaseBinding(index, file, call, candidates);
|
|
304
|
+
const { occurrenceIdentity } = lazyProvenanceFactsModule();
|
|
305
|
+
const site = occurrenceIdentity(index.files.get(file).relativePath, call);
|
|
306
|
+
const query = { file, relativePath: site.file, line: site.line, column: site.column,
|
|
307
|
+
name: call.name, qualifier: call.constructorQualifier || call.receiver || null, localShadow: !!call.localShadow,
|
|
308
|
+
construction: true, functional: !call.isConstructor && !!langTraits(language)?.functionalTypeConstruction };
|
|
309
|
+
if (langTraits(language)?.functionalTypeConstruction) {
|
|
310
|
+
try {
|
|
311
|
+
// The witness replays this same lookup. Other owner identities
|
|
312
|
+
// cannot satisfy it and need no separate capture attempt.
|
|
313
|
+
const bound = lazyProvenanceTypeModule().cppConstructionClasses(index, query);
|
|
314
|
+
candidates = candidates.filter(owner => bound.length > 0 && bound.every(def =>
|
|
315
|
+
sameDeclaration(declarationIdentity(def), declarationIdentity(owner))));
|
|
316
|
+
} catch (error) {
|
|
317
|
+
// A refused optional lookup leaves the ordinary proof attempts.
|
|
318
|
+
refusedProof(error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const family = langTraits(language)?.nameReferenceFamily || language;
|
|
322
|
+
const found = candidates.filter(def => ['class', 'struct', 'record', 'enum'].includes(def.type) &&
|
|
323
|
+
(langTraits(index.files.get(def.file)?.language)?.nameReferenceFamily || index.files.get(def.file)?.language) === family).map(owner => ({
|
|
324
|
+
owner, witness: lazyProvenanceTypeModule().captureTypeLookup(index, query, owner),
|
|
325
|
+
})).filter(entry => entry.witness);
|
|
326
|
+
if (found.length !== 1) return null;
|
|
327
|
+
const { owner, witness } = found[0];
|
|
328
|
+
const facts = { language, site, targets: [declarationIdentity(owner)], constructionBinding: witness };
|
|
329
|
+
return { owner, provenance: { rule: 'construction-type', rules: ['construction-type'], facts } };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const OWN_CLASS_KINDS = new Set(['class', 'struct', 'record']);
|
|
333
|
+
const namedAt = (def, line) => def.nameLine === line || def.startLine === line;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The class a construction through a method's own receiver builds (fix
|
|
337
|
+
* #434): `cls(..)` in a Python classmethod, `type(self)(..)`,
|
|
338
|
+
* `self.__class__(..)`, JS `new this(..)` in a static method and `new
|
|
339
|
+
* this.constructor(..)`. The site builds the method's class, or a subclass
|
|
340
|
+
* of it when the method runs through one; the witness replays the method's
|
|
341
|
+
* class from the recorded declarations.
|
|
342
|
+
*/
|
|
343
|
+
function ownClassBinding(index, file, call) {
|
|
344
|
+
const own = call.ownClass;
|
|
345
|
+
const entry = index.files.get(file);
|
|
346
|
+
if (!own || !entry || !langTraits(entry.language)?.ownClassMethod) return null;
|
|
347
|
+
const owner = (entry.symbols || []).find(def => def.name === own.name && OWN_CLASS_KINDS.has(def.type) &&
|
|
348
|
+
namedAt(def, own.line));
|
|
349
|
+
if (!owner) return null;
|
|
350
|
+
const { occurrenceIdentity } = lazyProvenanceFactsModule();
|
|
351
|
+
const site = occurrenceIdentity(entry.relativePath, call);
|
|
352
|
+
const witness = lazyProvenanceTypeModule().captureTypeLookup(index, { file, relativePath: site.file,
|
|
353
|
+
line: site.line, column: site.column, name: call.name, ownClass: own, construction: true }, owner);
|
|
354
|
+
if (!witness) return null;
|
|
355
|
+
return { owner, provenance: { rule: 'construction-type', rules: ['construction-type'],
|
|
356
|
+
facts: { language: entry.language, site, targets: [declarationIdentity(owner)], constructionBinding: witness } } };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Ancestors of the owners by resolved base identity, same name family, in
|
|
360
|
+
// breadth-first order (fix #434).
|
|
361
|
+
function resolvedAncestors(index, owners) {
|
|
362
|
+
const { classDefsNamed, parentRefsOf } = require('./class-identity');
|
|
363
|
+
const keyOf = def => classDefsNamed(index, def.name).entries.find(entry => entry.def === def)?.key || null;
|
|
364
|
+
const seen = new Set(owners.map(def => identityKey(declarationIdentity(def))));
|
|
365
|
+
const out = [];
|
|
366
|
+
const queue = owners.map(def => ({ name: def.name, key: keyOf(def), def }));
|
|
367
|
+
while (queue.length && out.length < 64) {
|
|
368
|
+
const ref = queue.shift();
|
|
369
|
+
if (!ref.key) continue;
|
|
370
|
+
for (const parent of parentRefsOf(index, ref)) {
|
|
371
|
+
if (parent.external || !parent.key) continue;
|
|
372
|
+
const def = classDefsNamed(index, parent.name).entries.find(entry => entry.key === parent.key)?.def;
|
|
373
|
+
if (!def || nameFamily(index, def) !== nameFamily(index, ref.def)) continue;
|
|
374
|
+
const id = identityKey(declarationIdentity(def));
|
|
375
|
+
if (seen.has(id)) continue;
|
|
376
|
+
seen.add(id);
|
|
377
|
+
out.push(def);
|
|
378
|
+
queue.push({ name: def.name, key: parent.key, def });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function isConstruction(call, site, language) {
|
|
385
|
+
return !site?.isFunctionReference && !site?.isTypeReference && !call.isFunctionReference &&
|
|
386
|
+
!call.isPotentialCallback && !call.isTypeReference && (call.isConstructor ||
|
|
387
|
+
langTraits(language)?.classesCallableWithoutNew || langTraits(language)?.functionalTypeConstruction);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function constructionTypeParameter(index, file, call) {
|
|
391
|
+
const language = index.files.get(file)?.language;
|
|
392
|
+
if (!langTraits(language)?.hasConstructors || langTraits(language)?.typeSystem !== 'nominal' ||
|
|
393
|
+
!isConstruction(call, null, language) || ['implicit', 'delegation', 'enum'].includes(call.constructorSyntax)) return false;
|
|
394
|
+
if (!call.isConstructor && !constructionTypeCandidates(index, call.name).length) return false;
|
|
395
|
+
const site = lazyProvenanceFactsModule().occurrenceIdentity(index.files.get(file).relativePath, call);
|
|
396
|
+
return lazyProvenanceTypeModule().constructionNameScope(index, { file, line: site.line, column: site.column,
|
|
397
|
+
name: call.name, qualifier: call.constructorQualifier || call.receiver || null }) === false;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function protocolFacts(index, owner) {
|
|
401
|
+
const memo = index._opMemo?.('constructorProtocolFacts', () => new WeakMap());
|
|
402
|
+
if (memo?.has(owner)) return memo.get(owner);
|
|
403
|
+
const facts = require('./constructor-protocol').collectConstructorProtocol(index, owner);
|
|
404
|
+
memo?.set(owner, facts);
|
|
405
|
+
return facts;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// The construction's target-independent evidence: the constructor family,
|
|
409
|
+
// its overload witness and the site's type and name-scope witnesses. A
|
|
410
|
+
// query over several constructors of one class reads each site once per
|
|
411
|
+
// operation (fix #418); every witness is replayed against each target set.
|
|
412
|
+
function constructionEvidence(index, owner, call, classBinding) {
|
|
413
|
+
const memo = classBinding && typeof classBinding === 'object' && call && typeof call === 'object' &&
|
|
414
|
+
!index.isProvenanceFactIndex ? index._opMemo?.('constructionEvidence', () => new WeakMap()) : null;
|
|
415
|
+
let byCall = memo?.get(classBinding);
|
|
416
|
+
if (memo && !byCall) memo.set(classBinding, byCall = new WeakMap());
|
|
417
|
+
let byOwner = byCall?.get(call);
|
|
418
|
+
if (byCall && !byOwner) byCall.set(call, byOwner = new Map());
|
|
419
|
+
const key = byOwner ? identityKey(declarationIdentity(owner)) : null;
|
|
420
|
+
if (key !== null && byOwner.has(key)) return byOwner.get(key);
|
|
421
|
+
const evidence = constructionEvidenceUncached(index, owner, call, classBinding);
|
|
422
|
+
if (key !== null) byOwner.set(key, evidence);
|
|
423
|
+
return evidence;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function constructionEvidenceUncached(index, owner, call, classBinding) {
|
|
427
|
+
let members = ownConstructors(index, owner), declaring = owner, hierarchy = null, delegating = null,
|
|
428
|
+
delegatingDefinition = null;
|
|
429
|
+
if (!members.length) {
|
|
430
|
+
const inherited = require('./constructor-hierarchy').captureHierarchy(index, owner);
|
|
431
|
+
if (!inherited) return { unresolved: true };
|
|
432
|
+
if (inherited.none) {
|
|
433
|
+
// No class of the order declares a constructor. Only an
|
|
434
|
+
// allocation protocol the language checks can still run project
|
|
435
|
+
// code; a construction then depends on its class alone.
|
|
436
|
+
const language = index.files.get(owner.file).language;
|
|
437
|
+
const wrapped = langTraits(language)?.decoratorsWrapCallables && owner.decorators?.length;
|
|
438
|
+
const protocol = langTraits(language)?.constructorProtocol;
|
|
439
|
+
const plain = !wrapped && (!protocol || require('./constructor-protocol').validateConstructorProtocol(
|
|
440
|
+
protocolFacts(index, owner), declarationIdentity(owner), [], language));
|
|
441
|
+
return plain ? { noBody: true } : { unresolved: true };
|
|
442
|
+
}
|
|
443
|
+
members = inherited.members; declaring = inherited.declaring; hierarchy = inherited.witness;
|
|
444
|
+
} else {
|
|
445
|
+
const header = headerConstructor(owner, members);
|
|
446
|
+
if (call.ctorDelegation === 'this') {
|
|
447
|
+
// A same-type delegation runs in one of the owner's own
|
|
448
|
+
// constructors; any other enclosing scope means the site does
|
|
449
|
+
// not belong to this owner's family.
|
|
450
|
+
const siteFile = classBinding?.facts?.site?.file;
|
|
451
|
+
const found = delegatingConstructor(index, owner, members, call,
|
|
452
|
+
siteFile ? path.resolve(index.root, siteFile) : null);
|
|
453
|
+
if (!found) return { unresolved: true, delegation: true };
|
|
454
|
+
delegating = found.member;
|
|
455
|
+
delegatingDefinition = found.definition || null;
|
|
456
|
+
members = members.filter(def => def !== delegating);
|
|
457
|
+
}
|
|
458
|
+
if (header) members = [...members, header];
|
|
459
|
+
if (!members.length) return { unresolved: true, delegation: true, delegating, delegatingDefinition };
|
|
460
|
+
}
|
|
461
|
+
const language = index.files.get(owner.file).language;
|
|
462
|
+
const protocol = protocolFacts(index, owner);
|
|
463
|
+
const { selectProvenanceOverload } = require('./callers');
|
|
464
|
+
const overloadCall = langTraits(language)?.inheritedOverloadSignatures ||
|
|
465
|
+
langTraits(language)?.userDefinedConversions === 'constructors-and-operators'
|
|
466
|
+
? { ...call, argumentFile: classBinding?.facts?.site?.file || call.argumentFile } : call;
|
|
467
|
+
const overload = captureOverload(index, overloadCall, members, language, selectProvenanceOverload);
|
|
468
|
+
const selected = overload?.selected;
|
|
469
|
+
const fallback = !selected && members.length === 1 ? declarationIdentity(members[0]) : null;
|
|
470
|
+
const chosen = selected || fallback;
|
|
471
|
+
const site = classBinding?.facts?.site;
|
|
472
|
+
const functional = !call.isConstructor && langTraits(language)?.functionalTypeConstruction;
|
|
473
|
+
const nameScope = langTraits(language)?.typeSystem === 'nominal' &&
|
|
474
|
+
!['implicit', 'delegation', 'enum'].includes(call.constructorSyntax) && site
|
|
475
|
+
? lazyProvenanceTypeModule().captureTypeLookup(index, {
|
|
476
|
+
file: path.resolve(index.root, site.file), relativePath: site.file,
|
|
477
|
+
line: site.line, column: site.column, name: call.name,
|
|
478
|
+
qualifier: call.constructorQualifier || call.receiver || null, nameScopeOnly: true,
|
|
479
|
+
}, owner) : null;
|
|
480
|
+
const typeLookup = call.ownClass && site
|
|
481
|
+
? lazyProvenanceTypeModule().captureTypeLookup(index, {
|
|
482
|
+
file: path.resolve(index.root, site.file), relativePath: site.file,
|
|
483
|
+
line: site.line, column: site.column, name: call.name, ownClass: call.ownClass, construction: true,
|
|
484
|
+
}, owner)
|
|
485
|
+
: (call.isConstructor || functional || langTraits(language)?.classesCallableWithoutNew) && site &&
|
|
486
|
+
(functional || langTraits(language)?.typeSystem === 'structural' ||
|
|
487
|
+
validateConfirmation(classBinding, declarationIdentity(owner)).verdict !== 'establishes-target')
|
|
488
|
+
? lazyProvenanceTypeModule().captureTypeLookup(index, {
|
|
489
|
+
file: path.resolve(index.root, site.file), relativePath: site.file,
|
|
490
|
+
line: site.line, column: site.column, name: call.name,
|
|
491
|
+
qualifier: call.constructorQualifier || call.receiver || null, functional: !!functional, construction: true,
|
|
492
|
+
localShadow: !!call.localShadow,
|
|
493
|
+
}, owner) : null;
|
|
494
|
+
return { members, declaring, hierarchy, delegating, delegatingDefinition, language, protocol, overload, selected,
|
|
495
|
+
chosen, nameScope, typeLookup };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// The out-of-class definition a delegation runs in and the signature it
|
|
499
|
+
// shares with its in-class declaration (fix #421): the replay checks that
|
|
500
|
+
// the site lies in that definition and that the two declare one constructor.
|
|
501
|
+
function delegatingDefinitionFacts(evidence) {
|
|
502
|
+
const { _cFamilySignatureKey } = require('./callers');
|
|
503
|
+
return { delegatingDefinition: { ...declarationIdentity(evidence.delegatingDefinition),
|
|
504
|
+
signature: _cFamilySignatureKey(evidence.delegatingDefinition) },
|
|
505
|
+
delegatingSignature: _cFamilySignatureKey(evidence.delegating) };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function constructionChoice(index, owner, call, classBinding, targets) {
|
|
509
|
+
const evidence = constructionEvidence(index, owner, call, classBinding);
|
|
510
|
+
if (evidence.noBody) return { noBody: true };
|
|
511
|
+
if (evidence.unresolved) {
|
|
512
|
+
const facts = { language: index.files.get(owner.file)?.language, site: classBinding?.facts?.site,
|
|
513
|
+
targets: (targets || []).map(declarationIdentity) };
|
|
514
|
+
// The delegating constructor is never its own delegation's target,
|
|
515
|
+
// even when no other member of the family remains (fix #419).
|
|
516
|
+
if (evidence.delegating) {
|
|
517
|
+
const parts = lazyProvenanceFactsModule().partialTypeParts(index, owner);
|
|
518
|
+
facts.constructorCall = { owner: declarationIdentity(owner), members: [],
|
|
519
|
+
...(parts && { parts: parts.map(declarationIdentity) }),
|
|
520
|
+
delegating: declarationIdentity(evidence.delegating),
|
|
521
|
+
...(evidence.delegatingDefinition && delegatingDefinitionFacts(evidence)), classBinding,
|
|
522
|
+
call: { isConstructor: !!call.isConstructor, syntax: call.constructorSyntax || null, delegation: 'this' } };
|
|
523
|
+
const provenance = { rule: 'constructor-call', rules: ['constructor-call'], facts };
|
|
524
|
+
if (targets && validateConfirmation(provenance, targets.map(declarationIdentity)).verdict === 'establishes-other') {
|
|
525
|
+
return { other: true, reason: 'constructor-delegation-self', provenance };
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return { reason: evidence.delegation ? 'constructor-delegation-unresolved' : 'constructor-inheritance-unresolved',
|
|
529
|
+
provenance: { rule: 'constructor-call', rules: ['constructor-call'], facts } };
|
|
530
|
+
}
|
|
531
|
+
const { members, declaring, hierarchy, delegating, delegatingDefinition, language, protocol, overload, selected, chosen,
|
|
532
|
+
nameScope, typeLookup } = evidence;
|
|
533
|
+
targets ||= members;
|
|
534
|
+
const parts = lazyProvenanceFactsModule().partialTypeParts(index, declaring);
|
|
535
|
+
const facts = { language, site: classBinding?.facts?.site, targets: targets.map(declarationIdentity),
|
|
536
|
+
constructorCall: { owner: declarationIdentity(declaring), members: members.map(declarationIdentity),
|
|
537
|
+
...(parts && { parts: parts.map(declarationIdentity) }),
|
|
538
|
+
...(hierarchy && { constructedOwner: declarationIdentity(owner), hierarchy }),
|
|
539
|
+
...(delegating && { delegating: declarationIdentity(delegating) }),
|
|
540
|
+
...(delegatingDefinition && delegatingDefinitionFacts(evidence)),
|
|
541
|
+
classBinding, call: { isConstructor: !!call.isConstructor,
|
|
542
|
+
isFunctionReference: !!call.isFunctionReference, isPotentialCallback: !!call.isPotentialCallback,
|
|
543
|
+
syntax: call.constructorSyntax || null, ...(delegating && { delegation: 'this' }) },
|
|
544
|
+
selected: chosen, overload, protocol, typeLookup, nameScope } };
|
|
545
|
+
const provenance = { rule: 'constructor-call', rules: ['constructor-call'], facts };
|
|
546
|
+
let checked = chosen && validateConfirmation(provenance, targets.map(declarationIdentity));
|
|
547
|
+
if (!chosen && delegating) {
|
|
548
|
+
const self = validateConfirmation(provenance, targets.map(declarationIdentity));
|
|
549
|
+
if (self.diagnostic === 'constructor-delegation-self') checked = self;
|
|
550
|
+
}
|
|
551
|
+
if (checked?.verdict === 'establishes-other') {
|
|
552
|
+
return { other: true, ...(checked.diagnostic === 'constructor-delegation-self' && { reason: checked.diagnostic }), provenance };
|
|
553
|
+
}
|
|
554
|
+
const wrappedClass = langTraits(language)?.decoratorsWrapCallables && owner.decorators?.length;
|
|
555
|
+
const reason = checked?.diagnostic === 'constructor-default-unresolved' ? 'constructor-default-unresolved'
|
|
556
|
+
: checked?.diagnostic === 'unproven-construction-name-scope' ? 'constructor-name-scope-unresolved'
|
|
557
|
+
: wrappedClass || checked?.diagnostic === 'constructor-protocol-unresolved' ? 'constructor-protocol-unresolved'
|
|
558
|
+
: checked?.diagnostic === 'constructor-definition-ambiguous' ? 'constructor-definition-ambiguous'
|
|
559
|
+
: checked?.diagnostic === 'constructor-functional-binding' ? 'constructor-functional-binding'
|
|
560
|
+
: overload?.outcome === 'no-fit' ? 'constructor-overload-no-fit'
|
|
561
|
+
: overload?.outcome === 'ambiguous' ? 'constructor-overload-ambiguous'
|
|
562
|
+
: checked?.verdict !== 'establishes-target' ? 'constructor-proof-incomplete' : null;
|
|
563
|
+
provenance.validation = checked?.verdict || 'incomplete';
|
|
564
|
+
if (checked?.diagnostic) provenance.diagnostic = checked.diagnostic;
|
|
565
|
+
// An undecided overload choice whose arguments no pinned constructor
|
|
566
|
+
// can take by count runs another constructor (fix #430: a one-argument
|
|
567
|
+
// `new JValue(x)` never runs `JValue(object?, JTokenType)`).
|
|
568
|
+
// A C++ braced list may run an initializer-list constructor whatever its
|
|
569
|
+
// element count, so only parenthesized and `new T(..)` arguments count.
|
|
570
|
+
if (reason === 'constructor-overload-ambiguous' && langTraits(language)?.hasArityOverloads &&
|
|
571
|
+
call.constructorSyntax !== 'list' && !langTraits(language)?.packExpansionArguments &&
|
|
572
|
+
Number.isInteger(call.argCount) && !call.argSpread && targets.length > 0 &&
|
|
573
|
+
targets.every(target => isConstructor(target, index) && !target.headerConstructor &&
|
|
574
|
+
!require('./callers')._callArityCompatible(call, [target], language))) {
|
|
575
|
+
return { other: true, reason: 'constructor-overload-other', provenance };
|
|
576
|
+
}
|
|
577
|
+
return { members, definition: selected && members.find(def => sameDeclaration(selected, declarationIdentity(def))),
|
|
578
|
+
provenance, reason };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Syntax whose site runs a constructor body without constructing the class
|
|
582
|
+
// at a source spelling of its own: a delegation (`this(..)`, `super(..)`,
|
|
583
|
+
// a mem-initializer), an omitted base initializer, an enum constant.
|
|
584
|
+
const BODY_ONLY_SYNTAX = new Set(['delegation', 'implicit', 'enum']);
|
|
585
|
+
|
|
586
|
+
const groupOrder = (a, b) => codeUnitCompare(a.definition.relativePath, b.definition.relativePath) ||
|
|
587
|
+
a.definition.startLine - b.definition.startLine ||
|
|
588
|
+
(a.definition.sourceSpan?.start ?? Infinity) - (b.definition.sourceSpan?.start ?? Infinity) ||
|
|
589
|
+
codeUnitCompare(identityKey(declarationIdentity(a.definition)), identityKey(declarationIdentity(b.definition)));
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* The constructor bodies a class callee's sites run (fix #421). A
|
|
593
|
+
* construction (`new C(..)`, `C(..)`) stays a dependency on the class,
|
|
594
|
+
* certain whenever its class binding is: its proven constructor joins as an
|
|
595
|
+
* initializer edge (`initializers`), and an undecided one is a review of the
|
|
596
|
+
* class edge (`initializerReviews`), never a demotion of it. A delegation,
|
|
597
|
+
* an omitted base initializer or an enum constant runs a constructor body
|
|
598
|
+
* only: its sites leave the class edge (`groups`, `unverified`).
|
|
599
|
+
*/
|
|
600
|
+
function projectConstructorCalleeSites(index, owner, siteIds, readSite) {
|
|
601
|
+
if (!siteIds?.length || !ownConstructors(index, owner).length &&
|
|
602
|
+
!langTraits(index.files.get(owner.file)?.language)?.constructorInheritance) return null;
|
|
603
|
+
const language = index.files.get(owner.file)?.language;
|
|
604
|
+
const remaining = [], unverified = [], groups = new Map(), initializers = new Map(), initializerReviews = [];
|
|
605
|
+
for (const siteId of siteIds) {
|
|
606
|
+
const { call, site } = readSite(siteId);
|
|
607
|
+
if (!isConstruction(call, site, language)) { remaining.push(siteId); continue; }
|
|
608
|
+
const bodyOnly = BODY_ONLY_SYNTAX.has(call.constructorSyntax);
|
|
609
|
+
if (!bodyOnly) remaining.push(siteId);
|
|
610
|
+
const choice = constructionChoice(index, owner, call, site.provenance);
|
|
611
|
+
// The type header's own constructor has no declaration (fix #419),
|
|
612
|
+
// and a class whose whole order declares none runs no project body
|
|
613
|
+
// (fix #421): a construction remains a dependency on the class.
|
|
614
|
+
if (choice.noBody && !bodyOnly) continue;
|
|
615
|
+
if (!choice.reason && choice.definition?.headerConstructor) {
|
|
616
|
+
if (bodyOnly) remaining.push(siteId);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (choice.reason || !choice.definition) {
|
|
620
|
+
const reason = choice.reason || 'constructor-proof-incomplete';
|
|
621
|
+
if (!bodyOnly) {
|
|
622
|
+
initializerReviews.push({ siteId, call, reason, provenance: choice.provenance });
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
// A Python class call still depends on its proven class binding
|
|
626
|
+
// when its allocator/decorator protocol cannot select a body.
|
|
627
|
+
// The constructor-handle query retains that uncertainty.
|
|
628
|
+
if (['constructor-protocol-unresolved', 'constructor-inheritance-unresolved'].includes(choice.reason) &&
|
|
629
|
+
validateConfirmation(site.provenance, declarationIdentity(owner)).verdict === 'establishes-target') {
|
|
630
|
+
remaining.push(siteId);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
unverified.push({ siteId, call, reason, provenance: choice.provenance });
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
// The edge names the definition holding the selected constructor's
|
|
637
|
+
// body (an in-class declaration's out-of-class definition, fix
|
|
638
|
+
// #421); its proof establishes the declaration.
|
|
639
|
+
const definition = require('./out-of-class-members').bodyDefinitionOf(index, choice.definition) || choice.definition;
|
|
640
|
+
const key = identityKey(declarationIdentity(definition));
|
|
641
|
+
const target = bodyOnly ? groups : initializers;
|
|
642
|
+
if (!target.has(key)) target.set(key, { definition, sites: [] });
|
|
643
|
+
target.get(key).sites.push({ ...site, targetDef: declarationIdentity(choice.definition),
|
|
644
|
+
provenance: choice.provenance, tier: 'confirmed' });
|
|
645
|
+
}
|
|
646
|
+
if (remaining.length === siteIds.length && !initializers.size && !initializerReviews.length) return null;
|
|
647
|
+
return { remaining, unverified, groups: [...groups.values()].sort(groupOrder),
|
|
648
|
+
initializers: [...initializers.values()].sort(groupOrder), initializerReviews };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// A class's construction sites, read once per operation for every
|
|
652
|
+
// constructor of the class a query projects (fix #418). Options carrying a
|
|
653
|
+
// profile, statistics or a callback keep their own read.
|
|
654
|
+
function classConstructionCalls(index, owner, options) {
|
|
655
|
+
const read = () => index.findCallers(owner.name, { ...options, profile: undefined,
|
|
656
|
+
constructorProjectionSource: true, targetDefinitions: [owner], collectAccount: true,
|
|
657
|
+
includeMethods: true, needsTotal: true });
|
|
658
|
+
if (options.profile || options.stats) return read();
|
|
659
|
+
const memo = index._opMemo?.('classConstructionCalls', () => new Map());
|
|
660
|
+
if (!memo) return read();
|
|
661
|
+
// An option that is not plain data (the candidate-file Set of a HOT
|
|
662
|
+
// ranking) is keyed by its identity within the operation; a function
|
|
663
|
+
// keeps its own read (fix #435: the key threw for a Set, so every
|
|
664
|
+
// constructor of a ranking re-read its class).
|
|
665
|
+
const identities = index._opMemo('classConstructionOptionIds', () => ({ ids: new WeakMap(), next: 0 }));
|
|
666
|
+
let unkeyed = false;
|
|
667
|
+
const key = identityKey(declarationIdentity(owner)) + '\0' + JSON.stringify(Object.keys(options).sort()
|
|
668
|
+
.filter(name => name !== 'targetDefinitions' && name !== 'profile')
|
|
669
|
+
.map(name => [name, options[name]]), (_name, value) => {
|
|
670
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
671
|
+
unkeyed = true;
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
if (value && typeof value === 'object' && !Array.isArray(value) &&
|
|
675
|
+
Object.getPrototypeOf(value) !== Object.prototype) {
|
|
676
|
+
if (!identities.ids.has(value)) identities.ids.set(value, `\0object:${identities.next++}`);
|
|
677
|
+
return identities.ids.get(value);
|
|
678
|
+
}
|
|
679
|
+
return value;
|
|
680
|
+
});
|
|
681
|
+
if (unkeyed) return read();
|
|
682
|
+
if (!memo.has(key)) memo.set(key, read());
|
|
683
|
+
return memo.get(key);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const nameFamily = (index, def) => {
|
|
687
|
+
const language = index.files.get(def.file)?.language;
|
|
688
|
+
return langTraits(language)?.nameReferenceFamily || language;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// Whether a class the inheritance graph lists under `parentName` derives
|
|
692
|
+
// from this owner (fix #434). The graph is keyed by the parent's spelling
|
|
693
|
+
// only: a class of another language never derives from the owner, and a
|
|
694
|
+
// base that resolves to another definition of the name is another class.
|
|
695
|
+
// An unresolved base stays a candidate.
|
|
696
|
+
function extendsOwner(index, candidate, parentName, owner) {
|
|
697
|
+
if (nameFamily(index, candidate) !== nameFamily(index, owner)) return false;
|
|
698
|
+
const { classDefsNamed, parentRefsOf } = require('./class-identity');
|
|
699
|
+
const ownerKey = classDefsNamed(index, owner.name).entries.find(entry => entry.def === owner)?.key;
|
|
700
|
+
const childKey = classDefsNamed(index, candidate.name).entries.find(entry => entry.def === candidate)?.key;
|
|
701
|
+
if (!ownerKey || !childKey) return true;
|
|
702
|
+
const spelled = parentRefsOf(index, { name: candidate.name, key: childKey, def: candidate })
|
|
703
|
+
.filter(parent => parent.name === parentName);
|
|
704
|
+
// A base naming no class definition (a value alias of the owner) stays
|
|
705
|
+
// a candidate.
|
|
706
|
+
return !spelled.length || spelled.some(parent => !parent.external && (!parent.key || parent.key === ownerKey ||
|
|
707
|
+
classDefsNamed(index, parent.name).entries.every(entry => entry.key !== parent.key)));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Add class constructions to a constructor's ordinary, explicitly named calls. */
|
|
711
|
+
function projectConstructorCallers(index, name, options, result) {
|
|
712
|
+
if (options.constructorProjectionSource || options.constructorProjection === false) return result;
|
|
713
|
+
const targets = options.targetDefinitions || index.symbols.get(name) || [];
|
|
714
|
+
if (!targets.length || !targets.every(def => isConstructor(def, index))) return result;
|
|
715
|
+
const owners = [];
|
|
716
|
+
for (const target of targets) {
|
|
717
|
+
const owner = ownerDefinitionOf(index, target);
|
|
718
|
+
if (owner && !owners.some(other => sameDeclaration(declarationIdentity(owner), declarationIdentity(other)))) owners.push(owner);
|
|
719
|
+
}
|
|
720
|
+
for (let at = 0; at < owners.length; at++) {
|
|
721
|
+
const owner = owners[at];
|
|
722
|
+
if (!langTraits(index.files.get(owner.file)?.language)?.constructorInheritance) continue;
|
|
723
|
+
const parentNames = [owner.name, ...constructionAliasNames(index, [owner])];
|
|
724
|
+
for (const parentName of parentNames) {
|
|
725
|
+
for (const child of index.extendedByGraph.get(parentName) || []) {
|
|
726
|
+
const name = typeof child === 'string' ? child : child.name;
|
|
727
|
+
for (const candidate of index.symbols.get(name) || []) {
|
|
728
|
+
if (candidate.type !== 'class' || child.file && candidate.file !== child.file ||
|
|
729
|
+
owners.some(other => sameDeclaration(declarationIdentity(candidate), declarationIdentity(other))) ||
|
|
730
|
+
!extendsOwner(index, candidate, parentName, owner)) continue;
|
|
731
|
+
owners.push(candidate);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const promoted = [], uncertain = [], excluded = [], replaced = new Set();
|
|
737
|
+
for (const owner of owners) {
|
|
738
|
+
const language = index.files.get(owner.file).language;
|
|
739
|
+
const classCalls = classConstructionCalls(index, owner, options);
|
|
740
|
+
const rows = [...classCalls, ...(classCalls.shadowEntries || []), ...(classCalls.unverifiedEntries || [])];
|
|
741
|
+
for (const site of rows) {
|
|
742
|
+
let records = callsOnLine(index, site.file, site.line).filter(call => sameSite(call, site));
|
|
743
|
+
if (records.length > 1 && records.some(call => call.implicitBase)) records = records.filter(call => call.implicitBase &&
|
|
744
|
+
call.name === (site.calledAs || owner.name) && constructionAliasBinding(index, site.file, call, [owner]));
|
|
745
|
+
// `type(self)(..)` starts where its inner `type(self)` call does.
|
|
746
|
+
if (records.length > 1 && site.ownClassConstruction) records = records.filter(call => call.ownClass);
|
|
747
|
+
if (records.length !== 1) continue;
|
|
748
|
+
let call = records[0];
|
|
749
|
+
if (!isConstruction(call, site, language)) continue;
|
|
750
|
+
// A target-typed `new(..)` whose slot (an argument, a member it
|
|
751
|
+
// is assigned to) is resolved at query time carries no type name
|
|
752
|
+
// in its record: the class binding named it (fix #426).
|
|
753
|
+
if (call.targetTyped && call.targetSlot) {
|
|
754
|
+
call = { ...call, name: site.calledAs || owner.name };
|
|
755
|
+
}
|
|
756
|
+
replaced.add(siteKey(site));
|
|
757
|
+
const choice = constructionChoice(index, owner, call, site.provenance, targets);
|
|
758
|
+
// No project constructor body runs here, so none of the targets.
|
|
759
|
+
if (choice.noBody) continue;
|
|
760
|
+
if (choice.other) {
|
|
761
|
+
excluded.push({ file: site.file, line: site.line, reason: choice.reason || 'constructor-overload-other',
|
|
762
|
+
provenance: choice.provenance });
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const reason = site.tier === 'unverified' ? site.reason || 'constructor-owner-unresolved' : choice.reason;
|
|
766
|
+
const row = { ...site, calledAs: site.calledAs || owner.name,
|
|
767
|
+
...(call.implicitBase && { implicitConstruction: true }),
|
|
768
|
+
provenance: choice.provenance, ...(reason ? { tier: 'unverified', reason } : { tier: 'confirmed' }) };
|
|
769
|
+
(reason ? uncertain : promoted).push(row);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// A construction through an ancestor method's own receiver (`cls(..)`,
|
|
773
|
+
// `type(self)(..)`, `new this(..)`) builds the class the method runs
|
|
774
|
+
// through: possibly an owner below that ancestor (fix #434).
|
|
775
|
+
if (options.collectAccount && owners.some(owner => langTraits(index.files.get(owner.file)?.language)?.ownClassMethod)) {
|
|
776
|
+
for (const ancestor of resolvedAncestors(index, owners)) {
|
|
777
|
+
const entry = index.files.get(ancestor.file);
|
|
778
|
+
for (const call of index.getCachedCalls(ancestor.file) || []) {
|
|
779
|
+
if (call.ownClass?.name !== ancestor.name || !namedAt(ancestor, call.ownClass.line)) continue;
|
|
780
|
+
const binding = ownClassBinding(index, ancestor.file, call);
|
|
781
|
+
if (!binding || !sameDeclaration(declarationIdentity(binding.owner), declarationIdentity(ancestor))) continue;
|
|
782
|
+
const caller = index.findEnclosingFunction(ancestor.file, call.line, true, call);
|
|
783
|
+
uncertain.push({ file: ancestor.file, relativePath: entry.relativePath, line: call.line,
|
|
784
|
+
...(Number.isInteger(call.callSite?.column) && { column: call.callSite.column }),
|
|
785
|
+
content: index._getFileLines(ancestor.file)?.[call.line - 1] ?? '',
|
|
786
|
+
callerName: caller?.name || null, callerFile: caller ? ancestor.file : null,
|
|
787
|
+
callerStartLine: caller?.startLine ?? null, callerEndLine: caller?.endLine ?? null,
|
|
788
|
+
isMethod: false, calledAs: call.ownClass.spelling, ownClassConstruction: true,
|
|
789
|
+
provenance: binding.provenance, tier: 'unverified', reason: 'possible-dispatch' });
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const confirmed = [];
|
|
794
|
+
for (const site of [...result, ...(result.shadowEntries || [])].filter(site => !replaced.has(siteKey(site)))) {
|
|
795
|
+
const language = index.files.get(site.file)?.language;
|
|
796
|
+
const initializer = langTraits(language)?.constructorProtocol?.initializer;
|
|
797
|
+
const records = callsOnLine(index, site.file, site.line).filter(call => sameSite(call, site));
|
|
798
|
+
if (targets.every(target => target.name === initializer) ||
|
|
799
|
+
records.length === 1 && (records[0].isConstructor || records[0].constructorSyntax === 'delegation') &&
|
|
800
|
+
validateConfirmation(site.provenance, targets.map(declarationIdentity)).verdict === 'establishes-target') {
|
|
801
|
+
confirmed.push(site);
|
|
802
|
+
} else {
|
|
803
|
+
uncertain.push({ ...site, tier: 'unverified', reason: 'constructor-call-unproven' });
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const unverified = (result.unverifiedEntries || []).filter(site => !replaced.has(siteKey(site)));
|
|
807
|
+
const unique = rows => [...new Map(rows.map(row => [siteKey(row), row])).values()].sort(order);
|
|
808
|
+
const all = unique([...confirmed, ...promoted]);
|
|
809
|
+
const limit = options.maxResults || Infinity;
|
|
810
|
+
// A row read beyond the enrichment limit carries no line text; one that
|
|
811
|
+
// moves into the visible slice is enriched like the others (fix #421).
|
|
812
|
+
const visible = all.slice(0, limit).map(row => row.content != null ? row : {
|
|
813
|
+
...row, content: index._getFileLines(row.file)?.[row.line - 1] ?? '' });
|
|
814
|
+
result.splice(0, result.length, ...visible);
|
|
815
|
+
result.shadowEntries = all.slice(limit);
|
|
816
|
+
result.totalCount = all.length;
|
|
817
|
+
if (options.collectAccount) {
|
|
818
|
+
result.unverifiedEntries = unique([...unverified, ...uncertain]);
|
|
819
|
+
const replacedLines = new Set([...promoted, ...uncertain].map(site => `${site.file}\0${site.line}`));
|
|
820
|
+
result.accountRaw.excludedEntries = [
|
|
821
|
+
...result.accountRaw.excludedEntries.filter(site => !replacedLines.has(`${site.file}\0${site.line}`)), ...excluded,
|
|
822
|
+
];
|
|
823
|
+
result.accountRaw.unverifiedLines = result.accountRaw.unverifiedLines.filter(site =>
|
|
824
|
+
!replacedLines.has(`${site.file}\0${site.line}`));
|
|
825
|
+
}
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
module.exports = { isConstructor, ownConstructors, isConstruction, constructionChoice, constructionTypeParameter,
|
|
830
|
+
projectConstructorCallers, projectConstructorCalleeSites, constructionAliasNames, constructionAliasBinding, ownClassBinding,
|
|
831
|
+
constructionTypeCandidates, constructionTypeNames, constructionSpellingsReaching, constructionAliasPossibilities, ownMembers };
|