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/languages/rust.js CHANGED
@@ -126,6 +126,16 @@ function buildMacroItemRecoveryTree(code, parser, tree) {
126
126
  return { tree: recovered, itemCount, declarationNameStarts, callRecovery };
127
127
  }
128
128
 
129
+ /**
130
+ * The item-body call recovery of an expanded file parsed by the caller
131
+ * (fix #405): what rustMacroItemCallRecovery computes for it once its
132
+ * declaration trees are primed with `tree`. Its walk builds the tree's flat
133
+ * node list, which the call extraction then reads.
134
+ */
135
+ function rustExpandedItemRecovery(code, parser, tree) {
136
+ return buildMacroItemRecoveryTree(code, parser, tree)?.callRecovery || null;
137
+ }
138
+
129
139
  /**
130
140
  * Rust macro invocation bodies are token trees, even when they contain item
131
141
  * declarations verbatim. Reparse only item-position bodies in a byte- and
@@ -682,7 +692,6 @@ function extractAttributesWithArgs(node, codeOrLines) {
682
692
  }
683
693
 
684
694
  // --- Module-scope constants for state object detection ---
685
- const _STATE_PATTERN = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
686
695
 
687
696
  // --- Single-pass helpers: extracted from find* callbacks ---
688
697
 
@@ -747,7 +756,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
747
756
  const paramsNode = node.childForFieldName('parameters');
748
757
 
749
758
  if (nameNode) {
750
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
759
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
751
760
  const text = node.text;
752
761
 
753
762
  const qualifiers = rustFunctionQualifiers(node);
@@ -788,6 +797,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
788
797
  ...(callbackParamTypes && { callbackParamTypes }),
789
798
  startLine,
790
799
  endLine,
800
+ sourceSpan,
791
801
  indent,
792
802
  modifiers,
793
803
  ...(returnType && { returnType }),
@@ -816,7 +826,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
816
826
  const fName = child.childForFieldName('name');
817
827
  const fParams = child.childForFieldName('parameters');
818
828
  if (fName) {
819
- const { startLine, endLine, indent } = nodeToLocation(child, lines);
829
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
820
830
  const visibility = extractVisibility(child.text);
821
831
  const returnType = extractReturnType(child);
822
832
  const docstring = extractRustDocstring(lines, startLine);
@@ -833,6 +843,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
833
843
  ...(iteratorItemType && { iteratorItemType }),
834
844
  startLine,
835
845
  endLine,
846
+ sourceSpan,
836
847
  indent,
837
848
  modifiers,
838
849
  ...(returnType && { returnType }),
@@ -1068,7 +1079,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1068
1079
 
1069
1080
  const nameNode = node.childForFieldName('name');
1070
1081
  if (nameNode) {
1071
- const { startLine, endLine } = nodeToLocation(node, lines);
1082
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1072
1083
  const docstring = extractRustDocstring(lines, startLine);
1073
1084
  const visibility = extractVisibility(node.text);
1074
1085
  const generics = extractGenerics(node);
@@ -1082,6 +1093,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1082
1093
  name: nameNode.text,
1083
1094
  startLine,
1084
1095
  endLine,
1096
+ sourceSpan,
1085
1097
  type: node.type === 'union_item' ? 'union' : 'struct',
1086
1098
  members,
1087
1099
  modifiers,
@@ -1102,7 +1114,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1102
1114
 
1103
1115
  const nameNode = node.childForFieldName('name');
1104
1116
  if (nameNode) {
1105
- const { startLine, endLine } = nodeToLocation(node, lines);
1117
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1106
1118
  const docstring = extractRustDocstring(lines, startLine);
1107
1119
  const visibility = extractVisibility(node.text);
1108
1120
  const generics = extractGenerics(node);
@@ -1114,6 +1126,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1114
1126
  name: nameNode.text,
1115
1127
  startLine,
1116
1128
  endLine,
1129
+ sourceSpan,
1117
1130
  type: 'enum',
1118
1131
  members: extractEnumVariants(node, lines),
1119
1132
  modifiers,
@@ -1133,7 +1146,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1133
1146
 
1134
1147
  const nameNode = node.childForFieldName('name');
1135
1148
  if (nameNode) {
1136
- const { startLine, endLine } = nodeToLocation(node, lines);
1149
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1137
1150
  const docstring = extractRustDocstring(lines, startLine);
1138
1151
  const visibility = extractVisibility(node.text);
1139
1152
  const generics = extractGenerics(node);
@@ -1143,6 +1156,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1143
1156
  name: nameNode.text,
1144
1157
  startLine,
1145
1158
  endLine,
1159
+ sourceSpan,
1146
1160
  type: 'trait',
1147
1161
  members: extractTraitMembers(node, lines),
1148
1162
  modifiers: visibility ? [visibility] : [],
@@ -1161,7 +1175,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1161
1175
  if (processedRanges.has(rangeKey)) return true;
1162
1176
  processedRanges.add(rangeKey);
1163
1177
 
1164
- const { startLine, endLine } = nodeToLocation(node, lines);
1178
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1165
1179
  const implInfo = extractImplInfo(node);
1166
1180
  const docstring = extractRustDocstring(lines, startLine);
1167
1181
  const derefTarget = extractDerefTarget(node, implInfo.traitName);
@@ -1177,6 +1191,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1177
1191
  name: implInfo.name,
1178
1192
  startLine,
1179
1193
  endLine,
1194
+ sourceSpan,
1180
1195
  type: 'impl',
1181
1196
  traitName: implInfo.traitName,
1182
1197
  ...rustBlockItemScope(node),
@@ -1200,7 +1215,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1200
1215
 
1201
1216
  const nameNode = node.childForFieldName('name');
1202
1217
  if (nameNode) {
1203
- const { startLine, endLine } = nodeToLocation(node, lines);
1218
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1204
1219
  const docstring = extractRustDocstring(lines, startLine);
1205
1220
  const visibility = extractVisibility(node.text);
1206
1221
  const attributes = extractAttributes(node, lines);
@@ -1211,6 +1226,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1211
1226
  name: nameNode.text,
1212
1227
  startLine,
1213
1228
  endLine,
1229
+ sourceSpan,
1214
1230
  type: 'module',
1215
1231
  members: [],
1216
1232
  modifiers,
@@ -1228,7 +1244,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1228
1244
 
1229
1245
  const nameNode = node.childForFieldName('name');
1230
1246
  if (nameNode) {
1231
- const { startLine, endLine } = nodeToLocation(node, lines);
1247
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1232
1248
  const docstring = extractRustDocstring(lines, startLine);
1233
1249
  const inferred = _inferMacroReturn(node, nameNode.text);
1234
1250
 
@@ -1255,6 +1271,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1255
1271
  name: nameNode.text,
1256
1272
  startLine,
1257
1273
  endLine,
1274
+ sourceSpan,
1258
1275
  type: 'macro',
1259
1276
  members: [],
1260
1277
  modifiers,
@@ -1284,7 +1301,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1284
1301
 
1285
1302
  const nameNode = node.childForFieldName('name');
1286
1303
  if (nameNode) {
1287
- const { startLine, endLine } = nodeToLocation(node, lines);
1304
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1288
1305
  const docstring = extractRustDocstring(lines, startLine);
1289
1306
  const visibility = extractVisibility(node.text);
1290
1307
  // `pub type StyledString = SpannedString<Style>;` — the alias IS
@@ -1300,6 +1317,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1300
1317
  name: nameNode.text,
1301
1318
  startLine,
1302
1319
  endLine,
1320
+ sourceSpan,
1303
1321
  type: 'type',
1304
1322
  members: [],
1305
1323
  modifiers: visibility ? [visibility] : [],
@@ -1367,15 +1385,19 @@ function extractDerefTarget(implNode, traitName) {
1367
1385
  * Returns true if node was matched, false otherwise
1368
1386
  */
