ucn 5.5.0 → 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 +41 -27
  6. package/cli/index.js +6 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +106 -31
  9. package/core/analysis.js +266 -127
  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 +144 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +5111 -1024
  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 +228 -114
  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 +1202 -157
  63. package/core/rust-modules.js +21 -3
  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 +157 -46
  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 +548 -89
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. 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 = new Set([file]);
60
- const queue = [file];
61
- while (queue.length > 0) {
62
- const current = queue.shift();
63
- for (const imported of index.importGraph?.get(current) || []) {
64
- if (visible.has(imported)) continue;
65
- visible.add(imported);
66
- queue.push(imported);
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.symbols.get(name) || []) {
87
- if (definition.type !== 'macro' || !visible.has(definition.file)) continue;
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.symbols.get(name) || []) {
117
- if (definition.type !== 'macro' || !visible.has(definition.file)) continue;
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
- for (const symbol of entry?.symbols || []) {
186
- if (symbol.startLine > line || symbol.endLine < line) continue;
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 _inventory(index) {
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
- namespaces.add(parts.slice(0, i).join('::'));
248
+ yield parts.slice(0, i).join('::');
208
249
  }
209
250
  }
210
251
  }
211
- state.inventory = namespaces;
212
- return namespaces;
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.symbols.get(name) || []).filter(definition =>
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.symbols.get(part) || []).some(definition => definition.type === 'macro'))) {
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.symbols.get(part) || []).some(definition => definition.type === 'macro'))) {
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) return finish({ kind: 'unknown', why: 'ambiguous-using' });
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 candidates = _typeCandidates(index, name);
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
- if (_scopeOf(index, definition) !== want) return false;
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 (definition.lexicalScopeStartLine != null &&
465
- definition.lexicalScopeEndLine != null &&
466
- !(definition.type === 'type' && definition.enclosingType)) {
467
- return definition.file === context.file &&
468
- definition.lexicalScopeStartLine <= context.line &&
469
- context.line <= definition.lexicalScopeEndLine;
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
- const scope = scopes.find(candidate => _inventory(index).has(_join(candidate, full)));
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 + receiver : mod + '.' + receiver;
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]