ucn 5.5.1 → 5.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +30 -21
  6. package/cli/index.js +1 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  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 +137 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +4726 -940
  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 +217 -112
  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 +151 -19
  63. package/core/rust-modules.js +1 -1
  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 +124 -37
  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 +510 -82
  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
@@ -692,7 +692,6 @@ function extractAttributesWithArgs(node, codeOrLines) {
692
692
  }
693
693
 
694
694
  // --- Module-scope constants for state object detection ---
695
- const _STATE_PATTERN = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
696
695
 
697
696
  // --- Single-pass helpers: extracted from find* callbacks ---
698
697
 
@@ -757,7 +756,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
757
756
  const paramsNode = node.childForFieldName('parameters');
758
757
 
759
758
  if (nameNode) {
760
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
759
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
761
760
  const text = node.text;
762
761
 
763
762
  const qualifiers = rustFunctionQualifiers(node);
@@ -798,6 +797,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
798
797
  ...(callbackParamTypes && { callbackParamTypes }),
799
798
  startLine,
800
799
  endLine,
800
+ sourceSpan,
801
801
  indent,
802
802
  modifiers,
803
803
  ...(returnType && { returnType }),
@@ -826,7 +826,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
826
826
  const fName = child.childForFieldName('name');
827
827
  const fParams = child.childForFieldName('parameters');
828
828
  if (fName) {
829
- const { startLine, endLine, indent } = nodeToLocation(child, lines);
829
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
830
830
  const visibility = extractVisibility(child.text);
831
831
  const returnType = extractReturnType(child);
832
832
  const docstring = extractRustDocstring(lines, startLine);
@@ -843,6 +843,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
843
843
  ...(iteratorItemType && { iteratorItemType }),
844
844
  startLine,
845
845
  endLine,
846
+ sourceSpan,
846
847
  indent,
847
848
  modifiers,
848
849
  ...(returnType && { returnType }),
@@ -1078,7 +1079,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1078
1079
 
1079
1080
  const nameNode = node.childForFieldName('name');
1080
1081
  if (nameNode) {
1081
- const { startLine, endLine } = nodeToLocation(node, lines);
1082
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1082
1083
  const docstring = extractRustDocstring(lines, startLine);
1083
1084
  const visibility = extractVisibility(node.text);
1084
1085
  const generics = extractGenerics(node);
@@ -1092,6 +1093,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1092
1093
  name: nameNode.text,
1093
1094
  startLine,
1094
1095
  endLine,
1096
+ sourceSpan,
1095
1097
  type: node.type === 'union_item' ? 'union' : 'struct',
1096
1098
  members,
1097
1099
  modifiers,
@@ -1112,7 +1114,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1112
1114
 
1113
1115
  const nameNode = node.childForFieldName('name');
1114
1116
  if (nameNode) {
1115
- const { startLine, endLine } = nodeToLocation(node, lines);
1117
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1116
1118
  const docstring = extractRustDocstring(lines, startLine);
1117
1119
  const visibility = extractVisibility(node.text);
1118
1120
  const generics = extractGenerics(node);
@@ -1124,6 +1126,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1124
1126
  name: nameNode.text,
1125
1127
  startLine,
1126
1128
  endLine,
1129
+ sourceSpan,
1127
1130
  type: 'enum',
1128
1131
  members: extractEnumVariants(node, lines),
1129
1132
  modifiers,
@@ -1143,7 +1146,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1143
1146
 
1144
1147
  const nameNode = node.childForFieldName('name');
1145
1148
  if (nameNode) {
1146
- const { startLine, endLine } = nodeToLocation(node, lines);
1149
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1147
1150
  const docstring = extractRustDocstring(lines, startLine);
1148
1151
  const visibility = extractVisibility(node.text);
1149
1152
  const generics = extractGenerics(node);
@@ -1153,6 +1156,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1153
1156
  name: nameNode.text,
1154
1157
  startLine,
1155
1158
  endLine,
1159
+ sourceSpan,
1156
1160
  type: 'trait',
1157
1161
  members: extractTraitMembers(node, lines),
1158
1162
  modifiers: visibility ? [visibility] : [],
@@ -1171,7 +1175,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1171
1175
  if (processedRanges.has(rangeKey)) return true;
1172
1176
  processedRanges.add(rangeKey);
1173
1177
 
1174
- const { startLine, endLine } = nodeToLocation(node, lines);
1178
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1175
1179
  const implInfo = extractImplInfo(node);
1176
1180
  const docstring = extractRustDocstring(lines, startLine);
1177
1181
  const derefTarget = extractDerefTarget(node, implInfo.traitName);
@@ -1187,6 +1191,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1187
1191
  name: implInfo.name,
1188
1192
  startLine,
1189
1193
  endLine,
1194
+ sourceSpan,
1190
1195
  type: 'impl',
1191
1196
  traitName: implInfo.traitName,
1192
1197
  ...rustBlockItemScope(node),
@@ -1210,7 +1215,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1210
1215
 
1211
1216
  const nameNode = node.childForFieldName('name');
1212
1217
  if (nameNode) {
1213
- const { startLine, endLine } = nodeToLocation(node, lines);
1218
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1214
1219
  const docstring = extractRustDocstring(lines, startLine);
1215
1220
  const visibility = extractVisibility(node.text);
1216
1221
  const attributes = extractAttributes(node, lines);
@@ -1221,6 +1226,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1221
1226
  name: nameNode.text,
1222
1227
  startLine,
1223
1228
  endLine,
1229
+ sourceSpan,
1224
1230
  type: 'module',
1225
1231
  members: [],
1226
1232
  modifiers,
@@ -1238,7 +1244,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1238
1244
 
1239
1245
  const nameNode = node.childForFieldName('name');
1240
1246
  if (nameNode) {
1241
- const { startLine, endLine } = nodeToLocation(node, lines);
1247
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1242
1248
  const docstring = extractRustDocstring(lines, startLine);
1243
1249
  const inferred = _inferMacroReturn(node, nameNode.text);
1244
1250
 
@@ -1265,6 +1271,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1265
1271
  name: nameNode.text,
1266
1272
  startLine,
1267
1273
  endLine,
1274
+ sourceSpan,
1268
1275
  type: 'macro',
1269
1276
  members: [],
1270
1277
  modifiers,
@@ -1294,7 +1301,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1294
1301
 
1295
1302
  const nameNode = node.childForFieldName('name');
1296
1303
  if (nameNode) {
1297
- const { startLine, endLine } = nodeToLocation(node, lines);
1304
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1298
1305
  const docstring = extractRustDocstring(lines, startLine);
1299
1306
  const visibility = extractVisibility(node.text);
1300
1307
  // `pub type StyledString = SpannedString<Style>;` — the alias IS
@@ -1310,6 +1317,7 @@ function _processClass(node, types, processedRanges, lines, code) {
1310
1317
  name: nameNode.text,
1311
1318
  startLine,
1312
1319
  endLine,
1320
+ sourceSpan,
1313
1321
  type: 'type',
1314
1322
  members: [],
1315
1323
  modifiers: visibility ? [visibility] : [],
@@ -1377,15 +1385,19 @@ function extractDerefTarget(implNode, traitName) {
1377
1385
  * Returns true if node was matched, false otherwise
1378
1386
  */
1379
1387
  function _processState(node, objects, lines) {
1380
- // 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
1381
1391
  // (fix #392) types the item when it is a method receiver (`FLAGS.iter()`).
1382
1392
  if (node.type === 'const_item' || node.type === 'static_item') {
1383
- 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
+ }
1384
1396
  const nameNode = node.childForFieldName('name');
1385
1397
  if (nameNode) {
1386
1398
  const name = nameNode.text;
1387
- if (_STATE_PATTERN.test(name)) {
1388
- const { startLine, endLine } = nodeToLocation(node, lines);
1399
+ {
1400
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1389
1401
  // Only a plain type path (behind any references) is recorded:
1390
1402
  // a generic wrapper (`Lazy<T>`, `Mutex<T>`) owns the methods
1391
1403
  // or derefs to its argument. A `static mut` is never typed.
@@ -1399,7 +1411,7 @@ function _processState(node, objects, lines) {
1399
1411
  node.children.some(child => child.type === 'mutable_specifier');
1400
1412
  const plain = typeNode && (typeNode.type === 'type_identifier' ||
1401
1413
  typeNode.type === 'scoped_type_identifier');
1402
- objects.push({ name, startLine, endLine,
1414
+ objects.push({ name, startLine, endLine, sourceSpan, ...rustBlockItemScope(node),
1403
1415
  ...(plain && !mutable && { valueType: typeNode.text,
1404
1416
  ...(deref && { valueDerefVia: deref.via }) }) });
1405
1417
  }
@@ -1527,6 +1539,45 @@ function rustRawPointerTypeOf(typeNode) {
1527
1539
  return current.children.some(child => child.type === 'mutable_specifier') ? '*mut' : '*const';
1528
1540
  }
1529
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
+
1530
1581
  /**
1531
1582
  * Standard-library wrappers whose `Deref::Target` is their (first) type
1532
1583
  * argument (fix #401): smart pointers, `ManuallyDrop`, lazy cells, `RefCell`
@@ -1681,11 +1732,12 @@ function extractStructFields(structNode, codeOrLines) {
1681
1732
  // `self.0.method()` exactly.
1682
1733
  if (field.type === 'visibility_modifier' ||
1683
1734
  field.type === 'attribute_item') continue;
1684
- const { startLine, endLine } = nodeToLocation(field, code);
1735
+ const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
1685
1736
  fields.push({
1686
1737
  name: String(position++),
1687
1738
  startLine,
1688
1739
  endLine,
1740
+ sourceSpan,
1689
1741
  memberType: 'field',
1690
1742
  fieldType: field.text,
1691
1743
  });
@@ -1696,7 +1748,7 @@ function extractStructFields(structNode, codeOrLines) {
1696
1748
  for (let i = 0; i < bodyNode.namedChildCount; i++) {
1697
1749
  const field = bodyNode.namedChild(i);
1698
1750
  if (field.type === 'field_declaration') {
1699
- const { startLine, endLine } = nodeToLocation(field, code);
1751
+ const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
1700
1752
  const nameNode = field.childForFieldName('name');
1701
1753
  const typeNode = field.childForFieldName('type');
1702
1754
 
@@ -1710,6 +1762,7 @@ function extractStructFields(structNode, codeOrLines) {
1710
1762
  name: nameNode.text,
1711
1763
  startLine,
1712
1764
  endLine,
1765
+ sourceSpan,
1713
1766
  memberType: 'field',
1714
1767
  ...(visibility && { modifiers: [visibility] }),
1715
1768
  ...(typeNode && { fieldType: typeNode.text })
@@ -1863,7 +1916,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
1863
1916
  if (child.type === 'enum_variant') {
1864
1917
  const nameNode = child.childForFieldName('name');
1865
1918
  if (nameNode) {
1866
- const { startLine, endLine } = nodeToLocation(child, code);
1919
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1867
1920
  // Check for tuple/struct variant data
1868
1921
  let params = undefined;
1869
1922
  // A unit variant names its value, a tuple variant its
@@ -1880,6 +1933,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
1880
1933
  name: nameNode.text,
1881
1934
  startLine,
1882
1935
  endLine,
1936
+ sourceSpan,
1883
1937
  memberType: 'variant',
1884
1938
  ...(params !== undefined && { params }),
1885
1939
  ...(valueShape && { valueShape }),
@@ -1904,7 +1958,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
1904
1958
  if (child.type === 'function_item' || child.type === 'function_signature_item') {
1905
1959
  const nameNode = child.childForFieldName('name');
1906
1960
  if (nameNode) {
1907
- const { startLine, endLine } = nodeToLocation(child, code);
1961
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
1908
1962
  const paramsNode = child.childForFieldName('parameters');
1909
1963
  const returnType = extractReturnType(child);
1910
1964
  const iteratorItemType = extractRustIteratorItemType(child);
@@ -1921,6 +1975,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
1921
1975
  name: nameNode.text,
1922
1976
  startLine,
1923
1977
  endLine,
1978
+ sourceSpan,
1924
1979
  memberType: 'method',
1925
1980
  isMethod: true,
1926
1981
  ...(qualifiers.async && { isAsync: true }),
@@ -2060,7 +2115,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
2060
2115
  const paramsNode = child.childForFieldName('parameters');
2061
2116
 
2062
2117
  if (nameNode) {
2063
- const { startLine, endLine } = nodeToLocation(child, code);
2118
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
2064
2119
  const text = child.text;
2065
2120
  const returnType = extractReturnType(child);
2066
2121
  const iteratorItemType = extractRustIteratorItemType(child);
@@ -2110,6 +2165,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
2110
2165
  ...(callbackParamTypes && { callbackParamTypes }),
2111
2166
  startLine,
2112
2167
  endLine,
2168
+ sourceSpan,
2113
2169
  memberType: 'method',
2114
2170
  isAsync: qualifiers.async,
2115
2171
  isMethod: hasSelf, // Only true methods (with self) — associated functions are false
@@ -2189,10 +2245,51 @@ function parse(code, parser) {
2189
2245
  const names = rustAsyncClosureNames(tree);
2190
2246
  return names.length > 0 ? { asyncClosureNames: names } : {};
2191
2247
  })(),
2248
+ ...(() => {
2249
+ const itemMacros = rustModuleItemMacros(tree.rootNode);
2250
+ return itemMacros.length > 0 ? { itemMacros } : {};
2251
+ })(),
2192
2252
  imports: [], exports: [],
2193
2253
  };
2194
2254
  }
2195
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
+
2196
2293
  const DECLARATION_NODE_TYPES = [
2197
2294
  'function_item', 'foreign_mod_item', 'struct_item', 'union_item', 'enum_item', 'trait_item', 'impl_item',
2198
2295
  'mod_item', 'macro_definition', 'type_item', 'const_item', 'static_item',
@@ -2702,7 +2799,10 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
2702
2799
  // associated-call shape `Type::default()`. It is still an AST token,
2703
2800
  // so admitting it here preserves the AST-first rule while recovering
2704
2801
  // calls nested inside macro token trees (assert_eq!, matches!, ...).
2705
- 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;
2706
2806
  const next = children[i + 1];
2707
2807
  const prev = children[i - 1];
2708
2808
  // $metavariable(...) — a macro fragment, not a named call
@@ -2787,6 +2887,8 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
2787
2887
  isPathCall: true,
2788
2888
  receiver: segments.length > 0 ? segments.join('::') : undefined,
2789
2889
  ...(tokenArgTypes && { argTypes: tokenArgTypes }),
2890
+ ...(segments.length > 0 && rustReceiverScopeBindingField(tok, segments.join('::'))),
2891
+ ...(segments.length > 0 && rustTokenTreeMayBind(tok, segments[0]) && { macroScopeUnknown: true }),
2790
2892
  ...macroFields,
2791
2893
  enclosingFunction
2792
2894
  };
@@ -2876,6 +2978,9 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
2876
2978
  callEnd: callArgs.endIndex,
2877
2979
  isMethod: false,
2878
2980
  ...macroFields,
2981
+ ...(rustBareNameShadowedByLocal(tok, tok.text) && { localShadow: true }),
2982
+ ...rustScopeBindingField(tok, tok.text),
2983
+ ...(rustTokenTreeMayBind(tok, tok.text) && { macroScopeUnknown: true }),
2879
2984
  enclosingFunction
2880
2985
  };
2881
2986
  calls.push(record);
@@ -3385,6 +3490,41 @@ function rustMacroCallIdentity(macroNode) {
3385
3490
  * value then has the fn's declared return type, which lets a trait path call
3386
3491
  * (`Trait::assoc(..)`) infer its Self from the declaration.
3387
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
+
3388
3528
  function rustFunctionReturnPosition(node) {
3389
3529
  const parent = node.parent;
3390
3530
  if (!parent) return false;
@@ -3442,6 +3582,283 @@ function rustPatternBindsName(pattern, name) {
3442
3582
  return false;
3443
3583
  }
3444
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
+
3445
3862
  function rustBareNameShadowedByLocal(callNode, name) {
3446
3863
  let child = callNode;
3447
3864
  for (let p = callNode.parent; p; child = p, p = p.parent) {
@@ -4526,6 +4943,7 @@ function findCallsInCode(code, parser, options = {}) {
4526
4943
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4527
4944
  ...(mayBeLocallyBound(funcNode.text) &&
4528
4945
  rustBareNameShadowedByLocal(node, funcNode.text) && { localShadow: true }),
4946
+ ...rustScopeBindingField(node, funcNode.text),
4529
4947
  });
4530
4948
  } else if (funcNode.type === 'field_expression') {
4531
4949
  // Method call: obj.method()
@@ -4872,8 +5290,13 @@ function findCallsInCode(code, parser, options = {}) {
4872
5290
  // Turbofish receivers (`Vec::<String>::new`) carry the type
4873
5291
  // arguments as their own `::`-split segments — drop them so
4874
5292
  // the receiver is the plain type/module path (fix #222).
4875
- const recvSegments = segments.slice(0, -1)
5293
+ let recvSegments = segments.slice(0, -1)
4876
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;
4877
5300
  calls.push({
4878
5301
  name: name,
4879
5302
  line: node.startPosition.row + 1,
@@ -4883,7 +5306,9 @@ function findCallsInCode(code, parser, options = {}) {
4883
5306
  ...(consumingMethod && { consumingMethod }),
4884
5307
  isMethod: segments.length > 1,
4885
5308
  isPathCall: true, // Distinguishes Type::func()/module::func() from obj.method()
4886
- receiver: recvSegments.length > 0 ? recvSegments.join('::') : undefined,
5309
+ receiver: receiverText,
5310
+ ...(qualified?.selfType && { receiverQualifiedSelf: qualified.selfType }),
5311
+ ...(receiverText && rustReceiverScopeBindingField(node, receiverText)),
4887
5312
  argCount,
4888
5313
  ...(segments.length > 1 && (() => {
4889
5314
  const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
@@ -4897,6 +5322,7 @@ function findCallsInCode(code, parser, options = {}) {
4897
5322
  enclosingFunction,
4898
5323
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4899
5324
  ...(rustFunctionReturnPosition(node) && { returnPosition: true }),
5325
+ ...(receiverText && rustExpectedTypeFields(node)),
4900
5326
  });
4901
5327
  }
4902
5328
  return true;
@@ -5082,6 +5508,32 @@ function findCallsInCode(code, parser, options = {}) {
5082
5508
  }
5083
5509
  }
5084
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
+
5085
5537
  // Track local variable types from let declarations
5086
5538
  // Pattern 1: let s = Server { ... } (struct expression)
5087
5539
  // Pattern 2: let s = Server::new() / ::from() / ::default() (scoped constructor)
@@ -5302,12 +5754,7 @@ function findImportsInCode(code, parser) {
5302
5754
  const tree = parseTree(parser, code);
5303
5755
  const imports = [];
5304
5756
 
5305
- const joinUsePath = (prefix, suffix) => {
5306
- const left = String(prefix || '').replace(/::$/, '');
5307
- const right = String(suffix || '').replace(/^::/, '');
5308
- return left && right ? `${left}::${right}` : left || right;
5309
- };
5310
- 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) => {
5311
5758
  if (!module || !localName) return;
5312
5759
  imports.push({
5313
5760
  module,
@@ -5316,63 +5763,33 @@ function findImportsInCode(code, parser) {
5316
5763
  dynamic,
5317
5764
  line,
5318
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,
5319
5769
  });
5320
5770
  };
5321
- const collectUseTree = (node, prefix, line) => {
5322
- if (!node || node.type === 'visibility_modifier') return;
5323
- if (node.type === 'scoped_use_list') {
5324
- const pathNode = node.childForFieldName('path');
5325
- const listNode = node.childForFieldName('list');
5326
- const nextPrefix = joinUsePath(prefix, pathNode?.text);
5327
- if (listNode) collectUseTree(listNode, nextPrefix, line);
5328
- return;
5329
- }
5330
- if (node.type === 'use_list') {
5331
- for (let i = 0; i < node.namedChildCount; i++) {
5332
- collectUseTree(node.namedChild(i), prefix, line);
5333
- }
5334
- return;
5335
- }
5336
- if (node.type === 'use_as_clause') {
5337
- const pathNode = node.namedChild(0);
5338
- const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
5339
- if (pathNode && aliasNode) {
5340
- // fix #353: `use alpha::widget as renamed; renamed()` — the
5341
- // alias pairing feeds findCallers' import-rename surface
5342
- // (calledAs), exactly like Python/JS `import x as y`.
5343
- // fix #357: `renames` pairs the alias with ITS record so
5344
- // createImportBindings emits {name: original, alias: local}
5345
- // (the #348 Python shape); two renames of one source name
5346
- // from different modules each pair with their own module
5347
- // instead of both bindings matching both calls. `names`
5348
- // keeps the local spelling (parser contract, imports command).
5349
- const original = String(pathNode.text).split('::').pop();
5350
- const renamed = original && original !== aliasNode.text && original !== 'self';
5351
- addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
5352
- 'use', false, line, renamed ? { original, local: aliasNode.text } : null);
5353
- if (renamed) {
5354
- if (!imports.aliases) imports.aliases = [];
5355
- imports.aliases.push({ original, local: aliasNode.text });
5356
- }
5357
- }
5771
+ const collectUseTree = (node, line, scope) => walkRustUseTree(node, '', leaf => {
5772
+ if (leaf.glob) {
5773
+ addLeaf(leaf.path, '*', 'use-glob', true, line, null, scope);
5358
5774
  return;
5359
5775
  }
5360
- if (node.type === 'use_wildcard') {
5361
- const pathNode = node.namedChild(0);
5362
- addLeaf(joinUsePath(prefix, pathNode?.text), '*',
5363
- 'use-glob', true, line);
5364
- 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 });
5365
5791
  }
5366
- if (node.type === 'identifier' || node.type === 'scoped_identifier' ||
5367
- node.type === 'crate' || node.type === 'self' || node.type === 'super') {
5368
- if (node.text === 'self' && prefix) {
5369
- addLeaf(prefix, prefix.split('::').pop(), 'use', false, line);
5370
- return;
5371
- }
5372
- const module = joinUsePath(prefix, node.text);
5373
- addLeaf(module, node.text.split('::').pop(), 'use', false, line);
5374
- }
5375
- };
5792
+ });
5376
5793
 
5377
5794
  traverseTreeCached(tree.rootNode, (node) => {
5378
5795
  // use declarations
@@ -5385,9 +5802,16 @@ function findImportsInCode(code, parser) {
5385
5802
  // bindings `crate::haystack::Haystack` and
5386
5803
  // `crate::haystack::Builder`, rather than the lossy old
5387
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;
5388
5812
  for (let i = 0; i < node.namedChildCount; i++) {
5389
5813
  const child = node.namedChild(i);
5390
- collectUseTree(child, '', line);
5814
+ collectUseTree(child, line, recordScope);
5391
5815
  }
5392
5816
  return true;
5393
5817
  }
@@ -6002,6 +6426,10 @@ function isEntryPoint(symbol) {
6002
6426
  }
6003
6427
 
6004
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,
6005
6433
  primeDeclarationTrees,
6006
6434
  DECLARATION_NODE_TYPES,
6007
6435
  rustExpandedItemRecovery,