1369
1387
  function _processState(node, objects, lines) {
1370
- // Handle const and static items (only top-level). The declared type
1388
+ // Const and static items have value identity regardless of spelling or
1389
+ // module nesting. Associated items are extracted with their owner.
1390
+ // The declared type
1371
1391
  // (fix #392) types the item when it is a method receiver (`FLAGS.iter()`).
1372
1392
  if (node.type === 'const_item' || node.type === 'static_item') {
1373
- if (!node.parent || node.parent.type !== 'source_file') return false;
1393
+ for (let scope = node.parent; scope; scope = scope.parent) {
1394
+ if (scope.type === 'impl_item' || scope.type === 'trait_item') return false;
1395
+ }
1374
1396
  const nameNode = node.childForFieldName('name');
1375
1397
  if (nameNode) {
1376
1398
  const name = nameNode.text;
1377
- if (_STATE_PATTERN.test(name)) {
1378
- const { startLine, endLine } = nodeToLocation(node, lines);
1399
+ {
1400
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1379
1401
  // Only a plain type path (behind any references) is recorded:
1380
1402
  // a generic wrapper (`Lazy<T>`, `Mutex<T>`) owns the methods
1381
1403
  // or derefs to its argument. A `static mut` is never typed.
@@ -1389,7 +1411,7 @@ function _processState(node, objects, lines) {
1389
1411
  node.children.some(child => child.type === 'mutable_specifier');
1390
1412
  const plain = typeNode && (typeNode.type === 'type_identifier' ||
1391
1413
  typeNode.type === 'scoped_type_identifier');
1392
- objects.push({ name, startLine, endLine,
1414
+ objects.push({ name, startLine, endLine, sourceSpan, ...rustBlockItemScope(node),
1393
1415
  ...(plain && !mutable && { valueType: typeNode.text,
1394
1416
  ...(deref && { valueDerefVia: deref.via }) }) });
1395
1417
  }
@@ -1517,6 +1539,45 @@ function rustRawPointerTypeOf(typeNode) {
1517
1539
  return current.children.some(child => child.type === 'mutable_specifier') ? '*mut' : '*const';
1518
1540
  }
1519
1541
 
1542
+ const primitiveTypeInfoCache = new (require('../core/source-memo').BoundedMap)({ maxEntries: 256 });
1543
+ const derefTypeInfoCache = new (require('../core/source-memo').BoundedMap)({ maxEntries: 256 });
1544
+
1545
+ /** Source-only target of a written deref wrapper; core verifies its binding. */
1546
+ function getDerefTypeInfo(text, parser) {
1547
+ if (typeof text !== 'string' || !text.includes('<')) return null;
1548
+ if (derefTypeInfoCache.has(text)) return derefTypeInfoCache.get(text);
1549
+ const tree = parseTree(parser, `type __ucn_type = ${text};`);
1550
+ const items = tree.rootNode.namedChildren.filter(node => !node.type.endsWith('comment'));
1551
+ const item = items.length === 1 && items[0].type === 'type_item' ? items[0] : null;
1552
+ const declared = !tree.rootNode.hasError && item?.childForFieldName('type');
1553
+ const chain = declared && rustStdDerefChain(declared);
1554
+ const info = chain ? Object.freeze({
1555
+ via: Object.freeze([...chain.via]), innerText: chain.inner.text,
1556
+ }) : null;
1557
+ derefTypeInfoCache.set(text, info);
1558
+ return info;
1559
+ }
1560
+
1561
+ /** Primitive identity of a declared type, independent of nominal names. */
1562
+ function getPrimitiveTypeInfo(text, parser) {
1563
+ if (typeof text !== 'string' || (!text.includes('[') && !text.includes('*'))) return null;
1564
+ if (primitiveTypeInfoCache.has(text)) return primitiveTypeInfoCache.get(text);
1565
+ const tree = parseTree(parser, `type __ucn_type = ${text};`);
1566
+ const items = tree.rootNode.namedChildren.filter(node => !node.type.endsWith('comment'));
1567
+ const item = items.length === 1 && items[0].type === 'type_item' ? items[0] : null;
1568
+ const declared = !tree.rootNode.hasError && item?.childForFieldName('type');
1569
+ const deref = declared && rustDerefTypedName(declared);
1570
+ const inner = deref?.inner || declared;
1571
+ const type = rustSliceTypeOf(inner) || rustRawPointerTypeOf(inner);
1572
+ const info = type ? Object.freeze({
1573
+ type,
1574
+ ...(deref && { derefVia: Object.freeze([...deref.via]),
1575
+ derefOuterRef: deref.outerRef }),
1576
+ }) : null;
1577
+ primitiveTypeInfoCache.set(text, info);
1578
+ return info;
1579
+ }
1580
+
1520
1581
  /**
1521
1582
  * Standard-library wrappers whose `Deref::Target` is their (first) type
1522
1583
  * argument (fix #401): smart pointers, `ManuallyDrop`, lazy cells, `RefCell`
@@ -1671,11 +1732,12 @@ function extractStructFields(structNode, codeOrLines) {
1671
1732
  // `self.0.method()` exactly.
1672
1733
  if (field.type === 'visibility_modifier' ||
1673
1734
  field.type === 'attribute_item') continue;
1674
- const { startLine, endLine } = nodeToLocation(field, code);
1735
+ const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
1675
1736
  fields.push({
1676
1737
  name: String(position++),
1677
1738
  startLine,
1678
1739
  endLine,
1740
+ sourceSpan,
1679
1741
  memberType: 'field',
1680
1742
  fieldType: field.text,
1681
1743
  });
@@ -1686,7 +1748,7 @@ function extractStructFields(structNode, codeOrLines) {
1686
1748
  for (let i = 0; i < bodyNode.namedChildCount; i++) {
1687
1749
  const field = bodyNode.namedChild(i);
1688
1750
  if (field.type === 'field_declaration') {
1689
- const { startLine, endLine } = nodeToLocation(field, code);
1751
+ const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
1690
1752
  const nameNode = field.childForFieldName('name');
1691
1753
  const typeNode = field.childForFieldName('type');
1692
1754
 
@@ -1700,6 +1762,7 @@ function extractStructFields(structNode, codeOrLines) {
1700
1762
  name: nameNode.text,
1701
1763
  startLine,
1702
1764
  endLine,
1765
+ sourceSpan,
1703
1766
  memberType: 'field',
1704
1767
  ...(visibility && { modifiers: [visibility] }),
1705
1768
  ...(typeNode && { fieldType: typeNode.text })
@@ -1853,7 +1916,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
1853
1916
  if (child.type === 'enum_variant') {
1854
1917
  const nameNode = child.childForFieldName('name');
1855
1918
  if (nameNode) {
1856
- const { startLine, endLine } = nodeToLocation(child, code);
1919
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1857
1920
  // Check for tuple/struct variant data
1858
1921
  let params = undefined;
1859
1922
  // A unit variant names its value, a tuple variant its
@@ -1870,6 +1933,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
1870
1933
  name: nameNode.text,
1871
1934
  startLine,
1872
1935
  endLine,
1936
+ sourceSpan,
1873
1937
  memberType: 'variant',
1874
1938
  ...(params !== undefined && { params }),
1875
1939
  ...(valueShape && { valueShape }),
@@ -1894,7 +1958,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
1894
1958
  if (child.type === 'function_item' || child.type === 'function_signature_item') {
1895
1959
  const nameNode = child.childForFieldName('name');
1896
1960
  if (nameNode) {
1897
- const { startLine, endLine } = nodeToLocation(child, code);
1961
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1898
1962
  const paramsNode = child.childForFieldName('parameters');
1899
1963
  const returnType = extractReturnType(child);
1900
1964
  const iteratorItemType = extractRustIteratorItemType(child);
@@ -1911,6 +1975,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
1911
1975
  name: nameNode.text,
1912
1976
  startLine,
1913
1977
  endLine,
1978
+ sourceSpan,
1914
1979
  memberType: 'method',
1915
1980
  isMethod: true,
1916
1981
  ...(qualifiers.async && { isAsync: true }),
@@ -2050,7 +2115,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
2050
2115
  const paramsNode = child.childForFieldName('parameters');
2051
2116
 
2052
2117
  if (nameNode) {
2053
- const { startLine, endLine } = nodeToLocation(child, code);
2118
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
2054
2119
  const text = child.text;
2055
2120
  const returnType = extractReturnType(child);
2056
2121
  const iteratorItemType = extractRustIteratorItemType(child);
@@ -2100,6 +2165,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
2100
2165
  ...(callbackParamTypes && { callbackParamTypes }),
2101
2166
  startLine,
2102
2167
  endLine,
2168
+ sourceSpan,
2103
2169
  memberType: 'method',
2104
2170
  isAsync: qualifiers.async,
2105
2171
  isMethod: hasSelf, // Only true methods (with self) — associated functions are false
@@ -2179,10 +2245,51 @@ function parse(code, parser) {
2179
2245
  const names = rustAsyncClosureNames(tree);
2180
2246
  return names.length > 0 ? { asyncClosureNames: names } : {};
2181
2247
  })(),
2248
+ ...(() => {
2249
+ const itemMacros = rustModuleItemMacros(tree.rootNode);
2250
+ return itemMacros.length > 0 ? { itemMacros } : {};
2251
+ })(),
2182
2252
  imports: [], exports: [],
2183
2253
  };
2184
2254
  }
2185
2255
 
2256
+ /**
2257
+ * Macro invocations in item position of a module (fix #422): `name!(..)`
2258
+ * directly in a file or an inline module body, with the identifier words
2259
+ * of their arguments. Such an invocation may declare items named by those
2260
+ * words, so a lookup of one of them in that module cannot rule out a name
2261
+ * the expansion binds.
2262
+ */
2263
+ function rustModuleItemMacros(root) {
2264
+ const out = [];
2265
+ const visit = container => {
2266
+ for (let i = 0; i < container.namedChildCount; i++) {
2267
+ let item = container.namedChild(i);
2268
+ if (item.type === 'expression_statement' && item.namedChildCount === 1) item = item.namedChild(0);
2269
+ if (item.type === 'macro_invocation') {
2270
+ const macro = item.childForFieldName('macro');
2271
+ if (!macro) continue;
2272
+ const name = macro.type === 'scoped_identifier'
2273
+ ? macro.childForFieldName('name')?.text : macro.text;
2274
+ if (!name) continue;
2275
+ const words = new Set();
2276
+ for (const child of item.namedChildren) {
2277
+ if (child.type !== 'token_tree') continue;
2278
+ for (const token of child.descendantsOfType('identifier')) words.add(token.text);
2279
+ }
2280
+ out.push({ name, line: item.startPosition.row + 1, words: [...words].sort() });
2281
+ continue;
2282
+ }
2283
+ if (item.type === 'mod_item') {
2284
+ const body = item.childForFieldName('body');
2285
+ if (body) visit(body);
2286
+ }
2287
+ }
2288
+ };
2289
+ visit(root);
2290
+ return out;
2291
+ }
2292
+
2186
2293
  const DECLARATION_NODE_TYPES = [
2187
2294
  'function_item', 'foreign_mod_item', 'struct_item', 'union_item', 'enum_item', 'trait_item', 'impl_item',
2188
2295
  'mod_item', 'macro_definition', 'type_item', 'const_item', 'static_item',
@@ -2193,13 +2300,19 @@ const DECLARATION_NODE_TYPES = [
2193
2300
  * extracts them (fix #374: the declarations a macro expansion generates,
2194
2301
  * without walking the rest of the expanded file).
2195
2302
  */
2196
- function parseDeclarationsIn(code, roots) {
2303
+ function parseDeclarationsIn(code, roots, { descendants = null } = {}) {
2197
2304
  const lines = code.split('\n');
2198
2305
  const functions = [], classes = [], stateObjects = [];
2199
2306
  const processedFn = new Set(), processedCls = new Set();
2200
- for (const root of roots) {
2307
+ // `descendants(root, i)`: the root's descendant nodes the caller already
2308
+ // listed (a superset of DECLARATION_NODE_TYPES, in document order).
2309
+ for (let i = 0; i < roots.length; i++) {
2310
+ const root = roots[i];
2201
2311
  const nodes = DECLARATION_NODE_TYPES.includes(root.type) ? [root] : [];
2202
- for (const node of nodes.concat(root.descendantsOfType(DECLARATION_NODE_TYPES))) {
2312
+ const below = descendants
2313
+ ? descendants(root, i).filter(node => DECLARATION_NODE_TYPES.includes(node.type))
2314
+ : root.descendantsOfType(DECLARATION_NODE_TYPES);
2315
+ for (const node of nodes.concat(below)) {
2203
2316
  _processFunction(node, functions, processedFn, lines, code);
2204
2317
  _processClass(node, classes, processedCls, lines, code);
2205
2318
  _processState(node, stateObjects, lines);
@@ -2686,7 +2799,10 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
2686
2799
  // associated-call shape `Type::default()`. It is still an AST token,
2687
2800
  // so admitting it here preserves the AST-first rule while recovering
2688
2801
  // calls nested inside macro token trees (assert_eq!, matches!, ...).
2689
- if (tok.type !== 'identifier' && tok.type !== 'default') continue;
2802
+ // Primitive type words can also name value-namespace functions and
2803
+ // methods (`width.char(...)`). The following call-shape check still
2804
+ // requires argument tokens, so type annotations are not calls.
2805
+ if (tok.type !== 'identifier' && tok.type !== 'default' && tok.type !== 'primitive_type') continue;
2690
2806
  const next = children[i + 1];
2691
2807
  const prev = children[i - 1];
2692
2808
  // $metavariable(...) — a macro fragment, not a named call
@@ -2771,6 +2887,8 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
2771
2887
  isPathCall: true,
2772
2888
  receiver: segments.length > 0 ? segments.join('::') : undefined,
2773
2889
  ...(tokenArgTypes && { argTypes: tokenArgTypes }),
2890
+ ...(segments.length > 0 && rustReceiverScopeBindingField(tok, segments.join('::'))),
2891
+ ...(segments.length > 0 && rustTokenTreeMayBind(tok, segments[0]) && { macroScopeUnknown: true }),
2774
2892
  ...macroFields,
2775
2893
  enclosingFunction
2776
2894
  };
@@ -2860,6 +2978,9 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
2860
2978
  callEnd: callArgs.endIndex,
2861
2979
  isMethod: false,
2862
2980
  ...macroFields,
2981
+ ...(rustBareNameShadowedByLocal(tok, tok.text) && { localShadow: true }),
2982
+ ...rustScopeBindingField(tok, tok.text),
2983
+ ...(rustTokenTreeMayBind(tok, tok.text) && { macroScopeUnknown: true }),
2863
2984
  enclosingFunction
2864
2985
  };
2865
2986
  calls.push(record);
@@ -3369,6 +3490,41 @@ function rustMacroCallIdentity(macroNode) {
3369
3490
  * value then has the fn's declared return type, which lets a trait path call
3370
3491
  * (`Trait::assoc(..)`) infer its Self from the declaration.
3371
3492
  */
3493
+ /**
3494
+ * Where a call's value goes with a written type (fix #422): a struct
3495
+ * expression's field (`Self { inner: Default::default() }` ->
3496
+ * `{ fieldInit: { struct: 'Self', field: 'inner' } }`) or an annotated `let`
3497
+ * (`let x: T = T::new()` -> `{ expectedType: 'T' }`). The type a
3498
+ * Self-returning trait member is selected by.
3499
+ */
3500
+ function rustExpectedTypeFields(node) {
3501
+ const parent = node.parent;
3502
+ if (parent?.type === 'field_initializer' && sameNode(parent.childForFieldName('value'), node)) {
3503
+ const field = parent.childForFieldName('field');
3504
+ const expression = parent.parent?.type === 'field_initializer_list' ? parent.parent.parent : null;
3505
+ const nameNode = expression?.type === 'struct_expression' ? expression.childForFieldName('name') : null;
3506
+ const struct = rustPlainPathText(nameNode?.type === 'generic_type_with_turbofish'
3507
+ ? nameNode.childForFieldName('type') : nameNode) || (nameNode?.type === 'type_identifier' ? nameNode.text : null);
3508
+ if (field?.type === 'field_identifier' && struct) return { fieldInit: { struct, field: field.text } };
3509
+ return null;
3510
+ }
3511
+ if (parent?.type === 'let_declaration' && sameNode(parent.childForFieldName('value'), node)) {
3512
+ const type = parent.childForFieldName('type');
3513
+ if (type) return { expectedType: type.text.replace(/\s+/g, '') };
3514
+ }
3515
+ // An argument of a plain or path function call (`run(Default::default())`,
3516
+ // fix #434): the callee's declared parameter type at that position.
3517
+ if (parent?.type === 'arguments' && parent.parent?.type === 'call_expression') {
3518
+ const callee = parent.parent.childForFieldName('function');
3519
+ const path = callee?.type === 'identifier' ? callee.text : rustPlainPathText(callee);
3520
+ if (!path) return null;
3521
+ const index = parent.namedChildren.filter(child => !child.type.endsWith('comment'))
3522
+ .findIndex(child => sameNode(child, node));
3523
+ if (index >= 0) return { argumentOf: { callee: path, index } };
3524
+ }
3525
+ return null;
3526
+ }
3527
+
3372
3528
  function rustFunctionReturnPosition(node) {
3373
3529
  const parent = node.parent;
3374
3530
  if (!parent) return false;
@@ -3426,6 +3582,283 @@ function rustPatternBindsName(pattern, name) {
3426
3582
  return false;
3427
3583
  }
3428
3584
 
3585
+ /**
3586
+ * Join two Rust use-path pieces (`a::b` + `c` -> `a::b::c`).
3587
+ */
3588
+ function rustJoinUsePath(prefix, suffix) {
3589
+ // Paths are compared as written without whitespace (`a:: b` is `a::b`).
3590
+ const left = String(prefix || '').replace(/\s+/g, '').replace(/::$/, '');
3591
+ const right = String(suffix || '').replace(/\s+/g, '').replace(/^::/, '');
3592
+ return left && right ? `${left}::${right}` : left || right;
3593
+ }
3594
+
3595
+ /**
3596
+ * Every binding a `use` tree makes (fix #422): `visit({ path, local,
3597
+ * original })` for a named leaf (the path ends with the item's ORIGINAL
3598
+ * name; `local` is the name the scope binds, an alias for `as`), and
3599
+ * `visit({ path, glob: true })` for `path::*`. `use a::b::{self}` binds
3600
+ * `b` to the path `a::b`.
3601
+ */
3602
+ function walkRustUseTree(node, prefix, visit) {
3603
+ if (!node || node.type === 'visibility_modifier') return;
3604
+ if (node.type === 'scoped_use_list') {
3605
+ const pathNode = node.childForFieldName('path');
3606
+ const listNode = node.childForFieldName('list');
3607
+ if (listNode) walkRustUseTree(listNode, rustJoinUsePath(prefix, pathNode?.text), visit);
3608
+ return;
3609
+ }
3610
+ if (node.type === 'use_list') {
3611
+ for (let i = 0; i < node.namedChildCount; i++) walkRustUseTree(node.namedChild(i), prefix, visit);
3612
+ return;
3613
+ }
3614
+ if (node.type === 'use_as_clause') {
3615
+ const pathNode = node.namedChild(0);
3616
+ const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
3617
+ if (pathNode && aliasNode) {
3618
+ const original = String(pathNode.text).replace(/\s+/g, '').split('::').pop();
3619
+ visit({ path: rustJoinUsePath(prefix, pathNode.text), local: aliasNode.text, original, node });
3620
+ }
3621
+ return;
3622
+ }
3623
+ if (node.type === 'use_wildcard') {
3624
+ visit({ path: rustJoinUsePath(prefix, node.namedChild(0)?.text), glob: true, node });
3625
+ return;
3626
+ }
3627
+ if (node.type === 'identifier' || node.type === 'scoped_identifier' ||
3628
+ node.type === 'crate' || node.type === 'self' || node.type === 'super') {
3629
+ if (node.text === 'self' && prefix) {
3630
+ const local = prefix.split('::').pop();
3631
+ visit({ path: prefix, local, original: local, node });
3632
+ return;
3633
+ }
3634
+ const local = node.text.replace(/\s+/g, '').split('::').pop();
3635
+ visit({ path: rustJoinUsePath(prefix, node.text), local, original: local, node });
3636
+ }
3637
+ }
3638
+
3639
+ // Item kinds a block declares in the value namespace, and in the type
3640
+ // namespace (fix #422). A tuple or unit struct names a value too.
3641
+ const RUST_BLOCK_VALUE_ITEMS = new Set(['function_item', 'const_item', 'static_item']);
3642
+ const RUST_BLOCK_TYPE_ITEMS = new Set(['struct_item', 'enum_item', 'union_item', 'trait_item',
3643
+ 'type_item', 'mod_item']);
3644
+
3645
+ /**
3646
+ * The names a block's own items and `use` declarations bind (fix #422):
3647
+ * `{ value: Map, type: Map, globs }`, each map name -> `{ kind: 'use', path,
3648
+ * line }` or `{ kind: 'item', line }`. Rust resolves a name through the
3649
+ * innermost block that binds it, its explicit names before its globs.
3650
+ */
3651
+ function rustBlockBindingTable(block) {
3652
+ let value = null;
3653
+ let type = null;
3654
+ let globs = null;
3655
+ const bind = (map, name, binding) => {
3656
+ if (!name) return map;
3657
+ const out = map || new Map();
3658
+ if (!out.has(name)) out.set(name, binding);
3659
+ return out;
3660
+ };
3661
+ for (let i = 0; i < block.namedChildCount; i++) {
3662
+ const item = block.namedChild(i);
3663
+ if (item.type === 'use_declaration') {
3664
+ const line = item.startPosition.row + 1;
3665
+ for (let j = 0; j < item.namedChildCount; j++) {
3666
+ walkRustUseTree(item.namedChild(j), '', leaf => {
3667
+ if (leaf.glob) {
3668
+ if (leaf.path) (globs || (globs = [])).push(leaf.path);
3669
+ return;
3670
+ }
3671
+ const binding = { kind: 'use', path: leaf.path, line };
3672
+ value = bind(value, leaf.local, binding);
3673
+ type = bind(type, leaf.local, binding);
3674
+ });
3675
+ }
3676
+ continue;
3677
+ }
3678
+ if (item.type === 'foreign_mod_item') {
3679
+ const body = item.namedChildren.find(child => child.type === 'declaration_list');
3680
+ for (const decl of body?.namedChildren || []) {
3681
+ if (decl.type !== 'function_signature_item' && decl.type !== 'static_item') continue;
3682
+ const nameNode = decl.childForFieldName('name');
3683
+ value = bind(value, nameNode?.text, { kind: 'item', line: decl.startPosition.row + 1 });
3684
+ }
3685
+ continue;
3686
+ }
3687
+ const nameNode = (RUST_BLOCK_VALUE_ITEMS.has(item.type) || RUST_BLOCK_TYPE_ITEMS.has(item.type))
3688
+ ? item.childForFieldName('name') : null;
3689
+ if (!nameNode) continue;
3690
+ const binding = { kind: 'item', line: item.startPosition.row + 1 };
3691
+ if (RUST_BLOCK_VALUE_ITEMS.has(item.type)) {
3692
+ value = bind(value, nameNode.text, binding);
3693
+ continue;
3694
+ }
3695
+ type = bind(type, nameNode.text, binding);
3696
+ if (item.type === 'struct_item' && rustStructValueShape(item)) value = bind(value, nameNode.text, binding);
3697
+ }
3698
+ return value || type || globs ? { value, type, globs } : EMPTY_BLOCK_TABLE;
3699
+ }
3700
+
3701
+ const EMPTY_BLOCK_TABLE = Object.freeze({ value: null, type: null, globs: null });
3702
+
3703
+ /**
3704
+ * What the enclosing blocks of `node` bind `name` to in namespace `ns`
3705
+ * ('value' or 'type'), fix #422: the innermost block naming it explicitly
3706
+ * (`{ kind, path?, line }`), with `innerGlobs` when a nearer block has glob
3707
+ * imports that may provide it, or `{ kind: 'globs', paths }` when only
3708
+ * blocks' globs may. null when no block between the node and its module
3709
+ * binds the name (the module's own scope decides). Items of outer blocks
3710
+ * are visible inside nested fns and impls; an inline module starts a new
3711
+ * scope.
3712
+ */
3713
+ const rustBlockTablesByTree = new WeakMap();
3714
+
3715
+ /** The memo of block binding tables for the tree holding `node`. */
3716
+ function rustBlockTables(node) {
3717
+ const tree = node?.tree;
3718
+ if (!tree) return new Map();
3719
+ let tables = rustBlockTablesByTree.get(tree);
3720
+ if (!tables) rustBlockTablesByTree.set(tree, tables = new Map());
3721
+ return tables;
3722
+ }
3723
+
3724
+ /**
3725
+ * The receiver a bracketed path head spells (fix #422): `<T as Trait>` ->
3726
+ * `{ receiver: 'Trait', selfType: 'T' }`, `<T>` -> `{ receiver: 'T' }`;
3727
+ * null for any other head.
3728
+ */
3729
+ function rustQualifiedPathHead(pathNode) {
3730
+ if (pathNode?.type !== 'bracketed_type') return null;
3731
+ const inner = pathNode.namedChild(0);
3732
+ const plainPath = n => n && (n.type === 'type_identifier' || n.type === 'scoped_type_identifier' ||
3733
+ n.type === 'identifier' || n.type === 'scoped_identifier') ? n.text.replace(/\s+/g, '') : null;
3734
+ if (inner?.type === 'qualified_type') {
3735
+ const trait = plainPath(inner.childForFieldName('alias'));
3736
+ if (!trait) return null;
3737
+ const selfType = plainPath(inner.childForFieldName('type'));
3738
+ return { receiver: trait, ...(selfType && { selfType }) };
3739
+ }
3740
+ const typeName = plainPath(inner);
3741
+ return typeName ? { receiver: typeName } : null;
3742
+ }
3743
+
3744
+ const rustTokenTreeScopeFacts = new WeakMap();
3745
+ const RUST_SCOPING_KEYWORD = /\b(?:use|fn|mod|impl)\b/;
3746
+
3747
+ /**
3748
+ * Whether the macro invocation holding a token may bind `name` itself
3749
+ * (fix #422): its tokens hold a `use`, `fn`, `mod` or `impl` keyword (items
3750
+ * and imports scoped by what the macro expands to), or spell `name` other
3751
+ * than as a callee (a `let`, closure parameter or pattern). The scopes
3752
+ * around the invocation then do not decide what the call names. The tokens
3753
+ * are read only when the invocation's text can hold such a token: every
3754
+ * keyword token spells its keyword there, and a second token spelling the
3755
+ * name spells it a second time.
3756
+ */
3757
+ function rustTokenTreeMayBind(token, name) {
3758
+ let top = null;
3759
+ for (let p = token.parent; p && p.type === 'token_tree'; p = p.parent) top = p;
3760
+ if (!top) return false;
3761
+ const tree = top.tree;
3762
+ let byTree = tree ? rustTokenTreeScopeFacts.get(tree) : null;
3763
+ if (tree && !byTree) rustTokenTreeScopeFacts.set(tree, byTree = new Map());
3764
+ let facts = byTree?.get(top.id);
3765
+ if (!facts) {
3766
+ const text = top.text;
3767
+ facts = { text, keyword: RUST_SCOPING_KEYWORD.test(text), tokens: null };
3768
+ byTree?.set(top.id, facts);
3769
+ }
3770
+ const tokens = () => facts.tokens || (facts.tokens = rustTokenTreeTokens(top));
3771
+ if (facts.keyword && tokens().scoped) return true;
3772
+ return rustWordSpelledTwice(facts.text, name) && tokens().words.has(name);
3773
+ }
3774
+
3775
+ /** The keyword tokens and the non-callee identifier words of a token tree. */
3776
+ function rustTokenTreeTokens(top) {
3777
+ const facts = { scoped: false, words: new Set() };
3778
+ const visit = node => {
3779
+ const children = node.children;
3780
+ for (let i = 0; i < children.length; i++) {
3781
+ const child = children[i];
3782
+ const type = child.type;
3783
+ if (type === 'use' || type === 'fn' || type === 'mod' || type === 'impl') facts.scoped = true;
3784
+ if (type === 'token_tree') { visit(child); continue; }
3785
+ if (type !== 'identifier') continue;
3786
+ const next = children[i + 1];
3787
+ const callee = next && ((next.type === 'token_tree' && next.child(0)?.type === '(') ||
3788
+ next.type === '!' || next.type === '::');
3789
+ if (!callee) facts.words.add(child.text);
3790
+ }
3791
+ };
3792
+ visit(top);
3793
+ return facts;
3794
+ }
3795
+
3796
+ /** ASCII identifier characters: any other character counts as a boundary, so no token is missed. */
3797
+ function rustAsciiWordChar(code) {
3798
+ return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;
3799
+ }
3800
+
3801
+ /** Whether `word` stands at least twice in `text` between non-word characters. */
3802
+ function rustWordSpelledTwice(text, word) {
3803
+ if (!word) return false;
3804
+ let count = 0;
3805
+ for (let at = text.indexOf(word); at !== -1; at = text.indexOf(word, at + 1)) {
3806
+ if (at > 0 && rustAsciiWordChar(text.charCodeAt(at - 1))) continue;
3807
+ if (rustAsciiWordChar(text.charCodeAt(at + word.length))) continue;
3808
+ if (++count >= 2) return true;
3809
+ }
3810
+ return false;
3811
+ }
3812
+
3813
+ /**
3814
+ * A plain path's text without whitespace or type arguments (`a::b::T`,
3815
+ * `Vec::<u8>` -> `Vec`), or null for any other path head.
3816
+ */
3817
+ function rustPlainPathText(pathNode) {
3818
+ if (!pathNode) return null;
3819
+ if (pathNode.type === 'generic_type') return rustPlainPathText(pathNode.childForFieldName('type'));
3820
+ if (!['identifier', 'scoped_identifier', 'type_identifier', 'scoped_type_identifier',
3821
+ 'self', 'super', 'crate'].includes(pathNode.type)) return null;
3822
+ const text = pathNode.text.replace(/\s+/g, '');
3823
+ return /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*$/.test(text) ? text : null;
3824
+ }
3825
+
3826
+ /** `scopeBinding` record field of a bare value name (fix #422), or null. */
3827
+ function rustScopeBindingField(node, name) {
3828
+ const binding = rustBlockScopeBinding(node, name, 'value', rustBlockTables(node));
3829
+ return binding ? { scopeBinding: binding } : null;
3830
+ }
3831
+
3832
+ /** `receiverScopeBinding` of a path's first segment in the type namespace (fix #422). */
3833
+ function rustReceiverScopeBindingField(node, receiver) {
3834
+ const head = String(receiver || '').split('::')[0];
3835
+ if (!head || head === 'crate' || head === 'self' || head === 'super' || head === 'Self' ||
3836
+ !/^[A-Za-z_][A-Za-z0-9_]*$/.test(head)) return null;
3837
+ const binding = rustBlockScopeBinding(node, head, 'type', rustBlockTables(node));
3838
+ return binding ? { receiverScopeBinding: binding } : null;
3839
+ }
3840
+
3841
+ function rustBlockScopeBinding(node, name, ns, tables) {
3842
+ let globs = null;
3843
+ for (let p = node.parent; p; p = p.parent) {
3844
+ if (p.type === 'mod_item' || p.type === 'source_file') break;
3845
+ if (p.type !== 'block') continue;
3846
+ let table = tables.get(p.id);
3847
+ if (!table) {
3848
+ table = rustBlockBindingTable(p);
3849
+ tables.set(p.id, table);
3850
+ }
3851
+ const hit = table[ns]?.get(name);
3852
+ if (hit) {
3853
+ const binding = { ...hit };
3854
+ if (globs) binding.innerGlobs = globs;
3855
+ return binding;
3856
+ }
3857
+ if (table.globs) globs = (globs || []).concat(table.globs);
3858
+ }
3859
+ return globs ? { kind: 'globs', paths: globs } : null;
3860
+ }
3861
+
3429
3862
  function rustBareNameShadowedByLocal(callNode, name) {
3430
3863
  let child = callNode;
3431
3864
  for (let p = callNode.parent; p; child = p, p = p.parent) {
@@ -3737,8 +4170,17 @@ function rustFieldAliasOf(identNode) {
3737
4170
  return null;
3738
4171
  }
3739
4172
 
3740
- function findCallsInCode(code, parser) {
3741
- const tree = parseTree(parser, code);
4173
+ /**
4174
+ * Call records of Rust code. `options` (fix #405, macro-expansion calls):
4175
+ * `tree` - the already parsed tree of `code` (an expanded file parsed with
4176
+ * included ranges), whose item-recovery facts are `itemRecovery` (null when
4177
+ * none); `skip(node)` - nodes whose subtrees are not read (other units of an
4178
+ * expanded file: every scope the extractor keeps is a function's own, so a
4179
+ * unit's records do not depend on its sibling units).
4180
+ */
4181
+ function findCallsInCode(code, parser, options = {}) {
4182
+ const tree = options.tree || parseTree(parser, code);
4183
+ const skip = typeof options.skip === 'function' ? options.skip : null;
3742
4184
  const calls = [];
3743
4185
  // Words of every value-binding pattern seen so far in document order
3744
4186
  // (fix #369). A binding shadows a bare call only if it precedes the call,
@@ -4378,7 +4820,7 @@ function findCallsInCode(code, parser) {
4378
4820
  // Item bodies of item-position macros (`quickcheck! { fn p(..) { .. } }`,
4379
4821
  // fix #401) are parsed as items: their calls are read from that parse,
4380
4822
  // with their functions' scopes and local types, instead of as tokens.
4381
- const itemRecovery = rustMacroItemCallRecovery(code, parser);
4823
+ const itemRecovery = options.tree ? (options.itemRecovery || null) : rustMacroItemCallRecovery(code, parser);
4382
4824
  const recoveredTokenTrees = new Set((itemRecovery?.ranges || []).map(([start]) => start - 1));
4383
4825
 
4384
4826
  const visitNode = (node) => {
@@ -4501,6 +4943,7 @@ function findCallsInCode(code, parser) {
4501
4943
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4502
4944
  ...(mayBeLocallyBound(funcNode.text) &&
4503
4945
  rustBareNameShadowedByLocal(node, funcNode.text) && { localShadow: true }),
4946
+ ...rustScopeBindingField(node, funcNode.text),
4504
4947
  });
4505
4948
  } else if (funcNode.type === 'field_expression') {
4506
4949
  // Method call: obj.method()
@@ -4847,8 +5290,13 @@ function findCallsInCode(code, parser) {
4847
5290
  // Turbofish receivers (`Vec::<String>::new`) carry the type
4848
5291
  // arguments as their own `::`-split segments — drop them so
4849
5292
  // the receiver is the plain type/module path (fix #222).
4850
- const recvSegments = segments.slice(0, -1)
5293
+ let recvSegments = segments.slice(0, -1)
4851
5294
  .filter(s => !s.startsWith('<') && !s.endsWith('>'));
5295
+ // A qualified path (fix #422): `<T as Trait>::m(x)` names
5296
+ // the trait's member with Self = T; `<T>::m()` is `T::m()`.
5297
+ const qualified = rustQualifiedPathHead(funcNode.childForFieldName('path'));
5298
+ if (qualified) recvSegments = qualified.receiver.split('::');
5299
+ const receiverText = recvSegments.length > 0 ? recvSegments.join('::') : undefined;
4852
5300
  calls.push({
4853
5301
  name: name,
4854
5302
  line: node.startPosition.row + 1,
@@ -4858,7 +5306,9 @@ function findCallsInCode(code, parser) {
4858
5306
  ...(consumingMethod && { consumingMethod }),
4859
5307
  isMethod: segments.length > 1,
4860
5308
  isPathCall: true, // Distinguishes Type::func()/module::func() from obj.method()
4861
- receiver: recvSegments.length > 0 ? recvSegments.join('::') : undefined,
5309
+ receiver: receiverText,
5310
+ ...(qualified?.selfType && { receiverQualifiedSelf: qualified.selfType }),
5311
+ ...(receiverText && rustReceiverScopeBindingField(node, receiverText)),
4862
5312
  argCount,
4863
5313
  ...(segments.length > 1 && (() => {
4864
5314
  const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
@@ -4872,6 +5322,7 @@ function findCallsInCode(code, parser) {
4872
5322
  enclosingFunction,
4873
5323
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4874
5324
  ...(rustFunctionReturnPosition(node) && { returnPosition: true }),
5325
+ ...(receiverText && rustExpectedTypeFields(node)),
4875
5326
  });
4876
5327
  }
4877
5328
  return true;
@@ -5057,6 +5508,32 @@ function findCallsInCode(code, parser) {
5057
5508
  }
5058
5509
  }
5059
5510
 
5511
+ // Path values passed as arguments (fix #422): `case(&t,
5512
+ // T::unknown_bits)`, `.map(Self::parse)`. A method value is
5513
+ // path-only in Rust; the path's qualifier decides what it names.
5514
+ if (node.type === 'scoped_identifier' && node.parent?.type === 'arguments') {
5515
+ const nameNode = node.childForFieldName('name');
5516
+ const pathNode = node.childForFieldName('path');
5517
+ const qualified = rustQualifiedPathHead(pathNode);
5518
+ const receiver = qualified ? qualified.receiver : rustPlainPathText(pathNode);
5519
+ if (nameNode?.type === 'identifier' && receiver) {
5520
+ calls.push({
5521
+ name: nameNode.text,
5522
+ line: nameNode.startPosition.row + 1,
5523
+ callStart: node.startIndex,
5524
+ callEnd: node.endIndex,
5525
+ isMethod: true,
5526
+ isPathCall: true,
5527
+ receiver,
5528
+ ...(qualified?.selfType && { receiverQualifiedSelf: qualified.selfType }),
5529
+ ...rustReceiverScopeBindingField(node, receiver),
5530
+ isFunctionReference: true,
5531
+ isPotentialCallback: true,
5532
+ enclosingFunction: getCurrentEnclosingFunction(),
5533
+ });
5534
+ }
5535
+ }
5536
+
5060
5537
  // Track local variable types from let declarations
5061
5538
  // Pattern 1: let s = Server { ... } (struct expression)
5062
5539
  // Pattern 2: let s = Server::new() / ::from() / ::default() (scoped constructor)
@@ -5227,17 +5704,21 @@ function findCallsInCode(code, parser) {
5227
5704
  }
5228
5705
  }
5229
5706
  };
5230
- traverseTree(tree.rootNode, visitNode, { onLeave: leaveNode });
5707
+ traverseTree(tree.rootNode, skip ? node => (skip(node) ? false : visitNode(node)) : visitNode,
5708
+ { onLeave: leaveNode });
5231
5709
  if (itemRecovery) {
5232
5710
  const ranges = itemRecovery.ranges;
5233
5711
  const inside = node => ranges.some(([start, end]) => node.startIndex >= start && node.endIndex <= end);
5234
5712
  const overlaps = node => ranges.some(([start, end]) => node.startIndex < end && node.endIndex > start);
5235
5713
  traverseTree(itemRecovery.tree.rootNode, node => {
5714
+ if (skip && skip(node)) return false;
5236
5715
  if (inside(node)) return visitNode(node);
5237
5716
  return overlaps(node);
5238
5717
  }, { onLeave: node => { if (inside(node)) leaveNode(node); } });
5239
5718
  }
5240
5719
 
5720
+ // A tree handed in is an expanded file: no unexpanded item bodies.
5721
+ if (options.tree) return calls;
5241
5722
  const declaration = declarationTrees(code, parser);
5242
5723
  if (!declaration.macroItemRecovery) return calls;
5243
5724
  const functions = findFunctions(code, parser);
@@ -5273,12 +5754,7 @@ function findImportsInCode(code, parser) {
5273
5754
  const tree = parseTree(parser, code);
5274
5755
  const imports = [];
5275
5756
 
5276
- const joinUsePath = (prefix, suffix) => {
5277
- const left = String(prefix || '').replace(/::$/, '');
5278
- const right = String(suffix || '').replace(/^::/, '');
5279
- return left && right ? `${left}::${right}` : left || right;
5280
- };
5281
- const addLeaf = (module, localName, type = 'use', dynamic = false, line, rename = null) => {
5757
+ const addLeaf = (module, localName, type = 'use', dynamic = false, line, rename = null, scope = null) => {
5282
5758
  if (!module || !localName) return;
5283
5759
  imports.push({
5284
5760
  module,
@@ -5287,63 +5763,33 @@ function findImportsInCode(code, parser) {
5287
5763
  dynamic,
5288
5764
  line,
5289
5765
  ...(rename && { renames: [rename] }),
5766
+ // A `use` in a block binds its names in that block only (fix
5767
+ // #422): its lines bound where the bindings are visible.
5768
+ ...scope,
5290
5769
  });
5291
5770
  };
5292
- const collectUseTree = (node, prefix, line) => {
5293
- if (!node || node.type === 'visibility_modifier') return;
5294
- if (node.type === 'scoped_use_list') {
5295
- const pathNode = node.childForFieldName('path');
5296
- const listNode = node.childForFieldName('list');
5297
- const nextPrefix = joinUsePath(prefix, pathNode?.text);
5298
- if (listNode) collectUseTree(listNode, nextPrefix, line);
5771
+ const collectUseTree = (node, line, scope) => walkRustUseTree(node, '', leaf => {
5772
+ if (leaf.glob) {
5773
+ addLeaf(leaf.path, '*', 'use-glob', true, line, null, scope);
5299
5774
  return;
5300
5775
  }
5301
- if (node.type === 'use_list') {
5302
- for (let i = 0; i < node.namedChildCount; i++) {
5303
- collectUseTree(node.namedChild(i), prefix, line);
5304
- }
5305
- return;
5776
+ // fix #353: `use alpha::widget as renamed; renamed()` — the alias
5777
+ // pairing feeds findCallers' import-rename surface (calledAs),
5778
+ // exactly like Python/JS `import x as y`. fix #357: `renames` pairs
5779
+ // the alias with ITS record so createImportBindings emits {name:
5780
+ // original, alias: local} (the #348 Python shape); two renames of
5781
+ // one source name from different modules each pair with their own
5782
+ // module instead of both bindings matching both calls. `names` keeps
5783
+ // the local spelling (parser contract, imports command).
5784
+ const renamed = leaf.node.type === 'use_as_clause' && leaf.original &&
5785
+ leaf.original !== leaf.local && leaf.original !== 'self';
5786
+ addLeaf(leaf.path, leaf.local, 'use', false, line,
5787
+ renamed ? { original: leaf.original, local: leaf.local } : null, scope);
5788
+ if (renamed) {
5789
+ if (!imports.aliases) imports.aliases = [];
5790
+ imports.aliases.push({ original: leaf.original, local: leaf.local });
5306
5791
  }
5307
- if (node.type === 'use_as_clause') {
5308
- const pathNode = node.namedChild(0);
5309
- const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
5310
- if (pathNode && aliasNode) {
5311
- // fix #353: `use alpha::widget as renamed; renamed()` — the
5312
- // alias pairing feeds findCallers' import-rename surface
5313
- // (calledAs), exactly like Python/JS `import x as y`.
5314
- // fix #357: `renames` pairs the alias with ITS record so
5315
- // createImportBindings emits {name: original, alias: local}
5316
- // (the #348 Python shape); two renames of one source name
5317
- // from different modules each pair with their own module
5318
- // instead of both bindings matching both calls. `names`
5319
- // keeps the local spelling (parser contract, imports command).
5320
- const original = String(pathNode.text).split('::').pop();
5321
- const renamed = original && original !== aliasNode.text && original !== 'self';
5322
- addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
5323
- 'use', false, line, renamed ? { original, local: aliasNode.text } : null);
5324
- if (renamed) {
5325
- if (!imports.aliases) imports.aliases = [];
5326
- imports.aliases.push({ original, local: aliasNode.text });
5327
- }
5328
- }
5329
- return;
5330
- }
5331
- if (node.type === 'use_wildcard') {
5332
- const pathNode = node.namedChild(0);
5333
- addLeaf(joinUsePath(prefix, pathNode?.text), '*',
5334
- 'use-glob', true, line);
5335
- return;
5336
- }
5337
- if (node.type === 'identifier' || node.type === 'scoped_identifier' ||
5338
- node.type === 'crate' || node.type === 'self' || node.type === 'super') {
5339
- if (node.text === 'self' && prefix) {
5340
- addLeaf(prefix, prefix.split('::').pop(), 'use', false, line);
5341
- return;
5342
- }
5343
- const module = joinUsePath(prefix, node.text);
5344
- addLeaf(module, node.text.split('::').pop(), 'use', false, line);
5345
- }
5346
- };
5792
+ });
5347
5793
 
5348
5794
  traverseTreeCached(tree.rootNode, (node) => {
5349
5795
  // use declarations
@@ -5356,9 +5802,16 @@ function findImportsInCode(code, parser) {
5356
5802
  // bindings `crate::haystack::Haystack` and
5357
5803
  // `crate::haystack::Builder`, rather than the lossy old
5358
5804
  // `{ module: "crate", name: "haystack" }` approximation.
5805
+ const scope = rustBlockItemScope(node);
5806
+ // A `use` is an item: its visibility decides which modules a
5807
+ // glob of its module reaches it from (fix #422).
5808
+ const visibility = node.namedChildren.find(child => child.type === 'visibility_modifier')
5809
+ ?.text.replace(/\s+/g, '');
5810
+ const recordScope = scope.lexicalScopeStartLine != null || visibility
5811
+ ? { ...scope, ...(visibility && { visibility }) } : null;
5359
5812
  for (let i = 0; i < node.namedChildCount; i++) {
5360
5813
  const child = node.namedChild(i);
5361
- collectUseTree(child, '', line);
5814
+ collectUseTree(child, line, recordScope);
5362
5815
  }
5363
5816
  return true;
5364
5817
  }
@@ -5973,7 +6426,13 @@ function isEntryPoint(symbol) {
5973
6426
  }
5974
6427
 
5975
6428
  module.exports = {
6429
+ // Query-time type lookup uses the same lexical ribs as call extraction.
6430
+ typePathScopeBinding: (node, written) => rustReceiverScopeBindingField(node, written)?.receiverScopeBinding || null,
6431
+ getPrimitiveTypeInfo,
6432
+ getDerefTypeInfo,
5976
6433
  primeDeclarationTrees,
6434
+ DECLARATION_NODE_TYPES,
6435
+ rustExpandedItemRecovery,
5977
6436
  parseDeclarationsIn,
5978
6437
  rustMacroInvocationContext,
5979
6438
  findFunctions,