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
@@ -40,7 +40,33 @@
40
40
  const path = require('path');
41
41
  const { getParser, safeParse, langTraits, LANGUAGES } = require('../languages');
42
42
  const { sameNode } = require('../languages/utils');
43
+ const { jsPatternTarget } = require('../languages/lexical-scope');
43
44
  const { classKeyOf } = require('./class-identity');
45
+ const { projectHas, sharedRead, containingDeclarations } = require('./provenance-overload');
46
+ const { indexedContent } = require('./source-memo');
47
+
48
+ // Modules read lazily (load cycles), each resolved once: a require() per
49
+ // call in hot paths was measurable (fix #436).
50
+ let lazyCppScopeModuleCache = null;
51
+ const lazyCppScopeModule = () => lazyCppScopeModuleCache || (lazyCppScopeModuleCache = require('./cpp-scope'));
52
+
53
+ /** The namespaces (packages) a language's files declare, with each dotted prefix. */
54
+ function declaredNamespacePrefixes(index, language) {
55
+ return require('./type-denotation').memoizedDerivation(index, `\x01ns-prefixes\x00${language}`, () => {
56
+ const namespaces = new Set();
57
+ for (const entry of index.files.values()) {
58
+ if (entry.language !== language) continue;
59
+ for (const s of entry.symbols || []) {
60
+ if (!s.namespace) continue;
61
+ const parts = s.namespace.split('.');
62
+ for (let i = 1; i <= parts.length; i++) namespaces.add(parts.slice(0, i).join('.'));
63
+ }
64
+ }
65
+ return namespaces;
66
+ });
67
+ }
68
+ const csharpNamespacePrefixes = index => declaredNamespacePrefixes(index, 'csharp');
69
+ const javaPackagePrefixes = index => declaredNamespacePrefixes(index, 'java');
44
70
 
