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
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
|
@@ -14,8 +14,8 @@ const { isFrameworkEntrypoint } = require('./entrypoints');
|
|
|
14
14
|
const { splitParentList } = require('./graph-build');
|
|
15
15
|
const { isOverrideMarked, codeUnitCompare, lineInRanges, maskBlockComments, escapeRegExp } = require('./shared');
|
|
16
16
|
const { projectComputedDispatch, projectReflectionSites } = require('./ast-analysis');
|
|
17
|
-
const { reflectionPatternReferences } = require('./reflection');
|
|
18
|
-
const { protocolMemberOf, protocolTypeOf, typedLiteralContract } = require('./contract-membership');
|
|
17
|
+
const { reflectionPatternReferences, reflectionLiteralNames } = require('./reflection');
|
|
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();
|
|
@@ -762,6 +770,8 @@ function deadcode(index, options = {}) {
|
|
|
762
770
|
}
|
|
763
771
|
}
|
|
764
772
|
const literalReflectionNames = new Set();
|
|
773
|
+
const dynamicReflectionFiles = new Set();
|
|
774
|
+
const reflectionTables = new Map();
|
|
765
775
|
const reflectionInfo = {
|
|
766
776
|
count: 0,
|
|
767
777
|
literalCount: 0,
|
|
@@ -773,6 +783,7 @@ function deadcode(index, options = {}) {
|
|
|
773
783
|
patterns: [],
|
|
774
784
|
withheld: 0,
|
|
775
785
|
withheldByPattern: [],
|
|
786
|
+
withheldByTable: [],
|
|
776
787
|
};
|
|
777
788
|
// Reflection is liveness evidence wherever it sits (a test or script
|
|
778
789
|
// outside --in still reaches the member); the disclosure counts cover the
|
|
@@ -782,6 +793,7 @@ function deadcode(index, options = {}) {
|
|
|
782
793
|
const fe = index.files.get(reflectionFile);
|
|
783
794
|
if (!fe || sites.length === 0) continue;
|
|
784
795
|
for (const site of sites) {
|
|
796
|
+
if (site.dynamic) dynamicReflectionFiles.add(reflectionFile);
|
|
785
797
|
if (site.name) literalReflectionNames.add(site.name);
|
|
786
798
|
if (site.patterns && site.patterns.length > 0 && !site.name) {
|
|
787
799
|
// Exact alternatives of a multi-spelling site are literals.
|
|
@@ -1164,8 +1176,10 @@ function deadcode(index, options = {}) {
|
|
|
1164
1176
|
// base definition this syntax consumes.
|
|
1165
1177
|
dottedScope = undefined;
|
|
1166
1178
|
} else {
|
|
1179
|
+
// The receiver is a binding's local name
|
|
1180
|
+
// (fix #424: `from pkg import sub as x`).
|
|
1167
1181
|
const binding = (fileEntry.importBindings || [])
|
|
1168
|
-
.find(b => b.name === receiver);
|
|
1182
|
+
.find(b => (b.alias || b.name) === receiver);
|
|
1169
1183
|
let resolved;
|
|
1170
1184
|
if (binding && fileEntry.moduleResolved) {
|
|
1171
1185
|
// `from . import types` binds the
|
|
@@ -1178,7 +1192,7 @@ function deadcode(index, options = {}) {
|
|
|
1178
1192
|
// to the file that actually owns them.
|
|
1179
1193
|
const mod = String(binding.module);
|
|
1180
1194
|
const submodule = mod.endsWith('.')
|
|
1181
|
-
? mod +
|
|
1195
|
+
? mod + binding.name : mod + '.' + binding.name;
|
|
1182
1196
|
resolved = fileEntry.moduleResolved[submodule] ||
|
|
1183
1197
|
fileEntry.moduleResolved[mod];
|
|
1184
1198
|
}
|
|
@@ -1346,6 +1360,13 @@ function deadcode(index, options = {}) {
|
|
|
1346
1360
|
if (symbol.relativePath.endsWith('.d.ts')) {
|
|
1347
1361
|
continue;
|
|
1348
1362
|
}
|
|
1363
|
+
// The same holds for a `declare global` / `declare module 'x'`
|
|
1364
|
+
// block in a source file (fix #423): its declarations merge
|
|
1365
|
+
// with declarations outside the project, whose readers UCN
|
|
1366
|
+
// cannot see (`interface Window`, a library's `Register`).
|
|
1367
|
+
if (symbol.ambientAugmentation) {
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1349
1370
|
|
|
1350
1371
|
// Language-specific entry points (called by runtime/test runner, not user code)
|
|
1351
1372
|
// Each language module declares its own isEntryPoint() rules.
|
|
@@ -1353,6 +1374,19 @@ function deadcode(index, options = {}) {
|
|
|
1353
1374
|
if (langModule.isEntryPoint?.(symbol)) {
|
|
1354
1375
|
continue;
|
|
1355
1376
|
}
|
|
1377
|
+
// A constructor participates in the type's allocation protocol,
|
|
1378
|
+
// including reflective/framework construction and the implicit
|
|
1379
|
+
// initializer that deleting it may introduce. A zero name count
|
|
1380
|
+
// cannot establish that this declaration is independently dead.
|
|
1381
|
+
// Ordinary methods with a constructor-like name remain auditable.
|
|
1382
|
+
// Counted apart from runtime callbacks (fix #421): the type is
|
|
1383
|
+
// audited instead, its constructors are not claims of their own.
|
|
1384
|
+
if (symbol.type === 'constructor' ||
|
|
1385
|
+
(symbol.className && !classAuditSet.has(symbol.type) &&
|
|
1386
|
+
langTraits(lang)?.constructorProtocol?.initializer === name)) {
|
|
1387
|
+
excludedConstructors++;
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1356
1390
|
// Members the runtime/compiler invokes by name (trait
|
|
1357
1391
|
// languageProtocolMember): Java serialization callbacks on
|
|
1358
1392
|
// Serializable types, Python Enum hooks on Enum subclasses, C#
|
|
@@ -1413,17 +1447,6 @@ function deadcode(index, options = {}) {
|
|
|
1413
1447
|
if (index.calleeIndex.has(name) && !selfRecursiveNames.has(name)) {
|
|
1414
1448
|
continue;
|
|
1415
1449
|
}
|
|
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
1450
|
// Class-kind claims (fix #253a): a class whose member the runtime
|
|
1428
1451
|
// or a framework invokes is live with zero textual references of
|
|
1429
1452
|
// the class NAME — `class Main { public static void main }`,
|
|
@@ -1579,6 +1602,25 @@ function deadcode(index, options = {}) {
|
|
|
1579
1602
|
continue;
|
|
1580
1603
|
}
|
|
1581
1604
|
|
|
1605
|
+
// A table may feed a dynamic reflection site through a loop
|
|
1606
|
+
// or helper. Withhold exact same-file literal spellings,
|
|
1607
|
+
// conservatively: this is possible dispatch, not proven flow.
|
|
1608
|
+
if (dynamicReflectionFiles.has(symbol.file)) {
|
|
1609
|
+
if (!reflectionTables.has(symbol.file)) {
|
|
1610
|
+
const source = index._readFile(symbol.file);
|
|
1611
|
+
const tree = index._getParsedTree(symbol.file, source,
|
|
1612
|
+
index.files.get(symbol.file).language);
|
|
1613
|
+
reflectionTables.set(symbol.file, reflectionLiteralNames(tree));
|
|
1614
|
+
}
|
|
1615
|
+
const line = reflectionTables.get(symbol.file).get(symbol.name);
|
|
1616
|
+
if (line) {
|
|
1617
|
+
reflectionInfo.withheldByTable.push({ name: symbol.name,
|
|
1618
|
+
file: symbol.relativePath, line, startLine: symbol.startLine,
|
|
1619
|
+
reason: 'literal-name-near-dynamic-reflection' });
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1582
1624
|
results.push({
|
|
1583
1625
|
name: symbol.name,
|
|
1584
1626
|
type: symbol.type,
|
|
@@ -1613,8 +1655,11 @@ function deadcode(index, options = {}) {
|
|
|
1613
1655
|
results.excludedExported = excludedExported;
|
|
1614
1656
|
results.excludedExternalContract = excludedExternalContract;
|
|
1615
1657
|
results.excludedRuntimeContract = excludedRuntimeContract;
|
|
1658
|
+
results.excludedConstructors = excludedConstructors;
|
|
1616
1659
|
results.excludedDynamicDispatch = excludedDynamicDispatch;
|
|
1617
1660
|
results.computedDispatch = computedDispatchInfo;
|
|
1661
|
+
reflectionInfo.withheldByTable.sort((a, b) => codeUnitCompare(a.file, b.file) ||
|
|
1662
|
+
a.startLine - b.startLine || codeUnitCompare(a.name, b.name));
|
|
1618
1663
|
reflectionInfo.withheldByPattern = [...reflectionWithheldByPattern]
|
|
1619
1664
|
.map(([key, count]) => {
|
|
1620
1665
|
const [pattern, namespaceOf] = key.split('\u0000');
|