ucn 5.5.1 → 5.5.3
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 +40 -18
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +32 -21
- package/cli/index.js +2 -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/bridge.js +15 -65
- 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/client-receivers.js +189 -0
- 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 +61 -16
- 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/inprocess-clients.js +41 -15
- 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 +2 -0
- package/core/output/reporting.js +12 -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 +270 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/request-helpers.js +63 -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 +3 -3
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { declarationIdentity, sameDeclaration } = require('./provenance');
|
|
4
|
+
const { symbolsSpanning } = require('./shared');
|
|
5
|
+
const { factIndex, sealFacts, replayed, seedReplay, refusedProof, sharedRead } = require('./provenance-overload');
|
|
6
|
+
const { BoundedMap, SourceMemo, parsedSource, typeNamesCanonical, indexedContent } = require('./source-memo');
|
|
7
|
+
|
|
8
|
+
// Modules read lazily (load cycles), each resolved once: a require() per
|
|
9
|
+
// call in hot paths was measurable (fix #436).
|
|
10
|
+
let lazyCppScopeModuleCache = null;
|
|
11
|
+
const lazyCppScopeModule = () => lazyCppScopeModuleCache || (lazyCppScopeModuleCache = require('./cpp-scope'));
|
|
12
|
+
let lazyCallableScopeModuleCache = null;
|
|
13
|
+
const lazyCallableScopeModule = () => lazyCallableScopeModuleCache || (lazyCallableScopeModuleCache = require('./callable-scope'));
|
|
14
|
+
let lazyLanguagesModuleCache = null;
|
|
15
|
+
const lazyLanguagesModule = () => lazyLanguagesModuleCache || (lazyLanguagesModuleCache = require('../languages'));
|
|
16
|
+
|
|
17
|
+
// Type lookup replays the same package, namespace and include rules as a
|
|
18
|
+
// type reference. The witness holds declarations and scope facts, rather
|
|
19
|
+
// than a recorded resolution verdict or access to the live filesystem.
|
|
20
|
+
const FILE_FIELDS = ['language', 'relativePath', 'symbols', 'importBindings', 'moduleResolved',
|
|
21
|
+
'importDetails', 'importAliases', 'exportDetails', 'cppUsings', 'macroScopeMarkers',
|
|
22
|
+
'moduleAssignedNames', 'moduleValueAliases', 'packageName', 'bindings', 'typeParameterNames'];
|
|
23
|
+
|
|
24
|
+
// Type-parameter scopes of a source, and the name tokens of each line a
|
|
25
|
+
// query asked about, by language. Lines are read from the shared parse on
|
|
26
|
+
// demand (fix #418: a token map of the whole file was built for one site).
|
|
27
|
+
const nameScopes = new SourceMemo({ maxEntries: 256, maxWeight: 32 * 1024 * 1024,
|
|
28
|
+
weigh: facts => (facts.lines.size * 4 + facts.parameters.length) * 64 });
|
|
29
|
+
const LOOKUP_MEMO_MAX_ENTRIES = 16384;
|
|
30
|
+
const TYPE_PARAMETER_PARENTS = new Set(require('../languages/utils').TYPE_PARAMETER_PARENTS);
|
|
31
|
+
const NAME_TOKENS = [...require('../languages/utils').TYPE_PARAMETER_NAME_TOKENS];
|
|
32
|
+
const NAME_TOKEN_SET = new Set(NAME_TOKENS);
|
|
33
|
+
const atOrigin = point => point.row === 0 && point.column === 0;
|
|
34
|
+
|
|
35
|
+
function nameScopeFacts(language, source) {
|
|
36
|
+
let facts = nameScopes.get(language, source);
|
|
37
|
+
if (facts !== undefined) return facts;
|
|
38
|
+
const tree = parsedSource(language, source);
|
|
39
|
+
if (!tree) return null;
|
|
40
|
+
const { referenceRole, referenceTypeParameterScope } = require('./type-references');
|
|
41
|
+
facts = { lines: new Map(), parameters: [] };
|
|
42
|
+
// A name token is a declared type parameter only under one of these
|
|
43
|
+
// declaration-shaped parents; the classifier still distinguishes its
|
|
44
|
+
// name from bounds and defaults. A zero-width token at the origin is not
|
|
45
|
+
// a name token of the file (a whole-tree token walk starts there).
|
|
46
|
+
for (const parent of tree.rootNode.descendantsOfType([...TYPE_PARAMETER_PARENTS])) {
|
|
47
|
+
for (const node of parent.children) {
|
|
48
|
+
if (!NAME_TOKEN_SET.has(node.type) || atOrigin(node.endPosition)) continue;
|
|
49
|
+
if (!referenceRole(node, language)?.typeParam) continue;
|
|
50
|
+
const scope = referenceTypeParameterScope(node);
|
|
51
|
+
if (scope) facts.parameters.push({ name: node.text, start: scope.startIndex, end: scope.endIndex });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
nameScopes.set(language, source, facts);
|
|
55
|
+
return facts;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The innermost name token starting at (line, column): its source range. */
|
|
59
|
+
function nameTokenAt(facts, language, source, line, column) {
|
|
60
|
+
let sites = facts.lines.get(line);
|
|
61
|
+
if (!sites) {
|
|
62
|
+
const tree = parsedSource(language, source);
|
|
63
|
+
if (!tree) return null;
|
|
64
|
+
sites = new Map();
|
|
65
|
+
// Every node starting on the line, in tree order: the last token at
|
|
66
|
+
// a column is the innermost one.
|
|
67
|
+
const from = line > 1 ? { row: line - 2, column: 0xffffffff } : { row: 0, column: 0 };
|
|
68
|
+
for (const node of tree.rootNode.descendantsOfType(NAME_TOKENS, from, { row: line, column: 0 })) {
|
|
69
|
+
if (node.startPosition.row === line - 1) sites.set(node.startPosition.column, [node.startIndex, node.endIndex]);
|
|
70
|
+
}
|
|
71
|
+
// A file queried at very many lines keeps answering without
|
|
72
|
+
// retaining every line.
|
|
73
|
+
if (facts.lines.size < 4096) facts.lines.set(line, sites);
|
|
74
|
+
}
|
|
75
|
+
return sites.get(column) || null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// A type declaration's header line: its name and supertypes are read there
|
|
79
|
+
// (fix #421: a parse error inside a member does not reach them).
|
|
80
|
+
const headerLines = def => ({ startLine: def.nameLine || def.startLine, endLine: def.nameLine || def.startLine });
|
|
81
|
+
|
|
82
|
+
function constructionNameScope(index, query) {
|
|
83
|
+
const entry = index.files.get(query.file);
|
|
84
|
+
const language = entry?.language;
|
|
85
|
+
const source = index._readFile(query.file);
|
|
86
|
+
const canonical = lazyLanguagesModule().langTraits(language)?.typeNameSourceCanonical;
|
|
87
|
+
if (canonical && !typeNamesCanonical(index, query.file, canonical, language, { startLine: query.line, endLine: query.line })) return null;
|
|
88
|
+
// A name the file declares as no type parameter anywhere names none at
|
|
89
|
+
// the site (fix #421): the indexed declaration names answer without a
|
|
90
|
+
// second parse of the file.
|
|
91
|
+
const written = query.qualifier || '';
|
|
92
|
+
const named = written ? written.split(/::|\./)[0] : query.name;
|
|
93
|
+
const declared = entry?.typeParameterNames;
|
|
94
|
+
if (Array.isArray(declared) && (!named || !declared.includes(named))) return true;
|
|
95
|
+
const facts = nameScopeFacts(language, source);
|
|
96
|
+
if (!facts) return null;
|
|
97
|
+
const site = nameTokenAt(facts, language, source, query.line, query.column);
|
|
98
|
+
if (!site) return null;
|
|
99
|
+
const qualifier = query.qualifier || '';
|
|
100
|
+
if (qualifier.startsWith('::') || qualifier.startsWith('global::')) return true;
|
|
101
|
+
const root = qualifier ? qualifier.split(/::|\./)[0] : query.name;
|
|
102
|
+
return !facts.parameters.some(parameter => parameter.name === root &&
|
|
103
|
+
parameter.start <= site[0] && parameter.end >= site[1]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function localConstructionBinding(index, query, definition, traits, language) {
|
|
107
|
+
if (query.file !== definition.file || query.name !== definition.name) return false;
|
|
108
|
+
const { getParser } = lazyLanguagesModule();
|
|
109
|
+
const config = traits.constructorInheritance;
|
|
110
|
+
const source = index._readFile(query.file);
|
|
111
|
+
const content = config?.source ? config.source(source, getParser(language)) : source;
|
|
112
|
+
const syntax = config?.syntaxLanguage || language;
|
|
113
|
+
const tree = parsedSource(syntax, content);
|
|
114
|
+
if (!tree) return false;
|
|
115
|
+
{
|
|
116
|
+
const nodes = tree.rootNode.descendantsOfType('identifier').filter(node => node.text === query.name &&
|
|
117
|
+
node.startPosition.row + 1 === query.line && node.startPosition.column === query.column);
|
|
118
|
+
if (nodes.length !== 1) return false;
|
|
119
|
+
const scope = require('../languages/lexical-scope').referenceScope(nodes[0], syntax, new Map(), query.name,
|
|
120
|
+
{ includeClasses: true });
|
|
121
|
+
return !!scope?.local && scope.defRows?.includes(definition.startLine - 1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Explicit construction still performs lookup in its enclosing class. The
|
|
126
|
+
// recorded declaration extents supply that context without building a type
|
|
127
|
+
// rename group. Equal or unreadable source scopes cannot select an owner.
|
|
128
|
+
function constructionClassAt(index, file, line, column) {
|
|
129
|
+
const { containsCall } = lazyCallableScopeModule();
|
|
130
|
+
let width = Infinity;
|
|
131
|
+
const owners = new Set();
|
|
132
|
+
for (const definition of symbolsSpanning(index.files.get(file)?.symbols, line)) {
|
|
133
|
+
const type = ['class', 'struct', 'union', 'enum', 'record'].includes(definition.type);
|
|
134
|
+
const owner = type
|
|
135
|
+
? [definition.enclosingType, definition.name].filter(Boolean).join('::')
|
|
136
|
+
: definition.className;
|
|
137
|
+
if (!owner) continue;
|
|
138
|
+
const contains = containsCall(definition, { line, column });
|
|
139
|
+
if (contains === false) continue;
|
|
140
|
+
if (Number.isInteger(column) && contains !== true) return undefined;
|
|
141
|
+
const span = definition.sourceSpan;
|
|
142
|
+
const size = span ? span.end - span.start : definition.endLine - definition.startLine;
|
|
143
|
+
if (size < width) { width = size; owners.clear(); }
|
|
144
|
+
if (size === width) owners.add(owner);
|
|
145
|
+
}
|
|
146
|
+
return owners.size === 1 ? [...owners][0] : owners.size === 0 ? null : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function cppConstructionClasses(index, query, resolver = null) {
|
|
150
|
+
// A construction site is looked up once per operation, whichever query
|
|
151
|
+
// projects it (fix #418). A witness view records its own lookup.
|
|
152
|
+
const memo = !query.position && !index.isProvenanceFactIndex
|
|
153
|
+
? index._opMemo?.('cppConstructionClasses', () => new Map()) : null;
|
|
154
|
+
const key = memo ? `${query.file}\0${query.line}\0${query.column ?? ''}\0${query.name}\0${query.qualifier ?? ''}` : null;
|
|
155
|
+
if (memo?.has(key)) return memo.get(key);
|
|
156
|
+
const classes = query.position ? cppConstructionClassesUncached(index, query, resolver)
|
|
157
|
+
: sharedRead(index, JSON.stringify(['cpp-construction-classes', query.file, query.line,
|
|
158
|
+
query.column, query.name, query.qualifier]),
|
|
159
|
+
view => cppConstructionClassesUncached(view, query, null));
|
|
160
|
+
memo?.set(key, classes);
|
|
161
|
+
return classes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function cppConstructionClassesUncached(index, query, resolver) {
|
|
165
|
+
const { resolveQualifier, includeClosure, namespaceAt } = lazyCppScopeModule();
|
|
166
|
+
const { file, line, name, qualifier } = query;
|
|
167
|
+
let context;
|
|
168
|
+
if (query.position) context = resolver.cppSiteContext(file, line);
|
|
169
|
+
else {
|
|
170
|
+
const className = constructionClassAt(index, file, line, query.column);
|
|
171
|
+
if (className === undefined) return [];
|
|
172
|
+
context = { namespace: namespaceAt(index, file, line), className };
|
|
173
|
+
}
|
|
174
|
+
const found = resolveQualifier(index, { file, line, ...context },
|
|
175
|
+
qualifier ? `${qualifier}::${name}` : name, includeClosure(index, file));
|
|
176
|
+
return found.kind === 'type' && found.visible ? found.classes : [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function constructionDenotes(index, resolver, query, definition, seen = new Set()) {
|
|
180
|
+
const { file, line, name, qualifier } = query;
|
|
181
|
+
const entry = index.files.get(file);
|
|
182
|
+
if (!entry || seen.size >= 12) return false;
|
|
183
|
+
const key = `${file}\0${qualifier || ''}\0${name}`;
|
|
184
|
+
if (seen.has(key)) return false;
|
|
185
|
+
const visited = new Set(seen).add(key);
|
|
186
|
+
const traits = lazyLanguagesModule().langTraits(entry.language);
|
|
187
|
+
if (query.localShadow && !localConstructionBinding(index, query, definition, traits, entry.language)) return false;
|
|
188
|
+
if (traits.constructorInheritance?.order === 'single' && definition.decorators?.length) return false;
|
|
189
|
+
if (traits.functionalTypeConstruction) {
|
|
190
|
+
const classes = cppConstructionClasses(index, query, resolver);
|
|
191
|
+
return classes.length > 0 && classes.every(def =>
|
|
192
|
+
sameDeclaration(declarationIdentity(def), declarationIdentity(definition)));
|
|
193
|
+
}
|
|
194
|
+
const answer = qualifier ? resolver.resolveQualified(file, line, null, qualifier, { role: 'type' })
|
|
195
|
+
: resolver.resolveName(file, line, name);
|
|
196
|
+
if (traits.typeNameScoping === 'namespace-usings' && answer.reason === 'using-alias') {
|
|
197
|
+
const { csharpUsings } = require('./type-denotation');
|
|
198
|
+
const namespace = resolver.csharpSiteNamespace(file, line);
|
|
199
|
+
if (namespace === undefined) return false;
|
|
200
|
+
const scope = csharpUsings(index, file, namespace);
|
|
201
|
+
if (scope.ambiguousAliases.has(name)) return false;
|
|
202
|
+
const syntax = scope.aliasSyntax.get(name);
|
|
203
|
+
const arity = require('../languages/utils').genericArityOf(syntax);
|
|
204
|
+
if (arity == null) return false;
|
|
205
|
+
if (syntax.includes('<')) {
|
|
206
|
+
let depth = 0, end = -1;
|
|
207
|
+
for (let at = syntax.indexOf('<'); at < syntax.length; at++) {
|
|
208
|
+
if (syntax[at] === '<') depth++;
|
|
209
|
+
else if (syntax[at] === '>' && --depth === 0) { end = at; break; }
|
|
210
|
+
}
|
|
211
|
+
if (end !== syntax.length - 1) return false;
|
|
212
|
+
}
|
|
213
|
+
resolver.siteSyntax = { ...resolver.siteSyntax, arity };
|
|
214
|
+
const target = scope.aliases.get(name);
|
|
215
|
+
if (!target || target.split('.').pop() !== definition.name) return false;
|
|
216
|
+
const prefix = target.split('.').slice(0, -1).join('.');
|
|
217
|
+
return prefix ? resolver.resolveQualified(file, line, null, `global::${prefix}`, { role: 'type' }).verdict === 'yes'
|
|
218
|
+
: resolver.resolveName(file, line, definition.name).verdict === 'yes';
|
|
219
|
+
}
|
|
220
|
+
if (traits.typeSystem !== 'structural') return answer.verdict === 'yes';
|
|
221
|
+
// Value aliases bind the value of their initializer, in its own module.
|
|
222
|
+
// Type-reference lookup intentionally keeps the alias token's identity.
|
|
223
|
+
const moduleFile = module => {
|
|
224
|
+
const relative = entry.moduleResolved?.[module];
|
|
225
|
+
return relative && require('path').resolve(index.root, relative);
|
|
226
|
+
};
|
|
227
|
+
if (qualifier) {
|
|
228
|
+
const bindings = (entry.importBindings || []).filter(b => (b.alias || b.name) === qualifier);
|
|
229
|
+
if (bindings.length !== 1) return false;
|
|
230
|
+
const target = moduleFile(`${bindings[0].module}.${bindings[0].name}`) || moduleFile(bindings[0].module);
|
|
231
|
+
return !!target && constructionDenotes(index, resolver, { file: target, line: 1, name }, definition, visited);
|
|
232
|
+
}
|
|
233
|
+
const aliases = (entry.moduleValueAliases || []).filter(alias => alias.name === name);
|
|
234
|
+
if (aliases.length) {
|
|
235
|
+
return aliases.every(alias => {
|
|
236
|
+
const alternatives = alias.alternatives || [{ target: alias.target }];
|
|
237
|
+
return alternatives.length > 0 && alternatives.every(alternative => {
|
|
238
|
+
if (alternative.target) {
|
|
239
|
+
const parts = alternative.target.split('.'), targetName = parts.pop();
|
|
240
|
+
return constructionDenotes(index, resolver, { file, line: alias.line || line,
|
|
241
|
+
name: targetName, qualifier: parts.join('.') || null }, definition, visited);
|
|
242
|
+
}
|
|
243
|
+
const target = moduleFile(alternative.module);
|
|
244
|
+
return !!target && !!alternative.imported && constructionDenotes(index, resolver,
|
|
245
|
+
{ file: target, line: 1, name: alternative.imported }, definition, visited);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (answer.verdict === 'yes') {
|
|
250
|
+
// A value declaration sharing a type's name can replace its value.
|
|
251
|
+
const shadows = (entry.symbols || []).filter(def => def.name === name && !def.className &&
|
|
252
|
+
!def.lexicalScopeStartLine && !['class', 'struct', 'interface', 'type'].includes(def.type));
|
|
253
|
+
return !shadows.length && !(entry.moduleAssignedNames || []).includes(name);
|
|
254
|
+
}
|
|
255
|
+
const links = [...(entry.importBindings || []).filter(b => (b.alias || b.name) === name &&
|
|
256
|
+
b.name !== '*' && b.kind !== 'namespace'), ...(entry.exportDetails || []).filter(e =>
|
|
257
|
+
e.type === 're-export' && (e.alias || e.name) === name && e.source)];
|
|
258
|
+
if (links.length !== 1) return false;
|
|
259
|
+
const target = moduleFile(links[0].module || links[0].source);
|
|
260
|
+
return !!target && constructionDenotes(index, resolver, { file: target, line: 1,
|
|
261
|
+
name: links[0].name }, definition, visited);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// A construction through a method's own receiver (fix #434): the class
|
|
265
|
+
// whose method holds the site, by its recorded name line, with the method
|
|
266
|
+
// and receiver binding its language gives such a construction (`cls` of a
|
|
267
|
+
// classmethod, `this` of a static method, `type(self)` of an instance
|
|
268
|
+
// method). No class declared between them holds the method.
|
|
269
|
+
const OWN_CLASS_KINDS = new Set(['class', 'struct', 'record']);
|
|
270
|
+
function ownClassDenotes(index, query, owner) {
|
|
271
|
+
const own = query.ownClass;
|
|
272
|
+
const entry = index.files.get(query.file);
|
|
273
|
+
const ownClassMethod = lazyLanguagesModule().langTraits(entry?.language)?.ownClassMethod;
|
|
274
|
+
if (!own || !ownClassMethod || !entry) return false;
|
|
275
|
+
const symbols = entry.symbols || [];
|
|
276
|
+
const named = (def, line) => def.nameLine === line || def.startLine === line;
|
|
277
|
+
const holds = (outer, inner) => outer.startLine <= inner.startLine && outer.endLine >= inner.endLine;
|
|
278
|
+
const definition = symbols.find(def => OWN_CLASS_KINDS.has(def.type) &&
|
|
279
|
+
sameDeclaration(declarationIdentity(def), owner));
|
|
280
|
+
if (!definition || definition.name !== own.name || !named(definition, own.line)) return false;
|
|
281
|
+
const method = symbols.find(def => def.className === definition.name && def.name === own.method &&
|
|
282
|
+
named(def, own.methodLine) && holds(definition, def));
|
|
283
|
+
if (!method || query.line < method.startLine || query.line > method.endLine) return false;
|
|
284
|
+
if (symbols.some(def => def !== definition && OWN_CLASS_KINDS.has(def.type) && holds(definition, def) &&
|
|
285
|
+
holds(def, method) && !(def.startLine === definition.startLine && def.endLine === definition.endLine))) return false;
|
|
286
|
+
return !!ownClassMethod(method, own.binding, own.receiver);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function resolve(index, query, owner) {
|
|
290
|
+
if (query.ownClass) return ownClassDenotes(index, query, owner) && !index.hasMissingProvenanceFacts;
|
|
291
|
+
if (query.nameScopeOnly) {
|
|
292
|
+
if (!constructionNameScope(index, query)) return false;
|
|
293
|
+
const language = index.files.get(query.file)?.language;
|
|
294
|
+
const canonical = lazyLanguagesModule().langTraits(language)?.typeNameSourceCanonical;
|
|
295
|
+
if (canonical) {
|
|
296
|
+
const file = owner.file === query.relativePath ? query.file
|
|
297
|
+
: require('path').resolve(index.root, owner.file);
|
|
298
|
+
if (!typeNamesCanonical(index, file, canonical, language, headerLines(owner))) return false;
|
|
299
|
+
}
|
|
300
|
+
return !index.hasMissingProvenanceFacts;
|
|
301
|
+
}
|
|
302
|
+
index._classIdentity = null;
|
|
303
|
+
index._cppScope = null;
|
|
304
|
+
index._typeDenotation = null;
|
|
305
|
+
const definition = (index.symbols.get(owner.name) || []).find(def =>
|
|
306
|
+
sameDeclaration(declarationIdentity(def), owner));
|
|
307
|
+
if (!definition) return false;
|
|
308
|
+
const { langTraits } = lazyLanguagesModule();
|
|
309
|
+
const definitionLanguage = index.files.get(definition.file)?.language;
|
|
310
|
+
const canonical = langTraits(definitionLanguage)?.typeNameSourceCanonical;
|
|
311
|
+
if (canonical && (!typeNamesCanonical(index, query.file, canonical, definitionLanguage, { startLine: query.line, endLine: query.line }) ||
|
|
312
|
+
!typeNamesCanonical(index, definition.file, canonical, definitionLanguage, headerLines(definition)))) return false;
|
|
313
|
+
// Explicit C++ construction uses its type lookup directly. Building a
|
|
314
|
+
// rename group and an unused ordinary-name answer would read unrelated
|
|
315
|
+
// declarations; functional calls still need their ordinary binding proof.
|
|
316
|
+
if (query.construction && langTraits(index.files.get(query.file)?.language)?.functionalTypeConstruction &&
|
|
317
|
+
!query.functional && !query.position) {
|
|
318
|
+
return constructionDenotes(index, null, query, definition) && !index.hasMissingProvenanceFacts;
|
|
319
|
+
}
|
|
320
|
+
const { TypeReferenceResolver } = require('./type-references');
|
|
321
|
+
const resolver = new TypeReferenceResolver(index, definition, owner.name);
|
|
322
|
+
if (Number.isInteger(query.arity)) resolver.siteSyntax = { arity: query.arity };
|
|
323
|
+
if (query.position) {
|
|
324
|
+
const site = positionedSite(index, query);
|
|
325
|
+
if (!site) return false;
|
|
326
|
+
resolver.siteNode = site.node; resolver.siteFile = query.file;
|
|
327
|
+
resolver.siteContextSyntax = site.record;
|
|
328
|
+
}
|
|
329
|
+
return resolveAt(index, query, owner, definition, resolver);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolveAt(index, query, owner, definition, resolver) {
|
|
333
|
+
// Construction has its own scoped lookup. The ordinary-name answer is
|
|
334
|
+
// unused there and would record unrelated facts in every witness.
|
|
335
|
+
const denotes = query.construction ? constructionDenotes(index, resolver, query, definition)
|
|
336
|
+
: (query.qualifier
|
|
337
|
+
? resolver.resolveQualified(query.file, query.line, null, query.qualifier, { role: 'type' })
|
|
338
|
+
: resolver.resolveName(query.file, query.line, query.name)).verdict === 'yes';
|
|
339
|
+
if (!denotes || index.hasMissingProvenanceFacts) return false;
|
|
340
|
+
if (query.functional) {
|
|
341
|
+
if (!lazyLanguagesModule().langTraits(index.files.get(query.file)?.language)?.functionalTypeConstruction) return false;
|
|
342
|
+
const visible = lazyCppScopeModule().includeClosure(index, query.file);
|
|
343
|
+
const named = index.symbols.get(query.name) || [];
|
|
344
|
+
let boundType = false;
|
|
345
|
+
for (const file of visible) {
|
|
346
|
+
for (const binding of index.files.get(file)?.bindings || []) {
|
|
347
|
+
if (binding.name !== query.name) continue;
|
|
348
|
+
const declaration = named.find(def => def.bindingId === binding.id);
|
|
349
|
+
if (!declaration) return false;
|
|
350
|
+
// The construction's own scoped lookup already proved the
|
|
351
|
+
// denoted class. Same-spelled aliases in other classes do
|
|
352
|
+
// not compete in that lookup; ordinary value bindings still
|
|
353
|
+
// require the checks below.
|
|
354
|
+
if (resolver.inGroup(declaration) || declaration.type === 'type' && query.construction) {
|
|
355
|
+
boundType = true; continue;
|
|
356
|
+
}
|
|
357
|
+
if (declaration.type !== 'constructor' || declaration.className !== owner.name ||
|
|
358
|
+
declaration.relativePath !== owner.file) return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (!boundType) return false;
|
|
362
|
+
}
|
|
363
|
+
return !index.hasMissingProvenanceFacts;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// A matching source-only record avoids reparsing a whole file for one base
|
|
367
|
+
// clause. Witnesses still read the source, and copied replays derive the
|
|
368
|
+
// node from it without consulting a live index or a recorded lookup verdict.
|
|
369
|
+
function positionedSite(index, query) {
|
|
370
|
+
const language = index.files.get(query.file)?.language;
|
|
371
|
+
const source = index._readFile(query.file);
|
|
372
|
+
const at = query.position;
|
|
373
|
+
const matches = row => row.start === at.start && row.end === at.end && row.nodeType === at.nodeType &&
|
|
374
|
+
row.line === query.line && row.column === query.column;
|
|
375
|
+
if (lazyLanguagesModule().langTraits(language)?.functionalTypeConstruction) {
|
|
376
|
+
const hint = live => {
|
|
377
|
+
const entry = live.files.get(query.file);
|
|
378
|
+
if (!Array.isArray(entry?.cppBaseSyntax) || !indexedContent(live, query.file, entry, source)) return null;
|
|
379
|
+
return entry.cppBaseSyntax.find(matches) || null;
|
|
380
|
+
};
|
|
381
|
+
const record = index.isProvenanceFactIndex ? index.unrecordedHint(hint) : hint(index);
|
|
382
|
+
if (record) return record.syntax ? { record, node: null } : null;
|
|
383
|
+
}
|
|
384
|
+
const tree = parsedSource(language, source);
|
|
385
|
+
let node = tree?.rootNode.descendantForIndex(at.start);
|
|
386
|
+
while (node && !(node.startIndex === at.start && node.endIndex === at.end && node.type === at.nodeType)) node = node.parent;
|
|
387
|
+
return node && node.startPosition.row + 1 === query.line && node.startPosition.column === query.column
|
|
388
|
+
? { node } : null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// The classes a C/C++ construction at a source position names (an omitted
|
|
392
|
+
// base initializer's header), looked up once per operation on the live
|
|
393
|
+
// index. A position lookup reads the site's tree node and the index, never
|
|
394
|
+
// the pinned type, so every owner a query tries shares it. null when the
|
|
395
|
+
// recorded position no longer names a node (the lookup fails for every
|
|
396
|
+
// owner).
|
|
397
|
+
function cppPositionConstructionClasses(index, query) {
|
|
398
|
+
const memo = index._opMemo?.('cppPositionConstructionClasses', () => new Map());
|
|
399
|
+
const at = query.position;
|
|
400
|
+
const key = `${query.file}\0${query.line}\0${query.column}\0${query.name}\0${query.qualifier ?? ''}\0` +
|
|
401
|
+
`${at.start}\0${at.end}\0${at.nodeType}`;
|
|
402
|
+
if (memo?.has(key)) return memo.get(key);
|
|
403
|
+
const site = positionedSite(index, query);
|
|
404
|
+
let classes = null;
|
|
405
|
+
if (site) {
|
|
406
|
+
const { resolveQualifier, includeClosure } = lazyCppScopeModule();
|
|
407
|
+
const context = require('./type-references').cppSiteContextOf(index, site.node, query.file, query.file,
|
|
408
|
+
query.line, site.record?.syntax);
|
|
409
|
+
const found = resolveQualifier(index, { file: query.file, line: query.line, ...context },
|
|
410
|
+
query.qualifier ? `${query.qualifier}::${query.name}` : query.name, includeClosure(index, query.file));
|
|
411
|
+
classes = found.kind === 'type' && found.visible ? found.classes : [];
|
|
412
|
+
}
|
|
413
|
+
memo?.set(key, classes);
|
|
414
|
+
return classes;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// A C/C++ construction lookup holds only when the site's classes are
|
|
418
|
+
// exactly the owner. This necessary condition does not depend on the rename
|
|
419
|
+
// group or functional-call binding check; a failed lookup cannot capture a
|
|
420
|
+
// witness for this owner. Keep the caller's ordinary unverified result.
|
|
421
|
+
function cppConstructionDenied(index, query, identity) {
|
|
422
|
+
if (index.isProvenanceFactIndex || !query.construction ||
|
|
423
|
+
query.ownClass || query.nameScopeOnly || query.localShadow) return false;
|
|
424
|
+
if (!lazyLanguagesModule().langTraits(index.files.get(query.file)?.language)?.functionalTypeConstruction) return false;
|
|
425
|
+
const classes = query.position ? cppPositionConstructionClasses(index, query) : cppConstructionClasses(index, query);
|
|
426
|
+
return !classes || !(classes.length > 0 && classes.every(def => sameDeclaration(declarationIdentity(def), identity)));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function captureTypeLookup(index, query, owner) {
|
|
430
|
+
const identity = declarationIdentity(owner);
|
|
431
|
+
// A rename visits one construction once for each slot member. Its type
|
|
432
|
+
// binding depends on the complete query and the operation's index view.
|
|
433
|
+
// Every caller owns its witness: the sealed facts are shared, the query
|
|
434
|
+
// and owner are copies (fix #418: cloning the facts per hit dominated
|
|
435
|
+
// constructor plans).
|
|
436
|
+
let memo = index?.isProvenanceFactIndex ? null
|
|
437
|
+
: index?._opMemo?.('constructionTypeLookups', () => new BoundedMap({ maxEntries: LOOKUP_MEMO_MAX_ENTRIES }));
|
|
438
|
+
let key = null;
|
|
439
|
+
if (memo) {
|
|
440
|
+
// Key order does not change a lookup (fix #421: the class binding
|
|
441
|
+
// and the constructor proof spell one site's query in two orders).
|
|
442
|
+
try { key = JSON.stringify([Object.fromEntries(Object.entries(query).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))), identity]); }
|
|
443
|
+
catch { memo = null; }
|
|
444
|
+
}
|
|
445
|
+
if (memo?.has(key)) {
|
|
446
|
+
const saved = memo.get(key);
|
|
447
|
+
if (!saved) return null;
|
|
448
|
+
// The replay of this caller's spelling is the saved capture's.
|
|
449
|
+
seedReplay(saved.reads, JSON.stringify(['type-lookup', query, identity]), true);
|
|
450
|
+
return { query: globalThis.structuredClone(query), owner: { ...saved.owner }, reads: saved.reads };
|
|
451
|
+
}
|
|
452
|
+
if (cppConstructionDenied(index, query, identity)) {
|
|
453
|
+
memo?.set(key, null);
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const reads = {};
|
|
457
|
+
let result = null;
|
|
458
|
+
try {
|
|
459
|
+
if (resolve(factIndex(reads, index, FILE_FIELDS), query, identity)) {
|
|
460
|
+
result = { query, owner: identity, reads: sealFacts(reads) };
|
|
461
|
+
seedReplay(result.reads, JSON.stringify(['type-lookup', query, identity]), true);
|
|
462
|
+
}
|
|
463
|
+
} catch (error) { refusedProof(error); /* An unprovable binding keeps its ordinary review result. */ }
|
|
464
|
+
if (memo) {
|
|
465
|
+
try {
|
|
466
|
+
memo.set(key, result && { query: globalThis.structuredClone(query), owner: { ...identity }, reads: result.reads });
|
|
467
|
+
} catch { /* A non-data extension can still use the uncached path. */ }
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function validateTypeLookup(witness, owner, site) {
|
|
473
|
+
if (!witness || !sameDeclaration(witness.owner, owner) ||
|
|
474
|
+
witness.query?.relativePath !== site?.file || witness.query.line !== site?.line ||
|
|
475
|
+
witness.query.column !== site?.column) return false;
|
|
476
|
+
if (witness.query.position && (witness.query.position.start !== site.start || witness.query.position.end !== site.end)) return false;
|
|
477
|
+
let key;
|
|
478
|
+
try { key = JSON.stringify(['type-lookup', witness.query, owner]); }
|
|
479
|
+
catch { return false; }
|
|
480
|
+
return replayed(witness.reads, key, () => {
|
|
481
|
+
try { return resolve(factIndex(witness.reads, undefined, FILE_FIELDS), witness.query, owner); }
|
|
482
|
+
catch (error) { return refusedProof(error, false); }
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
module.exports = { captureTypeLookup, validateTypeLookup, resolveTypeLookup: resolve,
|
|
487
|
+
constructionNameScope, cppConstructionClasses, FILE_FIELDS };
|