45
71
  const TYPE_RENAME_KINDS = new Set([
46
72
  'class', 'struct', 'interface', 'trait', 'enum', 'record', 'union', 'type',
@@ -145,10 +171,22 @@ function classifyJava(node) {
145
171
  if (field === 'name') return skip('variable', { binding: true });
146
172
  break;
147
173
  case 'formal_parameter': case 'catch_formal_parameter': case 'spread_parameter':
148
- case 'enhanced_for_statement': case 'resource': case 'record_pattern_component':
149
- if (field === 'name' || node.type === 'identifier') return skip('parameter', { binding: true });
174
+ case 'enhanced_for_statement': case 'resource':
175
+ if (field === 'name') return skip('parameter', { binding: true });
176
+ break;
177
+ // Pattern variables (fix #420): `o instanceof T v`, `case T v`,
178
+ // `case R(int v, ..)` declare locals whose scope follows the
179
+ // pattern's flow (JLS 6.3.1).
180
+ case 'instanceof_expression':
181
+ if (field === 'name') return skip('pattern', { binding: true });
182
+ break;
183
+ case 'type_pattern': case 'record_pattern_component':
184
+ if (node.type === 'identifier') return skip('pattern', { binding: true });
150
185
  break;
151
- case 'lambda_expression': case 'inferred_parameters':
186
+ case 'lambda_expression':
187
+ if (field === 'parameters') return skip('parameter', { binding: true });
188
+ break;
189
+ case 'inferred_parameters':
152
190
  return skip('parameter', { binding: true });
153
191
  case 'type_parameter':
154
192
  return skip('type-parameter', { typeParam: true });
@@ -179,8 +217,20 @@ function classifyJava(node) {
179
217
  case 'marker_annotation': case 'annotation':
180
218
  if (field === 'name') return { role: 'type' };
181
219
  break;
220
+ case 'element_value_pair':
221
+ if (field === 'key') return skip('annotation-element');
222
+ break;
182
223
  case 'labeled_statement': case 'break_statement': case 'continue_statement':
183
224
  return skip('label');
225
+ case 'object_creation_expression': {
226
+ // A qualified instance creation `outer.new Inner()` names a
227
+ // member class of the outer expression's type (JLS 15.9.1), not a
228
+ // type in scope at the site (fix #426).
229
+ const outer = parent.namedChild(0);
230
+ if (outer && field === 'type' && !sameNode(outer, node) &&
231
+ outer.endIndex <= node.startIndex) return { role: 'qualified-creation', outer };
232
+ break;
233
+ }
184
234
  default:
185
235
  break;
186
236
  }
@@ -188,6 +238,52 @@ function classifyJava(node) {
188
238
  if (node.type === 'identifier') return { role: 'value' };
189
239
  return { role: 'unknown', why: `java:${parent.type}` };
190
240
  }
241
+
242
+ /**
243
+ * The declared type of a Java expression naming a value, read from its
244
+ * declaration in scope (fix #426): `this` is the enclosing class, a simple
245
+ * name the nearest local, parameter or field declaring it. null otherwise.
246
+ */
247
+ function javaExpressionTypeName(expression) {
248
+ const head = typeNode => {
249
+ if (!typeNode) return null;
250
+ if (typeNode.type === 'generic_type') {
251
+ return head(typeNode.namedChildren.find(child =>
252
+ child.type === 'type_identifier' || child.type === 'scoped_type_identifier'));
253
+ }
254
+ if (typeNode.type === 'scoped_type_identifier') return typeNode.namedChildren.at(-1)?.text || null;
255
+ return typeNode.type === 'type_identifier' ? typeNode.text : null;
256
+ };
257
+ if (expression.type === 'this') {
258
+ const owner = ancestor(expression, JAVA_CLASS_NODES);
259
+ return owner?.childForFieldName('name')?.text || null;
260
+ }
261
+ if (expression.type !== 'identifier') return null;
262
+ const name = expression.text;
263
+ const declares = (declaration) => (declaration.namedChildren || []).some(child =>
264
+ child.type === 'variable_declarator' && child.childForFieldName('name')?.text === name);
265
+ for (let scope = expression.parent; scope; scope = scope.parent) {
266
+ if (scope.type === 'block' || scope.type === 'constructor_body' || scope.type === 'class_body') {
267
+ for (const child of scope.namedChildren || []) {
268
+ if (child.startIndex >= expression.startIndex && scope.type !== 'class_body') break;
269
+ if ((child.type === 'local_variable_declaration' || child.type === 'field_declaration') &&
270
+ declares(child)) return head(child.childForFieldName('type'));
271
+ }
272
+ }
273
+ if (['method_declaration', 'constructor_declaration', 'lambda_expression'].includes(scope.type)) {
274
+ const params = scope.childForFieldName('parameters');
275
+ for (const param of params?.namedChildren || []) {
276
+ if ((param.type === 'formal_parameter' || param.type === 'spread_parameter') &&
277
+ param.childForFieldName('name')?.text === name) return head(param.childForFieldName('type'));
278
+ }
279
+ }
280
+ if (JAVA_CLASS_NODES.has(scope.type)) {
281
+ // Fields of an enclosing class are in scope through its body.
282
+ continue;
283
+ }
284
+ }
285
+ return null;
286
+ }
191
287
  const JAVA_CLASS_NODES = new Set(['class_declaration', 'enum_declaration', 'record_declaration',
192
288
  'interface_declaration']);
193
289
 
@@ -242,6 +338,9 @@ function classifyCSharp(node) {
242
338
  case 'alias_qualified_name':
243
339
  if (field === 'name') return { role: 'type', qualifier: parent.childForFieldName('alias'), aliasQualified: true };
244
340
  return skip('extern-alias');
341
+ case 'invocation_expression':
342
+ if (field === 'function') return skip('method-call');
343
+ break;
245
344
  case 'member_access_expression':
246
345
  if (field === 'name') return { role: 'member', receiver: parent.childForFieldName('expression') };
247
346
  return { role: 'value' };
@@ -302,11 +401,24 @@ function classifyJs(node) {
302
401
  case 'member_expression':
303
402
  if (field === 'property') return { role: 'member', receiver: parent.childForFieldName('object') };
304
403
  return { role: 'value' };
305
- case 'required_parameter': case 'optional_parameter': case 'rest_pattern':
306
- case 'formal_parameters': case 'array_pattern': case 'assignment_pattern':
307
- case 'object_assignment_pattern':
404
+ case 'required_parameter': case 'optional_parameter':
405
+ // The parameter's pattern binds; its default value and type are
406
+ // read (fix #431: `x: number = LIMIT` reads LIMIT).
407
+ if (node.type === 'identifier' && field === 'pattern') return skip('parameter', { binding: true });
408
+ break;
409
+ case 'formal_parameters':
308
410
  if (node.type === 'identifier') return skip('parameter', { binding: true });
309
411
  break;
412
+ case 'rest_pattern': case 'array_pattern': case 'assignment_pattern':
413
+ case 'object_assignment_pattern': {
414
+ if (node.type !== 'identifier') break;
415
+ // A destructuring target declares a name, or (an assignment
416
+ // pattern, `[a, b] = f()`) writes the binding in scope (fix #431).
417
+ const target = jsPatternTarget(node);
418
+ if (target === 'declaration') return skip('parameter', { binding: true });
419
+ if (target === 'assignment') return { role: 'value', patternWrite: true };
420
+ break;
421
+ }
310
422
  case 'variable_declarator':
311
423
  if (field === 'name') return skip('variable', { binding: true });
312
424
  break;
@@ -333,6 +445,11 @@ function classifyJs(node) {
333
445
  if (required) return { role: 'import', requireSource: required, aliased: true };
334
446
  return skip('property-key');
335
447
  }
448
+ if (parent.type === 'pair_pattern' && field === 'value' && node.type === 'identifier') {
449
+ const target = jsPatternTarget(node);
450
+ if (target === 'declaration') return skip('destructured', { binding: true });
451
+ if (target === 'assignment') return { role: 'value', patternWrite: true };
452
+ }
336
453
  break;
337
454
  case 'type_parameter':
338
455
  if (field === 'name') return skip('type-parameter', { typeParam: true });
@@ -349,6 +466,8 @@ function classifyJs(node) {
349
466
  // `const { Widget } = require('./w')` is an import of Widget.
350
467
  const required = requireSourceOf(node);
351
468
  if (required) return { role: 'import', requireSource: required };
469
+ // `({ a } = obj)` writes the binding in scope (fix #431).
470
+ if (jsPatternTarget(node) === 'assignment') return { role: 'value', patternWrite: true };
352
471
  return skip('destructured', { binding: true });
353
472
  }
354
473
  if (node.type === 'type_identifier') return { role: 'type' };
@@ -459,7 +578,13 @@ function classifyGo(node) {
459
578
  if (field === 'name') return { role: 'decl', declNode: parent };
460
579
  break;
461
580
  case 'qualified_type':
462
- if (field === 'name') return { role: 'type', qualifier: parent.childForFieldName('package') };
581
+ if (field === 'name') {
582
+ // In an expression the grammar reads the index of `x[a.b]`
583
+ // as a type argument `a.b` (fix #431): a selector on a value
584
+ // `a` unless `a` names an imported package.
585
+ return { role: 'type', qualifier: parent.childForFieldName('package'),
586
+ ...(require('../languages/lexical-scope').goExpressionTypeArgument(parent) && { selectorOperand: true }) };
587
+ }
463
588
  return skip('package');
464
589
  case 'field_declaration':
465
590
  if (node.type === 'field_identifier') return skip('field-name');
@@ -569,6 +694,9 @@ function classifyRust(node) {
569
694
  case 'const_item': case 'static_item':
570
695
  if (field === 'name') return skip('constant', { binding: true });
571
696
  break;
697
+ case 'const_parameter':
698
+ if (field === 'name') return skip('parameter', { binding: true, genericParameter: true });
699
+ break;
572
700
  case 'mod_item':
573
701
  return skip('module-name');
574
702
  case 'label': case 'loop_label': case 'lifetime':
@@ -619,9 +747,18 @@ function classifyC(node, language) {
619
747
  case 'qualified_identifier': {
620
748
  const scope = parent.childForFieldName('scope');
621
749
  if (field === 'name') {
622
- const declarator = parent.parent?.type === 'function_declarator' &&
623
- isField(parent, 'declarator');
624
- if (declarator && scope && textOf(scope).replace(/<.*$/, '').split('::').pop() === node.text) {
750
+ let qualified = parent;
751
+ while (qualified.parent?.type === 'qualified_identifier' && isField(qualified, 'name')) {
752
+ qualified = qualified.parent;
753
+ }
754
+ const declarator = qualified.parent?.type === 'function_declarator' &&
755
+ isField(qualified, 'declarator') ? qualified.parent : null;
756
+ let declaration = declarator;
757
+ while (declaration?.parent && (isField(declaration, 'declarator') ||
758
+ ['reference_declarator', 'parenthesized_declarator', 'attributed_declarator'].includes(declaration.parent.type))) {
759
+ declaration = declaration.parent;
760
+ }
761
+ if (declarator && !declaration.childForFieldName('type') && scope) {
625
762
  return { role: 'ctor-qualified', qualifier: scope, qualifiedNode: parent };
626
763
  }
627
764
  return { role: 'type', cppQualified: true };
@@ -640,16 +777,26 @@ function classifyC(node, language) {
640
777
  }
641
778
  case 'function_declarator': {
642
779
  if (field !== 'declarator') break;
780
+ // `typedef int F(void *data);` names a function type (fix #427).
781
+ if (parent.parent?.type === 'type_definition' && isField(parent, 'declarator')) {
782
+ return { role: 'decl', declNode: parent.parent, typedef: true };
783
+ }
643
784
  const classNode = ancestor(node, C_TYPE_SPECIFIERS, new Set(['compound_statement']));
644
785
  const body = classNode?.childForFieldName('body');
645
786
  // `struct List<K>::Node { Node(..) }` names its class by the
646
787
  // qualified name's last segment (fix #396).
647
- const className = classNode?.childForFieldName('name');
648
- const classBase = className?.type === 'qualified_identifier'
649
- ? textOf(className).split('::').pop().replace(/<.*$/s, '') : className?.text;
788
+ let className = classNode?.childForFieldName('name');
789
+ while (className && (className.type === 'qualified_identifier' || className.type === 'template_type')) {
790
+ className = className.childForFieldName('name') || className.namedChild(0);
791
+ }
792
+ const classBase = className?.text;
650
793
  if (classNode && body && contains(body, node) && classBase === node.text) {
651
794
  return { role: 'ctor', classNode };
652
795
  }
796
+ // A deduction guide's name is the class template's (fix #427).
797
+ if (language === 'cpp' && require('../languages/c-family').deductionGuideOf(parent.parent)) {
798
+ return { role: 'type' };
799
+ }
653
800
  return skip('function', { binding: true });
654
801
  }
655
802
  case 'field_initializer':
@@ -676,7 +823,8 @@ function classifyC(node, language) {
676
823
  if (node.type === 'field_identifier') return skip('field-name');
677
824
  break;
678
825
  case 'enumerator':
679
- return skip('enumerator');
826
+ if (field === 'name') return skip('enumerator');
827
+ break;
680
828
  case 'type_parameter_declaration': case 'optional_type_parameter_declaration':
681
829
  case 'variadic_type_parameter_declaration':
682
830
  // `template <typename T = Widget>`: the default is a reference.
@@ -684,11 +832,21 @@ function classifyC(node, language) {
684
832
  return skip('type-parameter', { typeParam: true });
685
833
  case 'preproc_def': case 'preproc_function_def': case 'preproc_params':
686
834
  return skip('macro-name');
835
+ case 'structured_binding_declarator':
836
+ // `auto [a, b] = p;` declares each name (fix #420).
837
+ if (node.type === 'identifier') return skip('variable', { binding: true });
838
+ break;
839
+ case 'reference_declarator':
840
+ // The C++ grammar gives the referenced declarator no field name.
841
+ if (sameNode(parent.namedChild(0), node)) {
842
+ if (node.type === 'identifier') return skip('variable', { binding: true });
843
+ if (node.type === 'field_identifier') return skip('field-name');
844
+ }
845
+ break;
687
846
  case 'init_declarator': case 'parameter_declaration': case 'optional_parameter_declaration':
688
- case 'pointer_declarator': case 'reference_declarator': case 'array_declarator':
847
+ case 'pointer_declarator': case 'array_declarator':
689
848
  case 'declaration':
690
- if (node.type === 'identifier' && (field === 'declarator' || parent.type === 'pointer_declarator' ||
691
- parent.type === 'reference_declarator' || parent.type === 'array_declarator')) {
849
+ if (['identifier', 'field_identifier'].includes(node.type) && field === 'declarator') {
692
850
  return skip('variable', { binding: true });
693
851
  }
694
852
  break;
@@ -784,16 +942,30 @@ function typeParamScope(node) {
784
942
  // ── Identity of the pinned type ──────────────────────────────────────────
785
943
 
786
944
  function pinGroupOf(index, def, name) {
945
+ // Several construction witnesses may ask about the same type. Share
946
+ // the pure grouping calculation and all its reads, but give each
947
+ // resolver its own collections: value resolvers extend these groups.
948
+ const key = JSON.stringify(['type-pin-group', name, def]);
949
+ const saved = sharedRead(index, key, view => pinGroupFacts(view, def, name));
950
+ return { ...saved, group: new Set([def, ...saved.group]), all: [...saved.all] };
951
+ }
952
+
953
+ function pinGroupFacts(index, def, name) {
787
954
  const language = index.files.get(def.file)?.language;
788
955
  const family = familyOf(language);
789
- const all = (index.symbols.get(name) || []).filter(d => TYPE_DECL_KINDS.has(d.type) &&
790
- familyOf(index.files.get(d.file)?.language) === family);
956
+ // Different pins of one name inspect the same declarations. Their
957
+ // definition identities depend on the index, not on the selected pin.
958
+ const candidates = sharedRead(index, JSON.stringify(['type-pin-candidates', name, family]), view =>
959
+ (view.symbols.get(name) || []).filter(d => TYPE_DECL_KINDS.has(d.type) &&
960
+ familyOf(view.files.get(d.file)?.language) === family)
961
+ .map(d => ({ def: d, key: classKeyOf(view, d) })));
962
+ const all = candidates.map(candidate => candidate.def);
791
963
  const pinKey = classKeyOf(index, def);
792
- const group = new Set([def]);
964
+ const group = new Set();
793
965
  let crossConfig = null;
794
- for (const other of all) {
795
- if (other === def) continue;
796
- if (classKeyOf(index, other) === pinKey) {
966
+ for (const candidate of candidates) {
967
+ const other = candidate.def;
968
+ if (candidate.key === pinKey) {
797
969
  group.add(other);
798
970
  continue;
799
971
  }
@@ -821,7 +993,7 @@ function pinGroupOf(index, def, name) {
821
993
  group.add(other);
822
994
  continue;
823
995
  }
824
- const { includeClosure } = require('./cpp-scope');
996
+ const { includeClosure } = lazyCppScopeModule();
825
997
  if (includeClosure(index, def.file).has(other.file) || includeClosure(index, other.file).has(def.file)) {
826
998
  group.add(other);
827
999
  continue;
@@ -830,7 +1002,7 @@ function pinGroupOf(index, def, name) {
830
1002
  if (crossConfig(index, def.file, other.file)) group.add(other);
831
1003
  }
832
1004
  }
833
- return { group, all, family, language };
1005
+ return { group: [...group], all, family, language };
834
1006
  }
835
1007
 
836
1008
  const CLASS_KEYWORD_KINDS = new Set(['class', 'struct']);
@@ -841,7 +1013,7 @@ function isHeader(file) {
841
1013
  }
842
1014
 
843
1015
  function effectiveNs(index, d) {
844
- const { effectiveNamespace } = require('./cpp-scope');
1016
+ const { effectiveNamespace } = lazyCppScopeModule();
845
1017
  return effectiveNamespace(index, d) || '';
846
1018
  }
847
1019
 
@@ -852,6 +1024,101 @@ function rustModulePath(index, d) {
852
1024
 
853
1025
  // ── Resolution ───────────────────────────────────────────────────────────
854
1026
 
1027
+ // Whether a word occurrence of `name` in Rust source is followed, past
1028
+ // whitespace and comments, by `!`: what an invocation's path needs before
1029
+ // its token tree in a source that parses without error.
1030
+ function spelledBeforeBang(source, name) {
1031
+ // ASCII word characters only: anything else around the name (Unicode
1032
+ // identifiers, Unicode whitespace) keeps the file.
1033
+ const word = c => (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95;
1034
+ for (let at = source.indexOf(name); at >= 0; at = source.indexOf(name, at + 1)) {
1035
+ if (at > 0 && word(source.charCodeAt(at - 1)) || word(source.charCodeAt(at + name.length))) continue;
1036
+ let i = at + name.length;
1037
+ for (;;) {
1038
+ const c = source.charCodeAt(i);
1039
+ if (c > 127) return true;
1040
+ if (c === 32 || c === 9 || c === 10 || c === 13 || c === 11 || c === 12) { i++; continue; }
1041
+ if (c === 47 && source.charCodeAt(i + 1) === 47) {
1042
+ const end = source.indexOf('\n', i);
1043
+ if (end < 0) { i = source.length; break; }
1044
+ i = end + 1;
1045
+ continue;
1046
+ }
1047
+ if (c === 47 && source.charCodeAt(i + 1) === 42) {
1048
+ let depth = 1;
1049
+ i += 2;
1050
+ while (i < source.length && depth > 0) {
1051
+ if (source.charCodeAt(i) === 47 && source.charCodeAt(i + 1) === 42) { depth++; i += 2; }
1052
+ else if (source.charCodeAt(i) === 42 && source.charCodeAt(i + 1) === 47) { depth--; i += 2; }
1053
+ else i++;
1054
+ }
1055
+ continue;
1056
+ }
1057
+ break;
1058
+ }
1059
+ if (source.charCodeAt(i) === 33) return true;
1060
+ }
1061
+ return false;
1062
+ }
1063
+
1064
+ // The innermost class around a line by the index's declarations.
1065
+ function enclosingClassIn(index, file, line) {
1066
+ const entry = index.files.get(file);
1067
+ let best = null;
1068
+ for (const s of entry?.symbols || []) {
1069
+ if (s.startLine > line || s.endLine < line) continue;
1070
+ const isClass = TYPE_DECL_KINDS.has(s.type) && s.type !== 'type';
1071
+ const candidate = isClass ? s.name : (s.className || null);
1072
+ if (!candidate) continue;
1073
+ if (!best || (s.endLine - s.startLine) < (best.span)) {
1074
+ best = { name: candidate, span: s.endLine - s.startLine };
1075
+ }
1076
+ }
1077
+ return best?.name || null;
1078
+ }
1079
+
1080
+ // The namespace and class a C/C++ site's lookup starts in: from the site's
1081
+ // own tree node when it has one (no pinned type takes part).
1082
+ function cppSiteContextOf(index, siteNode, siteFile, file, line, syntax = null) {
1083
+ const node = siteNode && siteFile === file && siteNode.startPosition.row + 1 === line
1084
+ ? siteNode : null;
1085
+ const { macroNamespacePrefixAt, namespaceAt } = lazyCppScopeModule();
1086
+ if (!node && !syntax) return { namespace: namespaceAt(index, file, line), className: enclosingClassIn(index, file, line) };
1087
+ const facts = syntax || require('../languages/cpp-site-syntax').cppSiteSyntax(node);
1088
+ const { namespaces, memberOf, recovered } = facts;
1089
+ const classes = [...facts.classes];
1090
+ // A class body the raw tree lost (macro-heavy headers, conditional
1091
+ // members): the indexed nested classes around the line (parsed from
1092
+ // the recovered source) still name their owners (fix #396: `union
1093
+ // Data` inside `GenericValue`).
1094
+ if (classes.length > 0) {
1095
+ const symbols = index.files.get(file)?.symbols || [];
1096
+ const around = name => symbols.find(s => s.name === name && TYPE_DECL_KINDS.has(s.type) &&
1097
+ s.startLine <= line && line <= s.endLine);
1098
+ for (let owner = around(classes[0].split('::').pop())?.enclosingType, depth = 0;
1099
+ owner && depth < 8 && !classes.includes(owner); depth++) {
1100
+ classes.unshift(owner);
1101
+ owner = around(owner)?.enclosingType;
1102
+ }
1103
+ }
1104
+ const prefix = macroNamespacePrefixAt(index, file, line);
1105
+ const own = namespaces.join('::');
1106
+ let namespace = prefix && own ? `${prefix}::${own}` : prefix || own;
1107
+ let className = classes.length > 0 ? classes.join('::') : memberOf;
1108
+ // Macro-heavy code can break the raw tree so that an enclosing
1109
+ // namespace block closes early; the indexed declarations around the
1110
+ // line (parsed from the recovered source) still carry it.
1111
+ const indexed = namespaceAt(index, file, line);
1112
+ if (indexed && indexed !== namespace && (namespace === '' || own === '' ||
1113
+ indexed.endsWith(`::${own}`) || indexed.startsWith(`${namespace}::`) ||
1114
+ (recovered && indexed.startsWith(namespace)))) namespace = indexed;
1115
+ // Out-of-line member bodies: the index (parsed from the recovered
1116
+ // source) knows their class even when a macro before the return type
1117
+ // broke the literal head; the head's own qualifier is the fallback.
1118
+ if (classes.length === 0) className = enclosingClassIn(index, file, line) || memberOf;
1119
+ return { namespace, className };
1120
+ }
1121
+
855
1122
  class TypeReferenceResolver {
856
1123
  constructor(index, def, name) {
857
1124
  this.index = index;
@@ -862,6 +1129,7 @@ class TypeReferenceResolver {
862
1129
  this.all = all;
863
1130
  this.family = family;
864
1131
  this.language = language;
1132
+ this.declarationKinds = TYPE_DECL_KINDS;
865
1133
  this.pinFiles = new Set([...group].map(d => d.file));
866
1134
  this.memo = new Map();
867
1135
  this._embedders = undefined;
@@ -869,6 +1137,41 @@ class TypeReferenceResolver {
869
1137
 
870
1138
  inGroup(d) { return this.group.has(d); }
871
1139
 
1140
+ /**
1141
+ * The Python function scope the pinned class statement binds its name
1142
+ * in (fix #424), when that scope binds the name by other statements
1143
+ * too: one variable, renamed whole (its other bindings are the plan's
1144
+ * alternative bindings), so a reference resolving to that scope names
1145
+ * the pin's variable. Null for module and class-body scopes, which the
1146
+ * module lookup and class-body order decide.
1147
+ */
1148
+ pythonPinScope(tree) {
1149
+ if (this.family !== 'python' || !tree) return null;
1150
+ if (this._pythonPinScope && this._pythonPinScope.tree === tree) return this._pythonPinScope.scope;
1151
+ let scope = null;
1152
+ const row = (this.def.nameLine || this.def.startLine) - 1;
1153
+ const stack = [tree.rootNode];
1154
+ let classNode = null;
1155
+ while (stack.length > 0 && !classNode) {
1156
+ const node = stack.pop();
1157
+ if (node.startPosition.row > row || node.endPosition.row < row) continue;
1158
+ if (node.type === 'class_definition' && node.childForFieldName('name')?.text === this.name &&
1159
+ node.childForFieldName('name').startPosition.row === row) {
1160
+ classNode = node;
1161
+ break;
1162
+ }
1163
+ for (let i = node.namedChildCount - 1; i >= 0; i--) stack.push(node.namedChild(i));
1164
+ }
1165
+ const owner = classNode && bindingScope(classNode, 'python', {});
1166
+ if (owner?.type === 'function_definition') {
1167
+ const { pythonScopeBindingKinds } = require('../languages/lexical-scope');
1168
+ const sites = pythonScopeBindingKinds(classNode.parent, this.name);
1169
+ if (sites.some(site => !sameNode(site.node, classNode.childForFieldName('name')))) scope = owner;
1170
+ }
1171
+ this._pythonPinScope = { tree, scope };
1172
+ return scope;
1173
+ }
1174
+
872
1175
  /**
873
1176
  * Generic arity written at the token being decided, where arity is part
874
1177
  * of a type's identity (trait genericArityIsIdentity, fix #389): `X` is
@@ -876,9 +1179,14 @@ class TypeReferenceResolver {
876
1179
  * site is not an identifier token (doc comments).
877
1180
  */
878
1181
  siteArity() {
1182
+ if (this.siteSyntax && Number.isInteger(this.siteSyntax.arity)) return this.siteSyntax.arity;
879
1183
  const node = this.siteNode;
880
1184
  if (!node || node.type !== 'identifier' || !langTraits(this.language)?.genericArityIsIdentity) return null;
881
1185
  const parent = node.parent;
1186
+ if (CSHARP_TYPE_DECLS.has(parent?.type) && sameNode(parent.childForFieldName('name'), node)) {
1187
+ const list = parent.namedChildren.find(child => child.type === 'type_parameter_list');
1188
+ return list ? list.namedChildren.filter(child => child.type === 'type_parameter').length : 0;
1189
+ }
882
1190
  // A constructor or finalizer is named after its class without type
883
1191
  // arguments (fix #390: `Strategy(..)` inside `class Strategy<T>`):
884
1192
  // its arity is the enclosing declaration's.
@@ -911,7 +1219,7 @@ class TypeReferenceResolver {
911
1219
  /** Type-like definitions of NAME declared in `file`. */
912
1220
  fileTypes(file, name = this.name) {
913
1221
  return (this.index.symbols.get(name) || []).filter(d => d.file === file &&
914
- TYPE_DECL_KINDS.has(d.type));
1222
+ this.declarationKinds.has(d.type));
915
1223
  }
916
1224
 
917
1225
  /** Container range of a definition inside its own file (innermost
@@ -925,10 +1233,12 @@ class TypeReferenceResolver {
925
1233
  }
926
1234
 
927
1235
  _containerOf(d) {
928
- const entry = this.index.files.get(d.file);
929
1236
  let best = null;
930
- for (const s of entry?.symbols || []) {
1237
+ for (const s of containingDeclarations(this.index, d) || []) {
931
1238
  if (s === d || s.startLine == null || s.endLine == null) continue;
1239
+ if (s.sourceSpan && d.sourceSpan &&
1240
+ (s.sourceSpan.start > d.sourceSpan.start ||
1241
+ s.sourceSpan.end < d.sourceSpan.end)) continue;
932
1242
  if (s.startLine > d.startLine || s.endLine < d.endLine) continue;
933
1243
  if (s.startLine === d.startLine && s.endLine === d.endLine) continue;
934
1244
  if (s.type === 'impl' || s.type === 'field') continue;
@@ -991,7 +1301,9 @@ class TypeReferenceResolver {
991
1301
  if (!entry) return { verdict: 'unknown', reason: 'unindexed-file' };
992
1302
  if (this.family === 'c') return this.resolveCFamily(file, line, name, null);
993
1303
  if (this.family === 'rust') return this.resolveRustName(file, line, name);
994
- const local = this.visibleInFile(file, line, name);
1304
+ // A C# compilation unit can hold several namespace bodies. A type
1305
+ // declared somewhere in the file is not automatically in scope.
1306
+ const local = this.family === 'csharp' ? [] : this.visibleInFile(file, line, name);
995
1307
  if (local.length > 0) {
996
1308
  // A module-scope assignment of the name rebinds it, unless that
997
1309
  // assignment is the type's own declaration (a type alias).
@@ -1013,7 +1325,16 @@ class TypeReferenceResolver {
1013
1325
 
1014
1326
  chase(startFile, name) {
1015
1327
  const { _nameBindingReaches } = require('./callers');
1016
- return _nameBindingReaches(this.index, startFile, name, this.pinFiles, 8, { exactName: true });
1328
+ const reach = _nameBindingReaches(this.index, startFile, name, this.pinFiles, 8,
1329
+ { exactName: true, configurationVerdict: true });
1330
+ // A module binding the name through one alternative of a
1331
+ // configuration item keeps the item's name (fix #424, the #398F
1332
+ // import rule): its importers' and attribute readers' tokens name
1333
+ // the item, never the renamed pin.
1334
+ // Value dependencies still reach the chosen alternative at runtime.
1335
+ // Its importers keep the chooser's name, so they are review sites,
1336
+ // never automatic edits of one alternative's declaration.
1337
+ return reach === 'configuration' ? (this.valueReferences ? 'unknown' : 'no') : reach;
1017
1338
  }
1018
1339
 
1019
1340
  moduleFile(entry, module) {
@@ -1187,29 +1508,12 @@ class TypeReferenceResolver {
1187
1508
  * the namespace of the indexed types around the line.
1188
1509
  */
1189
1510
  csharpSiteNamespace(file, line) {
1511
+ if (this.siteSyntax?.file === file && this.siteSyntax.line === line) {
1512
+ return this.siteSyntax.context.scopes.at(-1)?.namespace || '';
1513
+ }
1190
1514
  const node = this.siteNode && this.siteFile === file && this.siteNode.startPosition.row + 1 === line
1191
1515
  ? this.siteNode : null;
1192
- if (node) {
1193
- const parts = [];
1194
- let root = node;
1195
- for (let current = node.parent; current; current = current.parent) {
1196
- if (current.type === 'namespace_declaration') {
1197
- const nameNode = current.childForFieldName('name');
1198
- if (nameNode) parts.unshift(textOf(nameNode));
1199
- }
1200
- root = current;
1201
- }
1202
- for (let i = 0; i < root.namedChildCount; i++) {
1203
- const child = root.namedChild(i);
1204
- if (child.startIndex > node.startIndex) break;
1205
- if (child.type === 'file_scoped_namespace_declaration') {
1206
- const nameNode = child.childForFieldName('name');
1207
- if (nameNode) parts.unshift(textOf(nameNode));
1208
- break;
1209
- }
1210
- }
1211
- if (parts.length > 0) return parts.join('.');
1212
- }
1516
+ if (node) return require('../languages/csharp-scope').usingScopesOf(node).at(-1)?.namespace || '';
1213
1517
  const { siteNamespaceOf } = require('./type-denotation');
1214
1518
  return siteNamespaceOf(this.index.files.get(file), line);
1215
1519
  }
@@ -1226,10 +1530,10 @@ class TypeReferenceResolver {
1226
1530
  const entry = this.index.files.get(file);
1227
1531
  const { csharpUsings } = require('./type-denotation');
1228
1532
  const candidates = this.all.filter(d => d.name === name && !d.enclosingType);
1229
- const alias = (entry.importBindings || []).some(b => b.name === name && b.kind === 'using');
1230
- if (alias) return { verdict: 'no', reason: 'using-alias' };
1231
1533
  const site = this.csharpSiteNamespace(file, line);
1232
1534
  if (site === undefined) return { verdict: 'unknown', reason: 'unknown-namespace' };
1535
+ const localNested = this.visibleInFile(file, line, name).filter(d => d.enclosingType);
1536
+ if (localNested.length) return this.judge(localNested);
1233
1537
  // Member types of an enclosing class come first, declared in any
1234
1538
  // `partial` part of it (fix #395: `readonly DepthLimiter _d;` in one
1235
1539
  // part of `partial class PropertyValueConverter`, the nested class
@@ -1250,6 +1554,7 @@ class TypeReferenceResolver {
1250
1554
  if (members.length > 0) return this.judge(members);
1251
1555
  }
1252
1556
  }
1557
+ const scope = csharpUsings(this.index, file, site, this.siteNode, this.siteSyntax?.context);
1253
1558
  const chain = [];
1254
1559
  for (let parts = site ? site.split('.') : []; parts.length > 0; parts = parts.slice(0, -1)) {
1255
1560
  chain.push(parts.join('.'));
@@ -1257,15 +1562,20 @@ class TypeReferenceResolver {
1257
1562
  chain.push('');
1258
1563
  for (const ns of chain) {
1259
1564
  const here = candidates.filter(d => (d.namespace || '') === ns);
1565
+ const alias = scope.aliases.has(name) && scope.aliasScopes?.get(name) === ns;
1566
+ if (alias && (here.length || scope.ambiguousAliases.has(name))) {
1567
+ return { verdict: 'unknown', reason: 'ambiguous-type' };
1568
+ }
1260
1569
  if (here.length > 0) return this.judge(here);
1570
+ if (alias) return { verdict: 'no', reason: 'using-alias' };
1571
+ // This namespace's imports are considered before its parent's
1572
+ // declarations; using-static directives can provide nested types.
1573
+ const usings = new Set([...scope.namespaces, ...scope.statics].filter(module =>
1574
+ scope.namespaceScopes.get(module)?.has(ns) || scope.staticScopes.get(module)?.has(ns)));
1575
+ const viaUsing = candidates.filter(d => usings.has(d.namespace || '') ||
1576
+ (d.enclosingType && usings.has(`${d.namespace ? d.namespace + '.' : ''}${d.enclosingType}`)));
1577
+ if (viaUsing.length > 0) return this.judge(viaUsing);
1261
1578
  }
1262
- // Using directives resolved from the namespace each is written in
1263
- // (fix #395: `namespace FluentValidation; using Results;` names
1264
- // FluentValidation.Results); `using static` also brings nested types.
1265
- const scope = csharpUsings(this.index, file);
1266
- const usings = new Set([...scope.namespaces, ...scope.statics]);
1267
- const viaUsing = candidates.filter(d => usings.has(d.namespace || ''));
1268
- if (viaUsing.length > 0) return this.judge(viaUsing);
1269
1579
  if ([...this.group].some(d => d.enclosingType) && this.inheritsFromPinOwner(file)) {
1270
1580
  return { verdict: 'unknown', reason: 'inherited-member-type' };
1271
1581
  }
@@ -1391,19 +1701,20 @@ class TypeReferenceResolver {
1391
1701
  if (entry?.language !== 'rust') continue;
1392
1702
  let content;
1393
1703
  try { content = this.index._readFile(file); } catch { continue; }
1704
+ // A cleanly parsed file holds an invocation of the macro only
1705
+ // where its name token is followed by `!` (fix #435: every file
1706
+ // spelling a type-like macro name was parsed and walked).
1707
+ if (!entry.parseErrorRegions && indexedContent(this.index, file, entry, content) &&
1708
+ !spelledBeforeBang(content, macroName)) continue;
1394
1709
  const tree = this.index._getParsedTree?.(file, content, 'rust') || safeParse(getParser('rust'), content);
1395
1710
  if (!tree) continue;
1396
1711
  const rows = new Set(lineNumbers.map(n => n - 1));
1397
- const stack = [tree.rootNode];
1398
- while (stack.length > 0) {
1399
- const current = stack.pop();
1400
- if (current.type === 'macro_invocation' && rows.has(current.startPosition.row)) {
1401
- const macro = current.childForFieldName('macro');
1402
- if (macro && macro.text.split('::').pop() === macroName) {
1403
- sites.push({ file, line: current.startPosition.row + 1 });
1404
- }
1712
+ for (const current of tree.rootNode.descendantsOfType('macro_invocation')) {
1713
+ if (!rows.has(current.startPosition.row)) continue;
1714
+ const macro = current.childForFieldName('macro');
1715
+ if (macro && macro.text.split('::').pop() === macroName) {
1716
+ sites.push({ file, line: current.startPosition.row + 1 });
1405
1717
  }
1406
- for (let i = current.namedChildCount - 1; i >= 0; i--) stack.push(current.namedChild(i));
1407
1718
  }
1408
1719
  }
1409
1720
  this.memo.set(key, sites);
@@ -1431,7 +1742,7 @@ class TypeReferenceResolver {
1431
1742
  rustModuleVerdict(resolved, name, depth) {
1432
1743
  const { rustModuleItems } = require('./rust-modules');
1433
1744
  const verdicts = [];
1434
- const items = rustModuleItems(this.index, resolved, name).filter(d => TYPE_DECL_KINDS.has(d.type));
1745
+ const items = rustModuleItems(this.index, resolved, name).filter(d => this.declarationKinds.has(d.type));
1435
1746
  if (items.length > 0) verdicts.push(this.judge(items));
1436
1747
  for (const moduleEntry of resolved.entries) {
1437
1748
  const target = this.index.files.get(moduleEntry.file);
@@ -1501,36 +1812,23 @@ class TypeReferenceResolver {
1501
1812
 
1502
1813
  // ── C / C++ ──
1503
1814
 
1504
- enclosingClassAt(file, line) {
1505
- const entry = this.index.files.get(file);
1506
- let best = null;
1507
- for (const s of entry?.symbols || []) {
1508
- if (s.startLine > line || s.endLine < line) continue;
1509
- const isClass = TYPE_DECL_KINDS.has(s.type) && s.type !== 'type';
1510
- const candidate = isClass ? s.name : (s.className || null);
1511
- if (!candidate) continue;
1512
- if (!best || (s.endLine - s.startLine) < (best.span)) {
1513
- best = { name: candidate, span: s.endLine - s.startLine };
1514
- }
1515
- }
1516
- return best?.name || null;
1517
- }
1815
+ enclosingClassAt(file, line) { return enclosingClassIn(this.index, file, line); }
1518
1816
 
1519
1817
  resolveCFamily(file, line, name, qualifierText, options = {}) {
1520
1818
  const entry = this.index.files.get(file);
1521
- const { includeClosure, resolveQualifier } = require('./cpp-scope');
1819
+ const { includeClosure, resolveQualifier } = lazyCppScopeModule();
1522
1820
  const visible = includeClosure(this.index, file);
1523
1821
  if (entry.language === 'c') {
1524
1822
  // C tags (struct/union/enum) live in their own namespace; typedef
1525
1823
  // names in the ordinary one.
1526
1824
  const tagSite = options.tag != null;
1527
1825
  const candidates = this.all.filter(d => d.name === name &&
1528
- (tagSite ? !cOrdinaryName(d) : cOrdinaryName(d)));
1826
+ (this.valueReferences || (tagSite ? !cOrdinaryName(d) : cOrdinaryName(d))));
1529
1827
  const seen = candidates.filter(d => d.file === file || visible.has(d.file));
1530
1828
  if (seen.length > 0) return this.judge(seen);
1531
1829
  return { verdict: 'unknown', reason: 'not-in-include-closure' };
1532
1830
  }
1533
- const spelling = qualifierText ? `${qualifierText}::${name}` : name;
1831
+ const spelling = qualifierText ? `${qualifierText}${qualifierText.endsWith('::') ? '' : '::'}${name}` : name;
1534
1832
  if (qualifierText && this.def.enclosingType) {
1535
1833
  // `Acc<T>::Member` / `Acc<double>::Member` against a member of a
1536
1834
  // specialization: the template arguments pick the class.
@@ -1573,9 +1871,12 @@ class TypeReferenceResolver {
1573
1871
  const site = this.cppSiteContext(file, line);
1574
1872
  const context = { file, line, namespace: site.namespace,
1575
1873
  className: qualifierText && qualifierText.startsWith('::') ? null : site.className };
1576
- const found = resolveQualifier(this.index, context, spelling, visible, 0, { declarations: true });
1874
+ const found = resolveQualifier(this.index, context, spelling, visible, 0,
1875
+ { declarations: true, values: this.valueReferences === true,
1876
+ ...(this.valueReferences && this.siteNode && { valueSite: {
1877
+ file: this.siteFile || file, index: this.siteNode.startIndex } }) });
1577
1878
  if (found.kind === 'namespace') return { verdict: 'no', reason: 'namespace' };
1578
- if (found.kind === 'type') {
1879
+ if (found.kind === 'type' || found.kind === 'value') {
1579
1880
  const declarations = (found.declarations || []).filter(d => !functionLocal(d) || localInScope(d));
1580
1881
  const siblings = this.cppSiblingMembers(declarations);
1581
1882
  if (siblings) {
@@ -1689,7 +1990,7 @@ class TypeReferenceResolver {
1689
1990
  * from file:line (its injected class name), or null. */
1690
1991
  cppBaseNamed(file, line, className, name, depth) {
1691
1992
  if (depth > 3) return null;
1692
- const { includeClosure, resolveQualifier, effectiveNamespace } = require('./cpp-scope');
1993
+ const { includeClosure, resolveQualifier, effectiveNamespace } = lazyCppScopeModule();
1693
1994
  const { splitParentList } = require('./graph-build');
1694
1995
  const visible = includeClosure(this.index, file);
1695
1996
  const simple = String(className).split('::').pop();
@@ -1726,73 +2027,9 @@ class TypeReferenceResolver {
1726
2027
  * other lines outside every indexed symbol keep their real scope.
1727
2028
  */
1728
2029
  cppSiteContext(file, line) {
1729
- const node = this.siteNode && this.siteFile === file && this.siteNode.startPosition.row + 1 === line
1730
- ? this.siteNode : null;
1731
- const { macroNamespacePrefixAt, namespaceAt } = require('./cpp-scope');
1732
- if (!node) return { namespace: namespaceAt(this.index, file, line), className: this.enclosingClassAt(file, line) };
1733
- const namespaces = [];
1734
- const classes = [];
1735
- let memberOf = null;
1736
- let recovered = false;
1737
- for (let current = node.parent; current; current = current.parent) {
1738
- if (current.type === 'ERROR') recovered = true;
1739
- if (current.type === 'namespace_definition') {
1740
- const nameNode = current.childForFieldName('name');
1741
- if (nameNode) namespaces.unshift(textOf(nameNode));
1742
- } else if (C_TYPE_SPECIFIERS.has(current.type)) {
1743
- const body = current.childForFieldName('body');
1744
- const nameNode = current.childForFieldName('name');
1745
- if (body && contains(body, node) && nameNode) {
1746
- // Specializations keep their arguments (`Acc<double>`), as
1747
- // the index names them; an out-of-class nested definition
1748
- // (`struct List<K>::Node`) is in its owner's scope too
1749
- // (fix #396).
1750
- const segments = textOf(nameNode).split('::');
1751
- classes.unshift(segments.slice(-2).join('::'));
1752
- }
1753
- } else if (current.type === 'function_definition' && !memberOf && classes.length === 0) {
1754
- let declarator = current.childForFieldName('declarator');
1755
- while (declarator && declarator.type !== 'function_declarator' && declarator.childForFieldName?.('declarator')) {
1756
- declarator = declarator.childForFieldName('declarator');
1757
- }
1758
- const qualified = declarator?.childForFieldName?.('declarator');
1759
- if (qualified?.type === 'qualified_identifier') {
1760
- const scope = qualified.childForFieldName('scope');
1761
- // `List<K>::Node::f`: Node's scope, then List's (fix #396).
1762
- if (scope) memberOf = qualifiedScopeText(qualified) || textOf(scope).split('::').pop();
1763
- }
1764
- }
1765
- }
1766
- // A class body the raw tree lost (macro-heavy headers, conditional
1767
- // members): the indexed nested classes around the line (parsed from
1768
- // the recovered source) still name their owners (fix #396: `union
1769
- // Data` inside `GenericValue`).
1770
- if (classes.length > 0) {
1771
- const symbols = this.index.files.get(file)?.symbols || [];
1772
- const around = name => symbols.find(s => s.name === name && TYPE_DECL_KINDS.has(s.type) &&
1773
- s.startLine <= line && line <= s.endLine);
1774
- for (let owner = around(classes[0].split('::').pop())?.enclosingType, depth = 0;
1775
- owner && depth < 8 && !classes.includes(owner); depth++) {
1776
- classes.unshift(owner);
1777
- owner = around(owner)?.enclosingType;
1778
- }
1779
- }
1780
- const prefix = macroNamespacePrefixAt(this.index, file, line);
1781
- const own = namespaces.join('::');
1782
- let namespace = prefix && own ? `${prefix}::${own}` : prefix || own;
1783
- let className = classes.length > 0 ? classes.join('::') : memberOf;
1784
- // Macro-heavy code can break the raw tree so that an enclosing
1785
- // namespace block closes early; the indexed declarations around the
1786
- // line (parsed from the recovered source) still carry it.
1787
- const indexed = namespaceAt(this.index, file, line);
1788
- if (indexed && indexed !== namespace && (namespace === '' || own === '' ||
1789
- indexed.endsWith(`::${own}`) || indexed.startsWith(`${namespace}::`) ||
1790
- (recovered && indexed.startsWith(namespace)))) namespace = indexed;
1791
- // Out-of-line member bodies: the index (parsed from the recovered
1792
- // source) knows their class even when a macro before the return type
1793
- // broke the literal head; the head's own qualifier is the fallback.
1794
- if (classes.length === 0) className = this.enclosingClassAt(file, line) || memberOf;
1795
- return { namespace, className };
2030
+ const syntax = this.siteFile === file && this.siteContextSyntax?.line === line
2031
+ ? this.siteContextSyntax.syntax : null;
2032
+ return cppSiteContextOf(this.index, this.siteNode, this.siteFile, file, line, syntax);
1796
2033
  }
1797
2034
 
1798
2035
  // ── Qualified names ──
@@ -1847,6 +2084,21 @@ class TypeReferenceResolver {
1847
2084
  return { verdict: 'no', reason: 'member' };
1848
2085
  }
1849
2086
  case 'go': {
2087
+ // A local declared before the selector hides the package
2088
+ // name in its block (fix #420: `conf := conf.Default` then
2089
+ // `conf.Limit` reads a field of the local).
2090
+ if (qualifierNode?.type === 'identifier') {
2091
+ if (!this._goScopes) this._goScopes = new WeakMap();
2092
+ let memo = this._goScopes.get(qualifierNode.tree);
2093
+ if (!memo) { memo = new Map(); this._goScopes.set(qualifierNode.tree, memo); }
2094
+ const { referenceScope } = require('../languages/lexical-scope');
2095
+ const scope = referenceScope(qualifierNode, 'go', memo, text);
2096
+ if (scope === 'unknown') return { verdict: 'unknown', reason: 'qualifier-scope' };
2097
+ if (scope && scope !== 'module' && scope !== 'none') {
2098
+ if (tok.role === 'member') return this.goFieldVerdict(this.siteNode, file);
2099
+ return { verdict: 'no', reason: 'local-binding' };
2100
+ }
2101
+ }
1850
2102
  const binding = (entry.importBindings || []).find(b => (b.alias || b.name) === text && b.module);
1851
2103
  if (!binding) {
1852
2104
  if (tok.role === 'member') return this.goFieldVerdict(this.siteNode, file);
@@ -1941,21 +2193,9 @@ class TypeReferenceResolver {
1941
2193
  return pythonModulePath(this.index, file, receiver, this.memo);
1942
2194
  }
1943
2195
 
1944
- javaPackages() {
1945
- const key = 'java-packages';
1946
- if (!this.memo.has(key)) {
1947
- const packages = new Set();
1948
- for (const entry of this.index.files.values()) {
1949
- if (entry.language !== 'java') continue;
1950
- for (const s of entry.symbols || []) {
1951
- if (!s.namespace) continue;
1952
- const parts = s.namespace.split('.');
1953
- for (let i = 1; i <= parts.length; i++) packages.add(parts.slice(0, i).join('.'));
1954
- }
1955
- }
1956
- this.memo.set(key, packages);
1957
- }
1958
- return this.memo.get(key);
2196
+ /** Does the project declare Java package `name` or one below it? */
2197
+ javaPackageDeclared(name) {
2198
+ return projectHas(this.index, 'java-package-prefixes', name, javaPackagePrefixes);
1959
2199
  }
1960
2200
 
1961
2201
  resolveJavaQualified(file, line, text, tok) {
@@ -1966,7 +2206,7 @@ class TypeReferenceResolver {
1966
2206
  return prefix === qualifier;
1967
2207
  });
1968
2208
  if (byFqnPrefix.length > 0) return this.judge(byFqnPrefix);
1969
- if (this.javaPackages().has(qualifier)) return { verdict: 'no', reason: 'other-package' };
2209
+ if (this.javaPackageDeclared(qualifier)) return { verdict: 'no', reason: 'other-package' };
1970
2210
  const last = qualifier.split('.').pop();
1971
2211
  const nested = candidates.filter(d => d.enclosingType === last);
1972
2212
  if (nested.length > 0) {
@@ -1984,25 +2224,17 @@ class TypeReferenceResolver {
1984
2224
  return { verdict: 'unknown', reason: 'qualified-name' };
1985
2225
  }
1986
2226
 
1987
- csharpNamespaces() {
1988
- const key = 'cs-namespaces';
1989
- if (!this.memo.has(key)) {
1990
- const namespaces = new Set();
1991
- for (const entry of this.index.files.values()) {
1992
- if (entry.language !== 'csharp') continue;
1993
- for (const s of entry.symbols || []) {
1994
- if (!s.namespace) continue;
1995
- const parts = s.namespace.split('.');
1996
- for (let i = 1; i <= parts.length; i++) namespaces.add(parts.slice(0, i).join('.'));
1997
- }
1998
- }
1999
- this.memo.set(key, namespaces);
2000
- }
2001
- return this.memo.get(key);
2227
+ /** Does the project declare C# namespace `name` or one below it? */
2228
+ csharpNamespaceDeclared(name) {
2229
+ return projectHas(this.index, 'cs-namespace-prefixes', name, csharpNamespacePrefixes);
2002
2230
  }
2003
2231
 
2004
2232
  resolveCSharpQualified(file, line, text, tok) {
2005
- const entry = this.index.files.get(file);
2233
+ // A constructed containing type has an arity at every owner
2234
+ // segment. Erasing those arguments would merge Outer.Inner with
2235
+ // Outer<T>.Inner. Until that owner chain is proven, keep the site
2236
+ // visible without supplying a type-identity witness.
2237
+ if (text.includes('<')) return { verdict: 'unknown', reason: 'generic-containing-type' };
2006
2238
  // An `extern alias` qualifier (`Old::Lib.Core.Widget`, fix #401)
2007
2239
  // names an assembly reference: a project namespace of that spelling
2008
2240
  // may or may not be the assembly's.
@@ -2014,26 +2246,39 @@ class TypeReferenceResolver {
2014
2246
  return spelled ? { verdict: 'unknown', reason: 'extern-alias' } : { verdict: 'no', reason: 'external-namespace' };
2015
2247
  }
2016
2248
  let qualifier = text.replace(/<.*?>/g, '').replace(/^global::/, '');
2017
- // A using alias names the qualifier's head.
2249
+ // A using alias names the qualifier's head in this namespace body.
2018
2250
  const head = qualifier.split('.')[0];
2019
- const alias = (entry.importBindings || []).find(b => b.name === head && b.kind === 'using' && b.module);
2020
- if (alias) qualifier = [alias.module, ...qualifier.split('.').slice(1)].join('.');
2021
- const site = text.startsWith('global::') || alias ? '' : (this.csharpSiteNamespace(file, line) || '');
2251
+ const { csharpUsings } = require('./type-denotation');
2252
+ const namespace = this.csharpSiteNamespace(file, line);
2253
+ if (namespace === undefined) return { verdict: 'unknown', reason: 'unknown-namespace' };
2254
+ const scope = csharpUsings(this.index, file, namespace, this.siteNode);
2255
+ const global = text.startsWith('global::');
2022
2256
  const chain = [];
2023
- for (let parts = site ? site.split('.') : []; parts.length > 0; parts = parts.slice(0, -1)) {
2257
+ for (let parts = !global && namespace ? namespace.split('.') : []; parts.length > 0; parts = parts.slice(0, -1)) {
2024
2258
  chain.push(parts.join('.'));
2025
2259
  }
2026
2260
  chain.push('');
2027
2261
  const candidates = this.all.filter(d => d.name === this.name);
2028
- const namespaces = this.csharpNamespaces();
2029
2262
  for (const ns of chain) {
2030
2263
  const full = ns ? `${ns}.${qualifier}` : qualifier;
2031
2264
  const hits = candidates.filter(d => {
2032
2265
  const prefix = `${d.namespace || ''}${d.enclosingType ? (d.namespace ? '.' : '') + d.enclosingType : ''}`;
2033
2266
  return prefix === full;
2034
2267
  });
2268
+ const alias = !global && scope.aliasScopes?.get(head) === ns && scope.aliases.get(head);
2269
+ if (alias && (hits.length || scope.ambiguousAliases.has(head))) {
2270
+ return { verdict: 'unknown', reason: 'ambiguous-type' };
2271
+ }
2035
2272
  if (hits.length > 0) return this.judge(hits);
2036
- if (namespaces.has(full)) return { verdict: 'no', reason: 'other-namespace' };
2273
+ if (this.csharpNamespaceDeclared(full)) return { verdict: 'no', reason: 'other-namespace' };
2274
+ if (alias) {
2275
+ if (scope.aliasSyntax.get(head)?.includes('<')) {
2276
+ return { verdict: 'unknown', reason: 'generic-alias-owner' };
2277
+ }
2278
+ const expanded = [alias, ...qualifier.split('.').slice(1)].join('.');
2279
+ return this.resolveCSharpQualified(file, line, 'global::' + expanded, tok);
2280
+ }
2281
+ if (this.csharpNamespaceDeclared(ns ? `${ns}.${head}` : head)) return { verdict: 'no', reason: 'other-namespace' };
2037
2282
  }
2038
2283
  const last = qualifier.split('.').pop();
2039
2284
  if (candidates.some(d => d.enclosingType === last)) {
@@ -2198,8 +2443,7 @@ const PY_ANNOTATION_STOP = new Set(['block', 'module', 'class_definition', 'func
2198
2443
  * Decide every name token on the ground lines of a type rename.
2199
2444
  * @returns {{ files: Map<string, {relativePath, lines: Map<number, {edits: number[], reviews: Array<{column, reason}>, skips: number, text: boolean, tokens: number}>}> }}
2200
2445
  */
2201
- function typeReferenceSites(index, def, name, groundSet) {
2202
- const resolver = new TypeReferenceResolver(index, def, name);
2446
+ function typeReferenceSites(index, def, name, groundSet, resolver = new TypeReferenceResolver(index, def, name)) {
2203
2447
  const files = new Map();
2204
2448
  const perFile = groundSet?.perFile || new Map();
2205
2449
  const sortedFiles = [...perFile.keys()].sort();
@@ -2246,6 +2490,8 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2246
2490
  const name = resolver.name;
2247
2491
  let content;
2248
2492
  try { content = index._readFile(file); } catch { return null; }
2493
+ const local = resolver.readLocalFile?.(file, entry, language, lineNumbers, content);
2494
+ if (local) return local;
2249
2495
  let htmlView = null;
2250
2496
  if (language === 'html') {
2251
2497
  htmlView = htmlScriptView(content, name);
@@ -2254,7 +2500,8 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2254
2500
  const parser = getParser(language);
2255
2501
  if (!parser) return null;
2256
2502
  const tree = htmlView ? safeParse(parser, htmlView.script)
2257
- : index._getParsedTree?.(file, content, language) || safeParse(parser, content);
2503
+ : (resolver.valueReferences && require('./query-bodies').localQueryTree(index, file, entry, language, name, content)) ||
2504
+ index._getParsedTree?.(file, content, language) || safeParse(parser, content);
2258
2505
  const lines = new Map();
2259
2506
  const lineOf = row => {
2260
2507
  let slot = lines.get(row + 1);
@@ -2299,15 +2546,20 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2299
2546
  };
2300
2547
  for (const at of occurrences) {
2301
2548
  let node = root.descendantForIndex(at, at + name.length);
2549
+ // An extra (a comment) inside an aliased rule can carry the
2550
+ // alias as its type, depending on which traversal first wrapped
2551
+ // the node (fix #420: `{ // x }` read as `interface_body` after a
2552
+ // children walk, as `comment` otherwise): its grammar type is
2553
+ // the stable one.
2554
+ const type = node && (node.isExtra ? node.grammarType || node.type : node.type);
2302
2555
  // The smallest node holding the text: a token, a text or macro
2303
2556
  // leaf, or (between tokens) a container the walk would not read.
2304
- while (node && !TOKEN_TYPES.has(node.type) && !TEXT_NODE.test(node.type) &&
2305
- node.type !== 'preproc_arg' && !(node.type === 'ERROR' && node.namedChildCount === 0)) {
2557
+ if (node && !TOKEN_TYPES.has(type) && !TEXT_NODE.test(type) &&
2558
+ type !== 'preproc_arg' && !(type === 'ERROR' && node.namedChildCount === 0)) {
2306
2559
  node = null;
2307
2560
  }
2308
2561
  if (!node || seen.has(node.id) || !overlaps(node)) continue;
2309
2562
  seen.add(node.id);
2310
- const type = node.type;
2311
2563
  if (TOKEN_TYPES.has(type)) {
2312
2564
  const length = node.endIndex - node.startIndex;
2313
2565
  if ((length === name.length || length === nameBytes) && node.text === name) found.push(node);
@@ -2321,9 +2573,10 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2321
2573
  // code, read as tokens of their own).
2322
2574
  if (node.namedChildCount !== 0 && !type.includes('comment')) continue;
2323
2575
  mark(node, 'text');
2324
- if (resolver.family === 'csharp' && type === 'comment' && marks) docComments.push(node);
2576
+ if (!resolver.valueReferences && resolver.family === 'csharp' && type === 'comment' && marks) docComments.push(node);
2325
2577
  if (resolver.family === 'python' && type === 'string_content' &&
2326
- (pythonTypeExpressionString(node, entry) || pythonDunderAllEntry(node, name))) found.push(node);
2578
+ ((!resolver.valueReferences && pythonTypeExpressionString(node, entry)) ||
2579
+ pythonDunderAllEntry(node, name))) found.push(node);
2327
2580
  }
2328
2581
  return found;
2329
2582
  };
@@ -2336,8 +2589,14 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2336
2589
  // a persisted blank reads through the recovered tree too.
2337
2590
  const persistedBlanks = resolver.family === 'c' && Array.isArray(entry.recoveryBlanks) &&
2338
2591
  entry.recoveryBlanks.length > 0 ? entry.recoveryBlanks : null;
2592
+ // A decoration can make a record look like a clean function whose
2593
+ // fields look like local declarations. Recover the pinned value's
2594
+ // enclosing header too, using the same blanks the index verified.
2595
+ const valueDeclarationHeader = resolver.valueReferences && persistedBlanks && resolver.def.file === file &&
2596
+ tokens.some(node => node.startPosition.row + 1 === (resolver.def.nameLine || resolver.def.startLine) &&
2597
+ enclosingHeaderHoldsBlank(node, persistedBlanks));
2339
2598
  if (resolver.family === 'c' && ((tree.rootNode.hasError && tokens.some(statementHasError)) ||
2340
- (persistedBlanks && tokens.some(node => statementHoldsBlank(node, persistedBlanks))))) {
2599
+ (persistedBlanks && tokens.some(node => statementHoldsBlank(node, persistedBlanks))) || valueDeclarationHeader)) {
2341
2600
  // Only when the literal parse breaks around a token: the recovery
2342
2601
  // re-parses the file.
2343
2602
  let module;
@@ -2393,6 +2652,7 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2393
2652
  .map(t => ({ node: t.node, scope: bindingScope(t.node, resolver.family, t.role), role: t.role }));
2394
2653
  const typeParams = classified.filter(t => t.role.role === 'skip' && t.role.typeParam)
2395
2654
  .map(t => ({ node: t.node, scope: typeParamScope(t.node) }));
2655
+ let fileLocalOnly = true;
2396
2656
  for (const token of classified) {
2397
2657
  const row = token.node.startPosition.row;
2398
2658
  const slot = lineOf(row);
@@ -2404,24 +2664,97 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
2404
2664
  resolver.siteNode = token.node;
2405
2665
  resolver.siteFile = file;
2406
2666
  // A position no rule decides is a review item, never an edit or a skip.
2407
- const decision = decide(resolver, file, entry, token, bindings, typeParams) ||
2667
+ const decision = (resolver.decideToken
2668
+ ? resolver.decideToken(file, entry, token, bindings, typeParams)
2669
+ : decide(resolver, file, entry, token, bindings, typeParams)) ||
2408
2670
  { verdict: 'unknown', reason: 'unmodelled-position' };
2671
+ if (!decision.fileLocalOnly) fileLocalOnly = false;
2409
2672
  resolver.siteNode = null;
2410
2673
  // An `__all__` entry is a code reference: once edited it is no
2411
2674
  // longer a text-only review item.
2412
2675
  if (token.role.role === 'dunder-all') slot.stringAnnotation = true;
2413
- if (decision.verdict === 'yes') slot.edits.push(token.node.startPosition.column);
2676
+ // The impact band (fix #428) reads each token's syntactic role and
2677
+ // why a token names something else; a rename reads only verdicts.
2678
+ if (resolver.collectRoles) {
2679
+ // A Python annotation is an expression in a type position.
2680
+ const role = token.role.role === 'value' && resolver.family === 'python' &&
2681
+ ancestor(token.node, PY_ANNOTATION, PY_ANNOTATION_STOP) ? 'type' : token.role.role;
2682
+ (slot.roles ||= new Map()).set(token.node.startPosition.column, role);
2683
+ if (constructionHead(token.node)) (slot.constructionColumns ||= []).push(token.node.startPosition.column);
2684
+ }
2685
+ if (decision.verdict === 'yes') {
2686
+ slot.edits.push(token.node.startPosition.column);
2687
+ // A binding renamed by aliasing its import (`import x as y`).
2688
+ if (decision.importAlias) (slot.importAliases ||= []).push(token.node.startPosition.column);
2689
+ }
2414
2690
  else if (decision.verdict === 'unknown') {
2415
2691
  slot.reviews.push({ column: token.node.startPosition.column, reason: decision.reason || 'unresolved' });
2416
2692
  } else {
2417
- slot.skips++;
2418
- slot.skipColumns.push(token.node.startPosition.column);
2693
+ skipToken(resolver, slot, token.node.startPosition.column, decision.reason);
2419
2694
  }
2420
2695
  }
2421
2696
  for (const comment of docComments) decideDocCrefs(resolver, file, comment, lineOf(comment.startPosition.row));
2697
+ if (resolver.family === 'c' && !resolver.valueReferences) decideReplacementListCasts(resolver, file, lines);
2698
+ resolver.rememberLocalFile?.(file, lines, fileLocalOnly);
2422
2699
  return lines;
2423
2700
  }
2424
2701
 
2702
+ /** A token that names something else: counted, and why when asked. */
2703
+ function skipToken(resolver, slot, column, reason) {
2704
+ slot.skips++;
2705
+ slot.skipColumns.push(column);
2706
+ if (resolver.collectRoles) (slot.skipReasons ||= new Map()).set(column, reason || 'other-declaration');
2707
+ }
2708
+
2709
+ // Expressions whose head names the constructed type (fix #428): `S { .. }`
2710
+ // (Rust struct expressions) and `S(..)` (tuple structs, functional casts).
2711
+ const CONSTRUCTION_HEADS = new Map([
2712
+ ['struct_expression', 'name'], ['call_expression', 'function'],
2713
+ ]);
2714
+ const HEAD_WRAPPERS = new Set(['scoped_type_identifier', 'scoped_identifier', 'generic_type', 'generic_function']);
2715
+
2716
+ /** Is this token the head of a construction expression? */
2717
+ function constructionHead(node) {
2718
+ let current = node;
2719
+ for (let depth = 0; depth < 4 && current.parent; depth++) {
2720
+ const parent = current.parent;
2721
+ const field = CONSTRUCTION_HEADS.get(parent.type);
2722
+ if (field) return sameNode(parent.childForFieldName(field), current);
2723
+ if (!HEAD_WRAPPERS.has(parent.type)) return false;
2724
+ // A path's last segment names the type; a qualifier does not.
2725
+ const name = parent.childForFieldName('name') || parent.childForFieldName('type');
2726
+ if (name && !sameNode(name, current)) return false;
2727
+ current = parent;
2728
+ }
2729
+ return false;
2730
+ }
2731
+
2732
+ /**
2733
+ * A cast spelled in a C/C++ replacement list (`#define D(t, p) t[(U16)(p)]`,
2734
+ * fix #427): the name is the type its file's lookup finds where the macro is
2735
+ * defined, so a line whose every occurrence of the name is such a cast to
2736
+ * the renamed type is an edit; any other replacement-list text stays a
2737
+ * review item.
2738
+ */
2739
+ function decideReplacementListCasts(resolver, file, lines) {
2740
+ const { getCachedCalls, _recordIsNoCall } = require('./callers');
2741
+ let records = null;
2742
+ const fileLines = resolver.index._getFileLines(file);
2743
+ const word = new RegExp(`(?<![A-Za-z0-9_$])${resolver.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9_$])`, 'g');
2744
+ for (const [line, slot] of lines) {
2745
+ if (!slot.macroBody || slot.tokens > 0 || slot.reviews.length > 0) continue;
2746
+ records ||= getCachedCalls(resolver.index, file) || [];
2747
+ const casts = records.filter(call => call.line === line && call.name === resolver.name &&
2748
+ call.parenthesizedCallee && call.inMacroBody && Number.isInteger(call.column));
2749
+ if (casts.length === 0) continue;
2750
+ const occurrences = (String(fileLines[line - 1] || '').match(word) || []).length;
2751
+ if (occurrences !== casts.length || !casts.every(call => _recordIsNoCall(resolver.index, file, call))) continue;
2752
+ if (resolver.resolveName(file, line)?.verdict !== 'yes') continue;
2753
+ slot.tokens += casts.length;
2754
+ slot.edits.push(...casts.map(call => call.column));
2755
+ }
2756
+ }
2757
+
2425
2758
  /**
2426
2759
  * C# XML documentation `cref` attributes are references the compiler
2427
2760
  * resolves (CS1574 when documentation is generated): each spelling of the
@@ -2463,20 +2796,20 @@ function decideDocCrefs(resolver, file, comment, slot) {
2463
2796
  handled = true;
2464
2797
  if (verdict.verdict === 'yes') slot.edits.push(column);
2465
2798
  else if (verdict.verdict === 'unknown') slot.reviews.push({ column, reason: 'doc-cref' });
2466
- else {
2467
- slot.skips++;
2468
- slot.skipColumns.push(column);
2469
- }
2799
+ else skipToken(resolver, slot, column, verdict.reason);
2800
+ if (resolver.collectRoles) (slot.roles ||= new Map()).set(column, 'doc-cref');
2470
2801
  }
2471
2802
  }
2472
2803
  if (handled) slot.stringAnnotation = true;
2473
2804
  }
2474
2805
 
2475
- function shadowOf(token, bindings, space, family) {
2806
+ function shadowOf(token, bindings, space, family, pinScope = null) {
2476
2807
  let verdict = null;
2477
2808
  for (const binding of bindings) {
2478
2809
  if (sameNode(binding.node, token.node)) continue;
2479
2810
  if (!binding.scope || !contains(binding.scope, token.node)) continue;
2811
+ // Another binding of the pinned class's own variable (fix #424).
2812
+ if (pinScope && sameNode(binding.scope, pinScope)) continue;
2480
2813
  // Type positions are a separate namespace outside Python/JS values.
2481
2814
  if (space === 'type' && family !== 'python') continue;
2482
2815
  if (binding.role.patternBinding) return 'unknown';
@@ -2517,20 +2850,6 @@ function statementHasError(node) {
2517
2850
  return false;
2518
2851
  }
2519
2852
 
2520
- /** The class path a qualified declarator's scope names, without its
2521
- * leading namespace-looking segments: `ns::List<K>::Node::f` -> the scope
2522
- * segments `List<K>::Node` (the last two at most). */
2523
- function qualifiedScopeText(qualified) {
2524
- const scopes = [];
2525
- let current = qualified;
2526
- while (current?.type === 'qualified_identifier') {
2527
- const scope = current.childForFieldName('scope');
2528
- if (scope) scopes.push(textOf(scope));
2529
- current = current.childForFieldName('name');
2530
- }
2531
- return scopes.length > 0 ? scopes.slice(-2).join('::') : null;
2532
- }
2533
-
2534
2853
  /** Does a persisted recovery blank fall inside the declaration or
2535
2854
  * statement holding this token? (`blanks`: sorted [start, end] ranges) */
2536
2855
  function statementHoldsBlank(node, blanks) {
@@ -2539,8 +2858,19 @@ function statementHoldsBlank(node, blanks) {
2539
2858
  if (/(declaration|definition|statement)$/.test(current.type)) { statement = current; break; }
2540
2859
  }
2541
2860
  if (!statement) return false;
2542
- const start = statement.startIndex;
2543
- const end = statement.endIndex;
2861
+ return rangeHoldsBlank(statement.startIndex, statement.endIndex, blanks);
2862
+ }
2863
+
2864
+ function enclosingHeaderHoldsBlank(node, blanks) {
2865
+ for (let owner = node.parent; owner; owner = owner.parent) {
2866
+ const body = owner.childForFieldName('body');
2867
+ if (body && body.startIndex <= node.startIndex &&
2868
+ rangeHoldsBlank(owner.startIndex, body.startIndex, blanks)) return true;
2869
+ }
2870
+ return false;
2871
+ }
2872
+
2873
+ function rangeHoldsBlank(start, end, blanks) {
2544
2874
  let lo = 0;
2545
2875
  let hi = blanks.length;
2546
2876
  while (lo < hi) {
@@ -2589,7 +2919,7 @@ function macroArgumentOtherUse(resolver, file, node) {
2589
2919
  : (list.namedChildren || []).filter(child => !child.type.endsWith('comment'));
2590
2920
  const index = args.findIndex(arg => sameNode(arg, argument));
2591
2921
  if (index < 0 || argument.text !== node.text) return null;
2592
- const { includeClosure } = require('./cpp-scope');
2922
+ const { includeClosure } = lazyCppScopeModule();
2593
2923
  const visible = includeClosure(resolver.index, file);
2594
2924
  const macros = (resolver.index.symbols.get(callee.text) || []).filter(d => d.type === 'macro' &&
2595
2925
  d.functionLike && (d.file === file || visible.has(d.file)));
@@ -2661,12 +2991,17 @@ function decide(resolver, file, entry, token, bindings, typeParams) {
2661
2991
  // the class its qualifier spells.
2662
2992
  const spelled = cppPrefix(node);
2663
2993
  if (spelled == null) return { verdict: 'unknown', reason: 'constructor-owner' };
2664
- const segments = spelled.split('::');
2665
- // `Sink<Mutex>::Sink(...)`: template arguments are not part of
2666
- // the class name the constructor repeats (fix #387).
2667
- const last = require('./cpp-scope').stripTemplateArguments(segments.pop()).trim();
2668
- if (last !== resolver.name) return { verdict: 'no', reason: 'other-type' };
2669
- return resolver.resolveCFamily(file, line, last, segments.length > 0 ? segments.join('::') : null);
2994
+ const { resolveQualifier, includeClosure } = lazyCppScopeModule();
2995
+ const site = resolver.cppSiteContext(file, line);
2996
+ // The qualifier may be an alias of the declaring class. Resolve
2997
+ // its type identity, rather than renaming the alias declaration.
2998
+ const owner = resolveQualifier(resolver.index, { file, line,
2999
+ namespace: site.namespace, className: site.className }, spelled,
3000
+ includeClosure(resolver.index, file));
3001
+ if (owner.kind !== 'type') return { verdict: 'unknown', reason: 'constructor-owner' };
3002
+ const verdict = resolver.judge(owner.classes);
3003
+ return verdict.verdict === 'yes' && !owner.visible
3004
+ ? { verdict: 'unknown', reason: 'not-in-include-closure' } : verdict;
2670
3005
  }
2671
3006
  case 'meminit': {
2672
3007
  // A member named like the type is initialized as a member.
@@ -2677,6 +3012,20 @@ function decide(resolver, file, entry, token, bindings, typeParams) {
2677
3012
  return resolveUnqualifiedToken(resolver, file, token, bindings, typeParams, 'type');
2678
3013
  }
2679
3014
  case 'import': return decideImport(resolver, file, entry, token);
3015
+ case 'qualified-creation': {
3016
+ // Only a member class is created through an outer instance.
3017
+ const members = resolver.all.filter(d => d.name === resolver.name && d.enclosingType);
3018
+ if (members.length === 0) return { verdict: 'no', reason: 'not-member-type' };
3019
+ const outerType = javaExpressionTypeName(role.outer);
3020
+ const owner = outerType ? resolver.resolveTypeNamed(file, line, outerType) : 'unknown';
3021
+ if (owner === 'owner') return { verdict: 'yes' };
3022
+ // Another outer type may inherit the member class; only one that
3023
+ // declares its own member class of the name settles it.
3024
+ if (owner === 'other' && members.some(d => !resolver.inGroup(d) && d.enclosingType === outerType)) {
3025
+ return { verdict: 'no', reason: 'other-type' };
3026
+ }
3027
+ return { verdict: 'unknown', reason: 'qualified-creation' };
3028
+ }
2680
3029
  case 'member':
2681
3030
  return decideMember(resolver, file, token, bindings);
2682
3031
  case 'gofield':
@@ -2689,7 +3038,8 @@ function decide(resolver, file, entry, token, bindings, typeParams) {
2689
3038
  if (prefix) return resolver.resolveCFamily(file, line, resolver.name, prefix);
2690
3039
  }
2691
3040
  if (role.qualifier) {
2692
- return resolver.resolveQualified(file, line, role.qualifier, null, { role: role.role });
3041
+ return resolver.resolveQualified(file, line, role.qualifier,
3042
+ role.aliasQualified ? textOf(role.qualifier) + '::' : null, { role: role.role });
2693
3043
  }
2694
3044
  if (role.aliasQualified) return { verdict: 'unknown', reason: 'extern-alias' };
2695
3045
  if (role.elaborated && family === 'c') {
@@ -2733,8 +3083,10 @@ function memberShadowOf(resolver, file, node) {
2733
3083
  // members of the enclosing classes first: a method of that name (C#
2734
3084
  // `static LogEvent LogEvent(..)`, a C++ member function) hides the
2735
3085
  // type there (fix #393). A constructor is the class's own name.
2736
- const invokedAt = node.parent && ((node.parent.type === 'invocation_expression' ||
2737
- node.parent.type === 'call_expression') && isField(node, 'function'));
3086
+ const invokedName = langTraits(resolver.index.files.get(file)?.language)?.isInvokedName;
3087
+ const invokedAt = invokedName ? invokedName(node, { unqualified: true }) : node.parent &&
3088
+ ((node.parent.type === 'invocation_expression' || node.parent.type === 'call_expression') &&
3089
+ isField(node, 'function'));
2738
3090
  if (invokedAt) {
2739
3091
  const scopeNames = new Set(owners);
2740
3092
  let level = [...owners];
@@ -2765,7 +3117,8 @@ function memberShadowOf(resolver, file, node) {
2765
3117
  function resolveUnqualifiedToken(resolver, file, token, bindings, typeParams, space) {
2766
3118
  const family = resolver.family;
2767
3119
  if (space === 'value' || family === 'python' || family === 'js') {
2768
- const shadow = shadowOf(token, bindings, space, family);
3120
+ const shadow = shadowOf(token, bindings, space, family,
3121
+ file === resolver.def.file ? resolver.pythonPinScope(token.node.tree) : null);
2769
3122
  if (shadow === 'no') return { verdict: 'no', reason: 'local-binding' };
2770
3123
  if (shadow === 'unknown') return { verdict: 'unknown', reason: 'local-binding' };
2771
3124
  if (shadow === 'module') {
@@ -2778,6 +3131,16 @@ function resolveUnqualifiedToken(resolver, file, token, bindings, typeParams, sp
2778
3131
  return { verdict: 'unknown', reason: 'module-rebinding' };
2779
3132
  }
2780
3133
  }
3134
+ // C and C++ name lookup finds a variable or parameter declared before
3135
+ // the name in an enclosing block before any type (C: one ordinary
3136
+ // namespace; C++: the variable hides the class or alias): `ctx(out,
3137
+ // vargs)` after `auto& vargs = ..` passes the variable (fix #427).
3138
+ if (space === 'type' && family === 'c' && bindings.some(binding =>
3139
+ ['variable', 'parameter'].includes(binding.role.why) && !sameNode(binding.node, token.node) &&
3140
+ binding.scope && binding.scope.type !== 'translation_unit' && contains(binding.scope, token.node) &&
3141
+ binding.node.startIndex < token.node.startIndex)) {
3142
+ return { verdict: 'no', reason: 'local-binding' };
3143
+ }
2781
3144
  if (space === 'value') {
2782
3145
  const member = memberShadowOf(resolver, file, token.node);
2783
3146
  if (member) return member;
@@ -2808,6 +3171,9 @@ function declVerdict(resolver, file, declNode, role = {}, nameLine = null) {
2808
3171
  // annotated or decorated declarations) is matched there first.
2809
3172
  const named = nameLine != null ? resolver.fileTypes(file).filter(d => d.nameLine === nameLine && kindOk(d)) : [];
2810
3173
  if (named.length > 0) return resolver.judge(named);
3174
+ const exact = resolver.fileTypes(file).filter(d => d.nameLine == null && kindOk(d) &&
3175
+ d.startLine === startLine && d.endLine === endLine);
3176
+ if (exact.length > 0) return resolver.judge(exact);
2811
3177
  const defs = resolver.fileTypes(file).filter(d => d.nameLine == null && (d.endLine === endLine ||
2812
3178
  (d.startLine <= startLine + 1 && d.startLine >= startLine - 3 &&
2813
3179
  d.endLine >= endLine - 1 && d.endLine <= endLine + 1)) &&
@@ -3160,10 +3526,8 @@ function decideStringAnnotation(resolver, file, entry, node, slot) {
3160
3526
  const column = node.startPosition.column + offset;
3161
3527
  if (verdict.verdict === 'yes') slot.edits.push(column);
3162
3528
  else if (verdict.verdict === 'unknown') slot.reviews.push({ column, reason: verdict.reason || 'string-annotation' });
3163
- else {
3164
- slot.skips++;
3165
- slot.skipColumns.push(column);
3166
- }
3529
+ else skipToken(resolver, slot, column, verdict.reason);
3530
+ if (resolver.collectRoles) (slot.roles ||= new Map()).set(column, 'string-annotation');
3167
3531
  }
3168
3532
  slot.stringAnnotation = true;
3169
3533
  }
@@ -3220,9 +3584,14 @@ function csharpAttributeShortSites(index, def, name) {
3220
3584
  }
3221
3585
 
3222
3586
  module.exports = {
3587
+ cppSiteContextOf,
3223
3588
  csharpAttributeShortSites,
3224
3589
  TYPE_RENAME_KINDS,
3225
3590
  isTypeRenamePin,
3591
+ referenceRole: classify,
3592
+ referenceBindingScope: (node, language, role) => bindingScope(node, familyOf(language), role),
3593
+ referenceTypeParameterScope: typeParamScope,
3594
+ referenceImportVerdict: decideImport,
3226
3595
  typeReferenceSites,
3227
3596
  TypeReferenceResolver,
3228
3597
  familyOf,