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
package/core/cpp-scope.js
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* Everything is memoized on the index (`_cppScope`) and reset at build/load.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
const { codeUnitCompare } = require('./shared');
|
|
26
|
+
const { codeUnitCompare, isValueDefinition, symbolsSpanning } = require('./shared');
|
|
27
|
+
const { projectFact, sharedRead } = require('./provenance-overload');
|
|
27
28
|
|
|
28
29
|
const TYPE_KINDS = new Set(['class', 'struct', 'union', 'enum', 'interface']);
|
|
29
30
|
const MAX_ALIAS_HOPS = 8;
|
|
@@ -42,6 +43,19 @@ function _state(index) {
|
|
|
42
43
|
return state;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Source-derived conversion declarations share this build's invalidation.
|
|
47
|
+
function conversionSyntaxMemo(index) {
|
|
48
|
+
const state = _state(index);
|
|
49
|
+
return state.conversionSyntax || (state.conversionSyntax = new Map());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Live queries can reuse pure conversion results until index facts change.
|
|
53
|
+
// Witness views recompute from their recorded raw declarations instead.
|
|
54
|
+
function conversionResultMemo(index) {
|
|
55
|
+
const state = _state(index);
|
|
56
|
+
return state.conversionResults || (state.conversionResults = new Map());
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
function resetCppScope(index) {
|
|
46
60
|
index._cppScope = null;
|
|
47
61
|
}
|
|
@@ -50,22 +64,40 @@ function _isCppFile(index, file) {
|
|
|
50
64
|
return index.files.get(file)?.language === 'cpp';
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
// Lookup consumes declaration inventories by kind. Record that raw subset
|
|
68
|
+
// so a type or macro query does not copy unrelated same-name value members
|
|
69
|
+
// into every overload witness. Scope and visibility still replay below.
|
|
70
|
+
function _declarationsOfKind(index, name, kind) {
|
|
71
|
+
return projectFact(index, `cpp-${kind}-declarations:${name}`, live => {
|
|
72
|
+
const state = _state(live);
|
|
73
|
+
const memo = state.declarationKinds || (state.declarationKinds = new Map());
|
|
74
|
+
const key = `${kind}\0${name}`;
|
|
75
|
+
if (!memo.has(key)) memo.set(key, (live.symbols.get(name) || []).filter(definition => kind === 'macro'
|
|
76
|
+
? definition.type === 'macro'
|
|
77
|
+
: _isCppFile(live, definition.file) && (TYPE_KINDS.has(definition.type) ||
|
|
78
|
+
definition.type === 'type' && definition.aliasOf)));
|
|
79
|
+
return memo.get(key);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
/** Files a translation unit rooted at `file` sees through its includes. */
|
|
54
84
|
function includeClosure(index, file) {
|
|
55
85
|
const state = _state(index);
|
|
56
86
|
if (!state.closures) state.closures = new Map();
|
|
57
87
|
const cached = state.closures.get(file);
|
|
58
88
|
if (cached) return cached;
|
|
59
|
-
const visible =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
const visible = sharedRead(index, `cpp-include-closure:${file}`, view => {
|
|
90
|
+
const seen = new Set([file]);
|
|
91
|
+
const queue = [file];
|
|
92
|
+
for (let i = 0; i < queue.length; i++) {
|
|
93
|
+
for (const imported of view.importGraph?.get(queue[i]) || []) {
|
|
94
|
+
if (seen.has(imported)) continue;
|
|
95
|
+
seen.add(imported);
|
|
96
|
+
queue.push(imported);
|
|
97
|
+
}
|
|
67
98
|
}
|
|
68
|
-
|
|
99
|
+
return seen;
|
|
100
|
+
});
|
|
69
101
|
state.closures.set(file, visible);
|
|
70
102
|
return visible;
|
|
71
103
|
}
|
|
@@ -83,8 +115,8 @@ function _macroEffect(index, name, file) {
|
|
|
83
115
|
const visible = includeClosure(index, file);
|
|
84
116
|
let effect = null;
|
|
85
117
|
let key = null;
|
|
86
|
-
for (const definition of index
|
|
87
|
-
if (
|
|
118
|
+
for (const definition of _declarationsOfKind(index, name, 'macro')) {
|
|
119
|
+
if (!visible.has(definition.file)) continue;
|
|
88
120
|
const scope = definition.namespaceScope;
|
|
89
121
|
const scopeKey = scope ? JSON.stringify(scope) : '';
|
|
90
122
|
if (key === null) {
|
|
@@ -113,8 +145,8 @@ function _macroEffect(index, name, file) {
|
|
|
113
145
|
function _expandedNamespaceName(index, name, visible, depth = 0) {
|
|
114
146
|
if (depth > 4) return [name];
|
|
115
147
|
let body = null;
|
|
116
|
-
for (const definition of index
|
|
117
|
-
if (
|
|
148
|
+
for (const definition of _declarationsOfKind(index, name, 'macro')) {
|
|
149
|
+
if (!visible.has(definition.file)) continue;
|
|
118
150
|
if (Array.isArray(definition.ppParams) || typeof definition.ppBody !== 'string') return [name];
|
|
119
151
|
const text = definition.ppBody.replace(/\s+/g, '');
|
|
120
152
|
if (!/^[A-Za-z_]\w*(?:::[A-Za-z_]\w*)*$/.test(text)) return [name];
|
|
@@ -134,6 +166,17 @@ function _expandedNamespaceName(index, name, visible, depth = 0) {
|
|
|
134
166
|
function _filePrefixes(index, file) {
|
|
135
167
|
const state = _state(index);
|
|
136
168
|
if (state.filePrefixes.has(file)) return state.filePrefixes.get(file);
|
|
169
|
+
// Every witness of an operation reads a file's macro scopes alike: a
|
|
170
|
+
// capture derives them once and records that derivation's reads (fix
|
|
171
|
+
// #435: each type-lookup capture re-derived them).
|
|
172
|
+
const transitions = index.isProvenanceFactIndex && typeof index.sharedRead === 'function'
|
|
173
|
+
? index.sharedRead(`cpp-file-prefixes\0${file}`, view => _filePrefixesUncached(view, file))
|
|
174
|
+
: _filePrefixesUncached(index, file);
|
|
175
|
+
state.filePrefixes.set(file, transitions);
|
|
176
|
+
return transitions;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function _filePrefixesUncached(index, file) {
|
|
137
180
|
const entry = index.files.get(file);
|
|
138
181
|
const markers = entry?.macroScopeMarkers || [];
|
|
139
182
|
let transitions = [];
|
|
@@ -151,7 +194,6 @@ function _filePrefixes(index, file) {
|
|
|
151
194
|
for (const group of effect.opens || []) stack.push(group);
|
|
152
195
|
transitions.push({ line: marker.line, prefix: stack.flat().join('::') });
|
|
153
196
|
}
|
|
154
|
-
state.filePrefixes.set(file, transitions);
|
|
155
197
|
return transitions;
|
|
156
198
|
}
|
|
157
199
|
|
|
@@ -177,13 +219,15 @@ function effectiveNamespace(index, definition) {
|
|
|
177
219
|
/** Effective namespace enclosing an arbitrary source position. */
|
|
178
220
|
function namespaceAt(index, file, line) {
|
|
179
221
|
const prefix = _macroPrefixAt(index, file, line);
|
|
180
|
-
const entry = index.files.get(file);
|
|
181
222
|
let own = '';
|
|
182
223
|
let ownSpan = Infinity;
|
|
183
224
|
// The innermost definition containing the line carries the AST
|
|
184
225
|
// namespace of that position.
|
|
185
|
-
|
|
186
|
-
|
|
226
|
+
const enclosing = projectFact(index, `cpp-namespace-spans:${file}:${line}`, live =>
|
|
227
|
+
symbolsSpanning(live.files.get(file)?.symbols, line).map(symbol => ({
|
|
228
|
+
startLine: symbol.startLine, endLine: symbol.endLine, namespace: symbol.namespace,
|
|
229
|
+
})));
|
|
230
|
+
for (const symbol of enclosing) {
|
|
187
231
|
const span = symbol.endLine - symbol.startLine;
|
|
188
232
|
if (span < ownSpan) {
|
|
189
233
|
ownSpan = span;
|
|
@@ -193,23 +237,41 @@ function namespaceAt(index, file, line) {
|
|
|
193
237
|
return prefix && own ? `${prefix}::${own}` : prefix || own;
|
|
194
238
|
}
|
|
195
239
|
|
|
196
|
-
function
|
|
197
|
-
const state = _state(index);
|
|
198
|
-
if (state.inventory) return state.inventory;
|
|
199
|
-
const namespaces = new Set();
|
|
240
|
+
function* _namespaceInventory(index, namespaces) {
|
|
200
241
|
for (const entry of index.files.values()) {
|
|
201
242
|
if (entry.language !== 'cpp') continue;
|
|
202
243
|
for (const symbol of entry.symbols || []) {
|
|
203
244
|
const namespace = effectiveNamespace(index, symbol);
|
|
204
|
-
if (!namespace) continue;
|
|
245
|
+
if (!namespace || namespaces.has(namespace)) continue;
|
|
205
246
|
const parts = namespace.split('::');
|
|
206
247
|
for (let i = 1; i <= parts.length; i++) {
|
|
207
|
-
|
|
248
|
+
yield parts.slice(0, i).join('::');
|
|
208
249
|
}
|
|
209
250
|
}
|
|
210
251
|
}
|
|
211
|
-
|
|
212
|
-
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function _hasNamespace(index, namespace) {
|
|
255
|
+
// A witness records the answer, never the project-wide inventory (fix #418).
|
|
256
|
+
if (index.isProvenanceFactIndex) {
|
|
257
|
+
return projectFact(index, `cpp-namespace:${namespace}`, live => _hasNamespace(live, namespace));
|
|
258
|
+
}
|
|
259
|
+
const state = _state(index);
|
|
260
|
+
if (!state.inventory) state.inventory = new Set();
|
|
261
|
+
if (state.inventory.has(namespace)) return true;
|
|
262
|
+
if (state.inventoryComplete) return false;
|
|
263
|
+
if (!state.inventoryScan) state.inventoryScan = _namespaceInventory(index, state.inventory);
|
|
264
|
+
// A successful lookup needs only the prefix it found. Keep the scan
|
|
265
|
+
// position so later lookups can discover every remaining namespace.
|
|
266
|
+
let next;
|
|
267
|
+
while (!(next = state.inventoryScan.next()).done) {
|
|
268
|
+
const name = next.value;
|
|
269
|
+
state.inventory.add(name);
|
|
270
|
+
if (name === namespace) return true;
|
|
271
|
+
}
|
|
272
|
+
state.inventoryComplete = true;
|
|
273
|
+
state.inventoryScan = null;
|
|
274
|
+
return false;
|
|
213
275
|
}
|
|
214
276
|
|
|
215
277
|
/** Remove template argument lists (depth-aware) and whitespace. */
|
|
@@ -271,10 +333,7 @@ function _scopeOf(index, definition) {
|
|
|
271
333
|
}
|
|
272
334
|
|
|
273
335
|
function _typeCandidates(index, name) {
|
|
274
|
-
return (index
|
|
275
|
-
_isCppFile(index, definition.file) &&
|
|
276
|
-
(TYPE_KINDS.has(definition.type) ||
|
|
277
|
-
(definition.type === 'type' && definition.aliasOf)));
|
|
336
|
+
return _declarationsOfKind(index, name, 'type');
|
|
278
337
|
}
|
|
279
338
|
|
|
280
339
|
/**
|
|
@@ -287,6 +346,10 @@ function _usingsInScope(index, context, visibleFiles) {
|
|
|
287
346
|
const take = (file, fact) => {
|
|
288
347
|
if (file === context.file) {
|
|
289
348
|
if (fact.line > context.line) return;
|
|
349
|
+
if (fact.ppGroups?.some(group => {
|
|
350
|
+
const branch = _branchAt(group, context.line);
|
|
351
|
+
return branch >= 0 && branch !== _branchAt(group, fact.line);
|
|
352
|
+
})) return;
|
|
290
353
|
if (fact.scopeStartLine != null &&
|
|
291
354
|
(context.line < fact.scopeStartLine || context.line > fact.scopeEndLine)) return;
|
|
292
355
|
} else if (fact.scopeStartLine != null) {
|
|
@@ -309,6 +372,80 @@ function _usingsInScope(index, context, visibleFiles) {
|
|
|
309
372
|
return out;
|
|
310
373
|
}
|
|
311
374
|
|
|
375
|
+
function _branchAt(group, line) {
|
|
376
|
+
return Number.isInteger(line) ? group.branches.findIndex(([start, end]) =>
|
|
377
|
+
start <= line && line <= end) : -1;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** An import and a local declaration can bind one name in different builds. */
|
|
381
|
+
function _conditionalImports(index, definition) {
|
|
382
|
+
if (!definition?.file || definition.className || definition.receiver || definition.enclosingType ||
|
|
383
|
+
definition.type === 'macro') return [];
|
|
384
|
+
const facts = index.files.get(definition.file)?.cppUsings;
|
|
385
|
+
if (!facts?.some(fact => fact.name === definition.name && fact.ppGroups?.length)) return [];
|
|
386
|
+
const state = _state(index);
|
|
387
|
+
state.conditionalImports ||= new WeakMap();
|
|
388
|
+
if (state.conditionalImports.has(definition)) return state.conditionalImports.get(definition);
|
|
389
|
+
const namespace = effectiveNamespace(index, definition);
|
|
390
|
+
const out = [];
|
|
391
|
+
// Qualifier lookup can revisit this declaration through a namespace alias.
|
|
392
|
+
state.conditionalImports.set(definition, out);
|
|
393
|
+
for (const raw of facts) {
|
|
394
|
+
if (!['declaration', 'namespace-alias'].includes(raw.kind) ||
|
|
395
|
+
raw.name !== definition.name || raw.scopeStartLine != null) continue;
|
|
396
|
+
const groups = (raw.ppGroups || []).filter(group => {
|
|
397
|
+
const branch = _branchAt(group, definition.startLine);
|
|
398
|
+
return branch >= 0 && branch !== _branchAt(group, raw.line);
|
|
399
|
+
});
|
|
400
|
+
if (groups.length === 0 || effectiveNamespace(index,
|
|
401
|
+
{ file: definition.file, startLine: raw.line, namespace: raw.namespace }) !== namespace) continue;
|
|
402
|
+
if (raw.kind === 'namespace-alias') {
|
|
403
|
+
out.push({ groups });
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const fact = _withExpandedTarget(index, { ...raw, file: definition.file });
|
|
407
|
+
const qualifier = fact.target.replace(/^::/, '').split('::').slice(0, -1).join('::');
|
|
408
|
+
const supplier = resolveQualifier(index, _factContext(index, fact, definition.file),
|
|
409
|
+
qualifier, includeClosure(index, definition.file));
|
|
410
|
+
if (supplier.kind === 'namespace' &&
|
|
411
|
+
(supplier.resolvedNamespace || supplier.namespace) === namespace) continue;
|
|
412
|
+
out.push({ groups });
|
|
413
|
+
}
|
|
414
|
+
state.conditionalImports.set(definition, out);
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** null: unchanged binding; other: absent here; uncertain: build selects it. */
|
|
419
|
+
function conditionalImportBinding(index, definitions, site) {
|
|
420
|
+
let affected = false;
|
|
421
|
+
let allOther = true;
|
|
422
|
+
for (const definition of definitions) {
|
|
423
|
+
const imports = _conditionalImports(index, definition);
|
|
424
|
+
// The import alternatives live in the definition's file: a
|
|
425
|
+
// translation unit that cannot include it reaches the definition
|
|
426
|
+
// by linkage alone, whatever that file's configuration (fix #419).
|
|
427
|
+
if (imports.length === 0 || site.file !== definition.file &&
|
|
428
|
+
require('./callers')._cppTargetVisibility(index, site.file, [definition]) === 'no') {
|
|
429
|
+
allOther = false;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
affected = true;
|
|
433
|
+
let other = false;
|
|
434
|
+
let unknown = false;
|
|
435
|
+
for (const item of imports) for (const group of item.groups) {
|
|
436
|
+
const branch = site.file === definition.file ? _branchAt(group, site.line) : -1;
|
|
437
|
+
if (branch < 0) unknown = true;
|
|
438
|
+
else if (branch !== _branchAt(group, definition.startLine)) other = true;
|
|
439
|
+
}
|
|
440
|
+
// One known incompatible branch excludes this definition even if
|
|
441
|
+
// an outer or nested condition remains unknown.
|
|
442
|
+
if (other) continue;
|
|
443
|
+
allOther = false;
|
|
444
|
+
if (!unknown) return null;
|
|
445
|
+
}
|
|
446
|
+
return affected ? (allOther ? 'other' : 'uncertain') : null;
|
|
447
|
+
}
|
|
448
|
+
|
|
312
449
|
/**
|
|
313
450
|
* A using fact whose target spells a namespace through an object-like macro
|
|
314
451
|
* (`using namespace LIB_NAMESPACE;`, `using LIB_NAMESPACE::f;`) names what
|
|
@@ -323,7 +460,7 @@ function _withExpandedTarget(index, fact) {
|
|
|
323
460
|
if (segments.some(part => !/^[A-Za-z_]\w*$/.test(part))) return fact;
|
|
324
461
|
const keepLast = fact.kind === 'declaration' && segments.length > 1;
|
|
325
462
|
const head = keepLast ? segments.slice(0, -1) : segments;
|
|
326
|
-
if (!head.some(part => (index
|
|
463
|
+
if (!head.some(part => _declarationsOfKind(index, part, 'macro').length)) {
|
|
327
464
|
return fact;
|
|
328
465
|
}
|
|
329
466
|
const visible = includeClosure(index, fact.file);
|
|
@@ -379,6 +516,7 @@ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, opt
|
|
|
379
516
|
// for the last segment (a class, or a typedef/alias of that spelling)
|
|
380
517
|
// instead of the classes an alias denotes.
|
|
381
518
|
const declarationsOnly = options.declarations === true;
|
|
519
|
+
const values = declarationsOnly && options.values === true;
|
|
382
520
|
const plain = stripTemplateArguments(spelling);
|
|
383
521
|
const global = plain.startsWith('::');
|
|
384
522
|
let segments = plain.replace(/^::/, '').split('::').filter(Boolean);
|
|
@@ -389,14 +527,14 @@ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, opt
|
|
|
389
527
|
// (`LIB_NAMESPACE::detail::X`) is the name the macro expands to, as in
|
|
390
528
|
// macro-opened namespaces (fix #396).
|
|
391
529
|
if (segments.length > 1 && visibleFiles && segments.slice(0, -1).some(part =>
|
|
392
|
-
(index
|
|
530
|
+
_declarationsOfKind(index, part, 'macro').length)) {
|
|
393
531
|
segments = [...segments.slice(0, -1).flatMap(part => _expandedNamespaceName(index, part, visibleFiles)),
|
|
394
532
|
segments[segments.length - 1]];
|
|
395
533
|
}
|
|
396
534
|
if (depth > MAX_ALIAS_HOPS) return { kind: 'unknown', why: 'alias-depth' };
|
|
397
535
|
const origin = context.origin || context.file;
|
|
398
536
|
const cacheKey = `${origin}\0${context.file}\0${context.line}\0${context.className || ''}\0${plain}` +
|
|
399
|
-
`${declarationsOnly ? '\0d' : ''}${context.namespace != null ? `\0ns:${context.namespace}` : ''}`;
|
|
537
|
+
`${declarationsOnly ? '\0d' : ''}${options.externalTypes ? '\0external' : ''}${values ? `\0v:${options.valueSite?.index ?? ''}` : ''}${context.namespace != null ? `\0ns:${context.namespace}` : ''}`;
|
|
400
538
|
const state = _state(index);
|
|
401
539
|
if (state.resolved.has(cacheKey)) return state.resolved.get(cacheKey);
|
|
402
540
|
const finish = result => {
|
|
@@ -415,9 +553,28 @@ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, opt
|
|
|
415
553
|
const introducers = usings.filter(fact =>
|
|
416
554
|
(fact.kind === 'declaration' || fact.kind === 'namespace-alias') &&
|
|
417
555
|
fact.name === segments[0]);
|
|
556
|
+
if (introducers.some(fact => fact.ppGroups?.length > 0)) {
|
|
557
|
+
const scopes = new Set(_enclosingPrefixes(contextNamespace));
|
|
558
|
+
const local = (index.symbols.get(segments[0]) || []).filter(definition =>
|
|
559
|
+
visibleFiles?.has(definition.file) && scopes.has(effectiveNamespace(index, definition)));
|
|
560
|
+
if (conditionalImportBinding(index, local, context) === 'uncertain') {
|
|
561
|
+
return finish({ kind: 'unknown', why: 'configuration-alternative' });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
418
564
|
if (introducers.length > 0) {
|
|
419
565
|
const targets = new Set(introducers.map(fact => fact.target));
|
|
420
|
-
if (targets.size !== 1)
|
|
566
|
+
if (targets.size !== 1) {
|
|
567
|
+
// Different namespace aliases leave identity undecided while
|
|
568
|
+
// still proving the qualifier cannot name a class. A type
|
|
569
|
+
// alternative keeps even that category uncertain.
|
|
570
|
+
const scopes = new Set(_enclosingPrefixes(contextNamespace));
|
|
571
|
+
const localTypes = _typeCandidates(index, segments[0]).some(definition =>
|
|
572
|
+
visibleFiles?.has(definition.file) && scopes.has(effectiveNamespace(index, definition)));
|
|
573
|
+
const namespaceOnly = segments.length === 1 && !localTypes &&
|
|
574
|
+
introducers.every(fact => fact.kind === 'namespace-alias');
|
|
575
|
+
return finish({ kind: 'unknown', why: 'ambiguous-using',
|
|
576
|
+
...(namespaceOnly && { namespaceOnly: true }) });
|
|
577
|
+
}
|
|
421
578
|
const fact = introducers[0];
|
|
422
579
|
const rewritten = [fact.target, ...segments.slice(1)].join('::');
|
|
423
580
|
return finish(resolveQualifier(index, _factContext(index, fact, origin),
|
|
@@ -452,29 +609,67 @@ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, opt
|
|
|
452
609
|
if (!scopes.includes(full)) scopes.push(full);
|
|
453
610
|
}
|
|
454
611
|
}
|
|
455
|
-
const
|
|
612
|
+
const enumScopes = new Map();
|
|
613
|
+
const candidates = values ? (index.symbols.get(name) || []).filter(definition => {
|
|
614
|
+
if (!_isCppFile(index, definition.file) || !isValueDefinition(definition)) return false;
|
|
615
|
+
const info = require('./value-references').enumValueBindingInfo(index, definition);
|
|
616
|
+
if (info?.scope?.type === 'field_declaration_list') return false;
|
|
617
|
+
if (!definition.className) return true;
|
|
618
|
+
const owner = info?.unscoped && require('./class-identity').ownerRefOf(index, definition);
|
|
619
|
+
if (!owner?.def) return false;
|
|
620
|
+
const local = info.scope && !['translation_unit', 'field_declaration_list'].includes(info.scope.type);
|
|
621
|
+
if (local && (options.valueSite?.file !== definition.file ||
|
|
622
|
+
info.scope.startIndex > options.valueSite.index || info.scope.endIndex <= options.valueSite.index)) return false;
|
|
623
|
+
enumScopes.set(definition, { definition: owner.def, ...(local && { scope: info.scope }) });
|
|
624
|
+
return true;
|
|
625
|
+
})
|
|
626
|
+
: _typeCandidates(index, name);
|
|
456
627
|
let matched = [];
|
|
457
628
|
// Innermost scope wins (C++ unqualified and qualified lookup both stop
|
|
458
629
|
// at the first scope that declares the name).
|
|
459
630
|
for (const scope of scopes) {
|
|
460
631
|
const want = _join(scope, qualifier);
|
|
461
632
|
matched = candidates.filter(definition => {
|
|
462
|
-
|
|
633
|
+
const declared = enumScopes.get(definition)?.definition || definition;
|
|
634
|
+
if (_scopeOf(index, declared) !== want) return false;
|
|
463
635
|
// Block-scope aliases are visible only inside their own block.
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
!(
|
|
467
|
-
return
|
|
468
|
-
|
|
469
|
-
context.line <=
|
|
636
|
+
if (declared.lexicalScopeStartLine != null &&
|
|
637
|
+
declared.lexicalScopeEndLine != null &&
|
|
638
|
+
!(declared.type === 'type' && declared.enclosingType)) {
|
|
639
|
+
return declared.file === context.file &&
|
|
640
|
+
declared.lexicalScopeStartLine <= context.line &&
|
|
641
|
+
context.line <= declared.lexicalScopeEndLine;
|
|
470
642
|
}
|
|
471
643
|
return true;
|
|
472
644
|
});
|
|
473
645
|
if (matched.length > 0) break;
|
|
474
646
|
}
|
|
647
|
+
const localEnums = matched.filter(definition => enumScopes.get(definition)?.scope);
|
|
648
|
+
if (localEnums.length) {
|
|
649
|
+
const width = definition => {
|
|
650
|
+
const scope = enumScopes.get(definition).scope;
|
|
651
|
+
return scope.endIndex - scope.startIndex;
|
|
652
|
+
};
|
|
653
|
+
const closest = Math.min(...localEnums.map(width));
|
|
654
|
+
matched = localEnums.filter(definition => width(definition) === closest);
|
|
655
|
+
}
|
|
475
656
|
if (matched.length === 0) {
|
|
476
657
|
const full = segments.join('::');
|
|
477
|
-
|
|
658
|
+
// Platform type facts are usable only after ordinary name lookup.
|
|
659
|
+
// A local namespace/type called std shadows the global root, and
|
|
660
|
+
// conditional imports cannot establish a platform identity.
|
|
661
|
+
if (options.externalTypes && !usings.some(fact => fact.ppGroups?.length)) {
|
|
662
|
+
const shadowed = !global && segments.length > 1 &&
|
|
663
|
+
_enclosingPrefixes(contextNamespace).filter(Boolean).some(prefix => {
|
|
664
|
+
const first = _join(prefix, segments[0]);
|
|
665
|
+
return _hasNamespace(index, first) || _typeCandidates(index, segments[0])
|
|
666
|
+
.some(definition => visibleFiles?.has(definition.file) && _scopeOf(index, definition) === prefix);
|
|
667
|
+
});
|
|
668
|
+
const external = !shadowed && scopes.map(scope => _join(scope, full))
|
|
669
|
+
.find(name => name.startsWith('std::'));
|
|
670
|
+
if (external) return finish({ kind: 'external', qualified: external });
|
|
671
|
+
}
|
|
672
|
+
const scope = scopes.find(candidate => _hasNamespace(index, _join(candidate, full)));
|
|
478
673
|
if (scope !== undefined) {
|
|
479
674
|
// `resolvedNamespace` (fix #393): the namespace the spelling
|
|
480
675
|
// names from this scope (innermost first).
|
|
@@ -492,8 +687,11 @@ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, opt
|
|
|
492
687
|
return finish({ kind: 'unknown', why: 'not-visible' });
|
|
493
688
|
}
|
|
494
689
|
const chosen = visible.length > 0 ? visible : matched;
|
|
690
|
+
if (conditionalImportBinding(index, chosen, context)) {
|
|
691
|
+
return finish({ kind: 'unknown', why: 'configuration-alternative' });
|
|
692
|
+
}
|
|
495
693
|
if (declarationsOnly) {
|
|
496
|
-
return finish({ kind: 'type', declarations: chosen, visible: visible.length > 0 });
|
|
694
|
+
return finish({ kind: values ? 'value' : 'type', declarations: chosen, visible: visible.length > 0 });
|
|
497
695
|
}
|
|
498
696
|
const classes = [];
|
|
499
697
|
for (const definition of chosen) {
|
|
@@ -520,6 +718,9 @@ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, opt
|
|
|
520
718
|
}
|
|
521
719
|
|
|
522
720
|
module.exports = {
|
|
721
|
+
conversionSyntaxMemo,
|
|
722
|
+
conversionResultMemo,
|
|
723
|
+
conditionalImportBinding,
|
|
523
724
|
macroPrefixAt: _macroPrefixAt,
|
|
524
725
|
globalQualifiedReaches,
|
|
525
726
|
resetCppScope,
|
package/core/deadcode.js
CHANGED
|
@@ -15,7 +15,7 @@ const { splitParentList } = require('./graph-build');
|
|
|
15
15
|
const { isOverrideMarked, codeUnitCompare, lineInRanges, maskBlockComments, escapeRegExp } = require('./shared');
|
|
16
16
|
const { projectComputedDispatch, projectReflectionSites } = require('./ast-analysis');
|
|
17
17
|
const { reflectionPatternReferences } = require('./reflection');
|
|
18
|
-
const { protocolMemberOf, protocolTypeOf, typedLiteralContract } = require('./contract-membership');
|
|
18
|
+
const { protocolMemberOf, protocolTypeOf, typedLiteralContract, rustProjectTraits } = require('./contract-membership');
|
|
19
19
|
|
|
20
20
|
const _CLASS_KINDS = ['class', 'struct', 'interface', 'trait', 'record'];
|
|
21
21
|
|
|
@@ -212,6 +212,13 @@ function _classHasExternalBase(index, symbol) {
|
|
|
212
212
|
*/
|
|
213
213
|
function overridesOutOfTreeBase(index, symbol) {
|
|
214
214
|
if (!symbol.className) return false; // standalone function can't override
|
|
215
|
+
// A member that names its contract (Rust `impl Trait for X`, fix #422):
|
|
216
|
+
// the trait's identity decides, not the number of same-name owners. Two
|
|
217
|
+
// project types implementing one external trait (`FromIterator`) are
|
|
218
|
+
// each reached through it (`.collect::<A>()`), the impls being owners
|
|
219
|
+
// of nothing the project declares.
|
|
220
|
+
if (symbol.traitName &&
|
|
221
|
+
rustProjectTraits(index, symbol.traitName, symbol.file, symbol.startLine).length === 0) return true;
|
|
215
222
|
if (isOverrideMarked(symbol)) {
|
|
216
223
|
const owners = (index.symbols.get(symbol.name) || []).filter(s => s.className);
|
|
217
224
|
if (owners.length <= 1) return true;
|
|
@@ -481,7 +488,7 @@ function buildUsageIndex(index, filterNames) {
|
|
|
481
488
|
|
|
482
489
|
for (const [filePath, fileEntry] of index.files) {
|
|
483
490
|
try {
|
|
484
|
-
const language = detectLanguage(filePath);
|
|
491
|
+
const language = fileEntry.language || detectLanguage(filePath, index.root);
|
|
485
492
|
if (!language) continue;
|
|
486
493
|
|
|
487
494
|
const content = index._readFile(filePath);
|
|
@@ -745,6 +752,7 @@ function deadcode(index, options = {}) {
|
|
|
745
752
|
let excludedExported = 0;
|
|
746
753
|
let excludedExternalContract = 0;
|
|
747
754
|
let excludedRuntimeContract = 0;
|
|
755
|
+
let excludedConstructors = 0;
|
|
748
756
|
let excludedDynamicDispatch = 0;
|
|
749
757
|
const computedDispatchByFile = projectComputedDispatch(index);
|
|
750
758
|
const computedDispatchReceivers = new Set();
|
|
@@ -1164,8 +1172,10 @@ function deadcode(index, options = {}) {
|
|
|
1164
1172
|
// base definition this syntax consumes.
|
|
1165
1173
|
dottedScope = undefined;
|
|
1166
1174
|
} else {
|
|
1175
|
+
// The receiver is a binding's local name
|
|
1176
|
+
// (fix #424: `from pkg import sub as x`).
|
|
1167
1177
|
const binding = (fileEntry.importBindings || [])
|
|
1168
|
-
.find(b => b.name === receiver);
|
|
1178
|
+
.find(b => (b.alias || b.name) === receiver);
|
|
1169
1179
|
let resolved;
|
|
1170
1180
|
if (binding && fileEntry.moduleResolved) {
|
|
1171
1181
|
// `from . import types` binds the
|
|
@@ -1178,7 +1188,7 @@ function deadcode(index, options = {}) {
|
|
|
1178
1188
|
// to the file that actually owns them.
|
|
1179
1189
|
const mod = String(binding.module);
|
|
1180
1190
|
const submodule = mod.endsWith('.')
|
|
1181
|
-
? mod +
|
|
1191
|
+
? mod + binding.name : mod + '.' + binding.name;
|
|
1182
1192
|
resolved = fileEntry.moduleResolved[submodule] ||
|
|
1183
1193
|
fileEntry.moduleResolved[mod];
|
|
1184
1194
|
}
|
|
@@ -1346,6 +1356,13 @@ function deadcode(index, options = {}) {
|
|
|
1346
1356
|
if (symbol.relativePath.endsWith('.d.ts')) {
|
|
1347
1357
|
continue;
|
|
1348
1358
|
}
|
|
1359
|
+
// The same holds for a `declare global` / `declare module 'x'`
|
|
1360
|
+
// block in a source file (fix #423): its declarations merge
|
|
1361
|
+
// with declarations outside the project, whose readers UCN
|
|
1362
|
+
// cannot see (`interface Window`, a library's `Register`).
|
|
1363
|
+
if (symbol.ambientAugmentation) {
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1349
1366
|
|
|
1350
1367
|
// Language-specific entry points (called by runtime/test runner, not user code)
|
|
1351
1368
|
// Each language module declares its own isEntryPoint() rules.
|
|
@@ -1353,6 +1370,19 @@ function deadcode(index, options = {}) {
|
|
|
1353
1370
|
if (langModule.isEntryPoint?.(symbol)) {
|
|
1354
1371
|
continue;
|
|
1355
1372
|
}
|
|
1373
|
+
// A constructor participates in the type's allocation protocol,
|
|
1374
|
+
// including reflective/framework construction and the implicit
|
|
1375
|
+
// initializer that deleting it may introduce. A zero name count
|
|
1376
|
+
// cannot establish that this declaration is independently dead.
|
|
1377
|
+
// Ordinary methods with a constructor-like name remain auditable.
|
|
1378
|
+
// Counted apart from runtime callbacks (fix #421): the type is
|
|
1379
|
+
// audited instead, its constructors are not claims of their own.
|
|
1380
|
+
if (symbol.type === 'constructor' ||
|
|
1381
|
+
(symbol.className && !classAuditSet.has(symbol.type) &&
|
|
1382
|
+
langTraits(lang)?.constructorProtocol?.initializer === name)) {
|
|
1383
|
+
excludedConstructors++;
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1356
1386
|
// Members the runtime/compiler invokes by name (trait
|
|
1357
1387
|
// languageProtocolMember): Java serialization callbacks on
|
|
1358
1388
|
// Serializable types, Python Enum hooks on Enum subclasses, C#
|
|
@@ -1413,17 +1443,6 @@ function deadcode(index, options = {}) {
|
|
|
1413
1443
|
if (index.calleeIndex.has(name) && !selfRecursiveNames.has(name)) {
|
|
1414
1444
|
continue;
|
|
1415
1445
|
}
|
|
1416
|
-
// Constructor members are invoked through the CLASS name
|
|
1417
|
-
// (fix #239, G2-js-measured: `new Widget()` indexes under
|
|
1418
|
-
// 'Widget' — the member lookup claimed every instantiated
|
|
1419
|
-
// class's constructor dead).
|
|
1420
|
-
if (symbol.className &&
|
|
1421
|
-
(symbol.type === 'constructor' || name === 'constructor' || name === '__init__') &&
|
|
1422
|
-
index.calleeIndex.has(symbol.className) &&
|
|
1423
|
-
!selfRecursiveNames.has(symbol.className)) {
|
|
1424
|
-
continue;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
1446
|
// Class-kind claims (fix #253a): a class whose member the runtime
|
|
1428
1447
|
// or a framework invokes is live with zero textual references of
|
|
1429
1448
|
// the class NAME — `class Main { public static void main }`,
|
|
@@ -1613,6 +1632,7 @@ function deadcode(index, options = {}) {
|
|
|
1613
1632
|
results.excludedExported = excludedExported;
|
|
1614
1633
|
results.excludedExternalContract = excludedExternalContract;
|
|
1615
1634
|
results.excludedRuntimeContract = excludedRuntimeContract;
|
|
1635
|
+
results.excludedConstructors = excludedConstructors;
|
|
1616
1636
|
results.excludedDynamicDispatch = excludedDynamicDispatch;
|
|
1617
1637
|
results.computedDispatch = computedDispatchInfo;
|
|
1618
1638
|
reflectionInfo.withheldByPattern = [...reflectionWithheldByPattern]
|