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.
Files changed (93) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +38 -16
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +30 -21
  6. package/cli/index.js +1 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/brief.js +8 -4
  12. package/core/build-worker.js +2 -1
  13. package/core/cache.js +137 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +4726 -940
  17. package/core/check.js +22 -5
  18. package/core/class-identity.js +399 -12
  19. package/core/constructor-calls.js +831 -0
  20. package/core/constructor-hierarchy.js +147 -0
  21. package/core/constructor-protocol.js +196 -0
  22. package/core/contract-membership.js +346 -140
  23. package/core/cpp-conversion-constraints.js +123 -0
  24. package/core/cpp-conversion-syntax.js +216 -0
  25. package/core/cpp-conversions.js +414 -0
  26. package/core/cpp-scope.js +245 -44
  27. package/core/deadcode.js +35 -15
  28. package/core/decorator-binding.js +137 -3
  29. package/core/enum-reference-syntax.js +134 -0
  30. package/core/execute.js +17 -1
  31. package/core/external-macros.js +42 -10
  32. package/core/go-packages.js +171 -0
  33. package/core/graph-build.js +2 -0
  34. package/core/graph.js +75 -59
  35. package/core/ignore-rules.js +2 -1
  36. package/core/imports.js +145 -19
  37. package/core/index-ir.js +41 -4
  38. package/core/ir.js +27 -6
  39. package/core/macro-expansion.js +38 -2
  40. package/core/out-of-class-members.js +182 -0
  41. package/core/output/analysis-ext.js +4 -2
  42. package/core/output/analysis.js +89 -20
  43. package/core/output/graph.js +12 -3
  44. package/core/output/lines.js +4 -3
  45. package/core/output/public.js +1 -0
  46. package/core/output/reporting.js +6 -1
  47. package/core/output/shared.js +2 -0
  48. package/core/output/tracing.js +28 -22
  49. package/core/output-budget.js +2 -2
  50. package/core/parallel-build.js +52 -1
  51. package/core/project.js +217 -112
  52. package/core/provenance-constructor-base.js +161 -0
  53. package/core/provenance-facts.js +257 -27
  54. package/core/provenance-overload.js +458 -54
  55. package/core/provenance-type.js +487 -0
  56. package/core/provenance.js +234 -15
  57. package/core/public-command.js +17 -2
  58. package/core/query-bodies.js +159 -0
  59. package/core/reflection.js +255 -2
  60. package/core/rename-token-syntax.js +111 -0
  61. package/core/reporting.js +13 -7
  62. package/core/rust-macro-expansion.js +151 -19
  63. package/core/rust-modules.js +1 -1
  64. package/core/rust-names.js +607 -0
  65. package/core/search.js +70 -17
  66. package/core/shared.js +98 -1
  67. package/core/source-memo.js +201 -0
  68. package/core/text-usage-syntax.js +38 -0
  69. package/core/tracing.js +7 -1
  70. package/core/type-denotation.js +124 -37
  71. package/core/type-references.js +615 -246
  72. package/core/value-references.js +2777 -0
  73. package/core/verify.js +1097 -281
  74. package/languages/adapter.js +28 -4
  75. package/languages/c-family.js +1118 -164
  76. package/languages/constructor-bases.js +105 -0
  77. package/languages/cpp-platform.js +26 -0
  78. package/languages/cpp-site-syntax.js +83 -0
  79. package/languages/csharp-scope.js +439 -0
  80. package/languages/csharp.js +428 -68
  81. package/languages/go.js +385 -207
  82. package/languages/html.js +4 -2
  83. package/languages/index.js +397 -2
  84. package/languages/java-platform.js +226 -0
  85. package/languages/java-unicode.js +269 -0
  86. package/languages/java.js +461 -117
  87. package/languages/javascript.js +418 -66
  88. package/languages/lexical-scope.js +190 -23
  89. package/languages/python.js +404 -54
  90. package/languages/rust.js +510 -82
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. package/package.json +2 -2
@@ -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 };