ucn 5.4.2 → 5.5.1

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 (85) hide show
  1. package/.claude/skills/ucn/SKILL.md +58 -236
  2. package/.claude/skills/ucn/references/commands.md +144 -68
  3. package/.claude/skills/ucn/references/resolution-rules.md +171 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +27 -33
  5. package/README.md +27 -16
  6. package/cli/index.js +45 -14
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +62 -11
  9. package/core/analysis.js +517 -266
  10. package/core/ast-analysis.js +76 -117
  11. package/core/async-producers.js +1729 -0
  12. package/core/bridge.js +1877 -268
  13. package/core/brief.js +6 -0
  14. package/core/build-worker.js +78 -14
  15. package/core/cache.js +529 -31
  16. package/core/callers.js +12228 -921
  17. package/core/check.js +30 -38
  18. package/core/class-identity.js +1028 -0
  19. package/core/command-contracts.js +6 -6
  20. package/core/configuration-items.js +86 -0
  21. package/core/contract-membership.js +2262 -0
  22. package/core/cpp-scope.js +532 -0
  23. package/core/deadcode.js +171 -49
  24. package/core/decorator-binding.js +260 -0
  25. package/core/discovery.js +281 -164
  26. package/core/errors.js +52 -0
  27. package/core/execute.js +21 -9
  28. package/core/external-macros.js +326 -0
  29. package/core/graph-build.js +277 -8
  30. package/core/graph.js +35 -2
  31. package/core/ignore-rules.js +472 -0
  32. package/core/imports.js +504 -42
  33. package/core/index-ir.js +94 -11
  34. package/core/inprocess-clients.js +636 -0
  35. package/core/ir.js +87 -8
  36. package/core/macro-expansion.js +1416 -0
  37. package/core/output/analysis.js +18 -2
  38. package/core/output/brief.js +3 -0
  39. package/core/output/doctor.js +4 -0
  40. package/core/output/endpoints.js +15 -6
  41. package/core/output/find.js +7 -2
  42. package/core/output/refactoring.js +121 -14
  43. package/core/output/reporting.js +31 -3
  44. package/core/output/shared.js +14 -0
  45. package/core/output-budget.js +305 -122
  46. package/core/parallel-build.js +124 -47
  47. package/core/parser.js +3 -2
  48. package/core/project.js +472 -66
  49. package/core/provenance-facts.js +156 -12
  50. package/core/provenance-overload.js +30 -3
  51. package/core/provenance.js +104 -30
  52. package/core/pytest-fixtures.js +434 -0
  53. package/core/python-fixture-flow.js +17 -3
  54. package/core/python-modules.js +173 -0
  55. package/core/reflection.js +877 -0
  56. package/core/registry.js +1 -1
  57. package/core/reporting.js +234 -52
  58. package/core/request-helpers.js +561 -0
  59. package/core/route-graph.js +1922 -0
  60. package/core/route-lists.js +2414 -0
  61. package/core/rust-macro-expansion.js +3026 -0
  62. package/core/rust-modules.js +203 -0
  63. package/core/search.js +121 -29
  64. package/core/shared.js +19 -1
  65. package/core/test-name-bindings.js +256 -0
  66. package/core/type-denotation.js +1185 -0
  67. package/core/type-references.js +3229 -0
  68. package/core/verify.js +3360 -672
  69. package/languages/adapter.js +31 -5
  70. package/languages/c-family.js +4061 -212
  71. package/languages/c-macro-invocations.js +380 -0
  72. package/languages/c-preprocessor.js +254 -0
  73. package/languages/csharp.js +1406 -49
  74. package/languages/go.js +1243 -70
  75. package/languages/index.js +728 -6
  76. package/languages/java.js +473 -61
  77. package/languages/javascript.js +1270 -197
  78. package/languages/lexical-scope.js +1280 -0
  79. package/languages/python.js +1557 -72
  80. package/languages/rust-macro-rules.js +1020 -0
  81. package/languages/rust-value-flow.js +416 -0
  82. package/languages/rust.js +2265 -112
  83. package/languages/utils.js +298 -4
  84. package/mcp/server.js +22 -8
  85. package/package.json +1 -1
package/languages/rust.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
9
+ const { referenceScope, scopeFields, rustSelfFieldBinding } = require('./lexical-scope');
9
10
 
10
11
 
11
12
  const {
@@ -17,8 +18,11 @@ const {
17
18
  extractRustDocstring,
18
19
  visitNameNodes,
19
20
  sameNode,
21
+ parseErrorRegions,
22
+ cachedNodeRange,
20
23
  } = require('./utils');
21
24
  const { PARSE_OPTIONS, safeParse } = require('./index');
25
+ const { rustValueFacts } = require('./rust-value-flow');
22
26
 
23
27
  function parseTree(parser, code) {
24
28
  return safeParse(parser, code, undefined, PARSE_OPTIONS);
@@ -60,6 +64,22 @@ function tokenTreeMayDeclareItem(tokenTree) {
60
64
  return false;
61
65
  }
62
66
 
67
+ /**
68
+ * Item bodies of item-position macro invocations that declare functions
69
+ * (fix #401): the byte- and line-preserving recovery parse and the token
70
+ * tree content ranges whose parse holds at least one `fn` with a body. Calls
71
+ * in those ranges are read from the recovered items. null when none.
72
+ */
73
+ function rustMacroItemCallRecovery(code, parser) {
74
+ // The declaration trees are memoized on the code: the call extractor and
75
+ // the declaration extractors share one recovery parse.
76
+ const declaration = declarationTrees(code, parser);
77
+ if (declaration.macroCallRecovery === undefined) {
78
+ declaration.macroCallRecovery = buildMacroItemRecoveryTree(code, parser, declaration.primary)?.callRecovery || null;
79
+ }
80
+ return declaration.macroCallRecovery;
81
+ }
82
+
63
83
  function buildMacroItemRecoveryTree(code, parser, tree) {
64
84
  const ranges = [];
65
85
  traverseTreeCached(tree.rootNode, node => {
@@ -87,16 +107,33 @@ function buildMacroItemRecoveryTree(code, parser, tree) {
87
107
  const recovered = parseTree(parser, masked.join(''));
88
108
  let itemCount = 0;
89
109
  const declarationNameStarts = new Set();
110
+ const callRanges = new Set();
90
111
  traverseTreeCached(recovered.rootNode, node => {
91
112
  if (MACRO_ITEM_NODES.has(node.type)) {
92
113
  itemCount++;
93
114
  const name = node.childForFieldName('name');
94
115
  if (name) declarationNameStarts.add(name.startIndex);
116
+ if (node.type === 'function_item' && node.childForFieldName('body')) {
117
+ const range = ranges.find(([start, end]) => node.startIndex >= start && node.endIndex <= end);
118
+ if (range) callRanges.add(range);
119
+ }
95
120
  }
96
121
  return true;
97
122
  });
98
123
  if (itemCount === 0) return null;
99
- return { tree: recovered, itemCount, declarationNameStarts };
124
+ const callRecovery = callRanges.size > 0
125
+ ? { tree: recovered, ranges: [...callRanges].sort((a, b) => a[0] - b[0]) } : null;
126
+ return { tree: recovered, itemCount, declarationNameStarts, callRecovery };
127
+ }
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;
100
137
  }
101
138
 
102
139
  /**
@@ -116,6 +153,7 @@ function declarationTrees(code, parser) {
116
153
  macroItemRecovery: !!macro,
117
154
  macroItemCount: macro?.itemCount || 0,
118
155
  macroDeclarationNameStarts: macro?.declarationNameStarts || new Set(),
156
+ macroCallRecovery: macro?.callRecovery || null,
119
157
  };
120
158
  lastDeclarationParser = parser;
121
159
  lastDeclarationCode = code;
@@ -123,6 +161,189 @@ function declarationTrees(code, parser) {
123
161
  return result;
124
162
  }
125
163
 
164
+ /** Names of the outer attributes preceding an item (`#[cfg(..)]` -> cfg). */
165
+ function rustAttributeNames(node) {
166
+ const names = [];
167
+ for (let sibling = node.previousNamedSibling; sibling; sibling = sibling.previousNamedSibling) {
168
+ if (sibling.type === 'line_comment' || sibling.type === 'block_comment') continue;
169
+ if (sibling.type !== 'attribute_item') break;
170
+ const head = sibling.namedChildren.find(child => child.type === 'attribute')
171
+ ?.namedChildren.find(child => child.type === 'identifier' || child.type === 'scoped_identifier');
172
+ if (head) names.unshift(head.text);
173
+ }
174
+ return names;
175
+ }
176
+
177
+ /**
178
+ * Declaration trees for a source the caller already parsed and knows holds
179
+ * no macro item bodies to recover (fix #374: an expanded file, whose project
180
+ * invocations are already replaced by their expansions). Primes the memo
181
+ * the extractors below read, so they share that tree.
182
+ */
183
+ function primeDeclarationTrees(code, parser, tree) {
184
+ lastDeclarationParser = parser;
185
+ lastDeclarationCode = code;
186
+ lastDeclarationTrees = {
187
+ primary: tree,
188
+ trees: [tree],
189
+ macroItemRecovery: false,
190
+ macroItemCount: 0,
191
+ macroDeclarationNameStarts: new Set(),
192
+ // An expansion may hand items to an outside macro (`quickcheck! {
193
+ // fn .. }`, fix #401): their calls are read from the item parse,
194
+ // computed when calls are extracted.
195
+ macroCallRecovery: undefined,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Function qualifiers from the AST `function_modifiers` node (fix #370): a
201
+ * first-line text probe misread `fn f() -> impl Future { async { .. } }` as
202
+ * an async fn and missed qualifiers written on a later line.
203
+ */
204
+ function rustFunctionQualifiers(node) {
205
+ const out = { async: false, unsafe: false, const: false, extern: false };
206
+ const mods = node?.namedChildren?.find(child => child.type === 'function_modifiers');
207
+ if (!mods) return out;
208
+ for (let i = 0; i < mods.childCount; i++) {
209
+ const type = mods.child(i).type;
210
+ if (type === 'async') out.async = true;
211
+ else if (type === 'unsafe') out.unsafe = true;
212
+ else if (type === 'const') out.const = true;
213
+ else if (type === 'extern_modifier' || type === 'extern') out.extern = true;
214
+ }
215
+ return out;
216
+ }
217
+
218
+ // Future vocabulary (fix #370): the std/core Future trait, the smart-pointer
219
+ // wrappers that keep a future a future, and the futures-crate boxed aliases.
220
+ const RUST_FUTURE_TRAITS = new Set(['Future']);
221
+ const RUST_FUTURE_WRAPPERS = new Set(['Pin', 'Box']);
222
+ const RUST_FUTURE_ALIASES = new Set(['BoxFuture', 'LocalBoxFuture']);
223
+
224
+ function rustTypeLastName(typeNode) {
225
+ if (!typeNode) return null;
226
+ if (typeNode.type === 'type_identifier') return typeNode.text;
227
+ if (typeNode.type === 'scoped_type_identifier') return typeNode.childForFieldName('name')?.text || null;
228
+ if (typeNode.type === 'generic_type') return rustTypeLastName(typeNode.childForFieldName('type'));
229
+ return null;
230
+ }
231
+
232
+ function rustTypeArguments(typeNode) {
233
+ const args = typeNode?.type === 'generic_type' ? typeNode.childForFieldName('type_arguments') : null;
234
+ return (args?.namedChildren || []).filter(arg => arg.type !== 'lifetime');
235
+ }
236
+
237
+ /**
238
+ * Whether a type node denotes a future, and its Output type text:
239
+ * `impl Future<Output = T> (+ Send)`, `dyn Future<..>` behind `Pin`/`Box`/
240
+ * references, `BoxFuture<'a, T>`. Returns { output } or null.
241
+ */
242
+ function rustFutureShape(typeNode, depth = 0) {
243
+ if (!typeNode || depth > 8) return null;
244
+ switch (typeNode.type) {
245
+ case 'bounded_type':
246
+ for (const child of typeNode.namedChildren) {
247
+ const shape = rustFutureShape(child, depth + 1);
248
+ if (shape) return shape;
249
+ }
250
+ return null;
251
+ case 'abstract_type':
252
+ case 'dynamic_type': {
253
+ const bound = typeNode.childForFieldName('trait') || typeNode.namedChild(0);
254
+ if (bound?.type === 'bounded_type') return rustFutureShape(bound, depth + 1);
255
+ if (!RUST_FUTURE_TRAITS.has(rustTypeLastName(bound))) return null;
256
+ const args = bound.type === 'generic_type' ? bound.childForFieldName('type_arguments') : null;
257
+ const output = (args?.namedChildren || []).find(arg =>
258
+ arg.type === 'type_binding' && arg.childForFieldName('name')?.text === 'Output');
259
+ return { output: output?.childForFieldName('type')?.text || null };
260
+ }
261
+ case 'reference_type':
262
+ return rustFutureShape(typeNode.childForFieldName('type'), depth + 1);
263
+ case 'generic_type': {
264
+ const name = rustTypeLastName(typeNode);
265
+ const args = rustTypeArguments(typeNode);
266
+ if (RUST_FUTURE_WRAPPERS.has(name) && args.length === 1) return rustFutureShape(args[0], depth + 1);
267
+ if (RUST_FUTURE_ALIASES.has(name)) return { output: args.length > 0 ? args[args.length - 1].text : null };
268
+ return null;
269
+ }
270
+ case 'type_identifier':
271
+ case 'scoped_type_identifier':
272
+ return RUST_FUTURE_ALIASES.has(rustTypeLastName(typeNode)) ? { output: null } : null;
273
+ default:
274
+ return null;
275
+ }
276
+ }
277
+
278
+ // Attributes that never replace a function's body or signature: the
279
+ // language's built-in lint/doc/codegen attributes and `tracing::instrument`
280
+ // (which preserves the async signature). Any other attribute may be a
281
+ // proc macro that rewrites the function (`#[tokio::main]` turns an async fn
282
+ // into a blocking sync fn), so its call result is not known.
283
+ const RUST_TRANSPARENT_FN_ATTRIBUTES = new Set([
284
+ 'inline', 'cold', 'must_use', 'allow', 'warn', 'deny', 'forbid', 'expect',
285
+ 'deprecated', 'doc', 'cfg', 'track_caller', 'target_feature', 'rustfmt::skip',
286
+ 'instrument', 'tracing::instrument',
287
+ ]);
288
+
289
+ function rustAttributePath(node) {
290
+ return node && ['identifier', 'scoped_identifier'].includes(node.type) ? node.text.replace(/\s+/g, '') : null;
291
+ }
292
+
293
+ /** Outer attribute paths of an item; `cfg_attr(pred, a, b)` yields a, b. */
294
+ function rustOuterAttributePaths(itemNode) {
295
+ const paths = [];
296
+ for (let sibling = itemNode.previousNamedSibling; sibling; sibling = sibling.previousNamedSibling) {
297
+ if (sibling.type === 'line_comment' || sibling.type === 'block_comment') continue;
298
+ if (sibling.type !== 'attribute_item') break;
299
+ const attribute = sibling.namedChildren.find(child => child.type === 'attribute');
300
+ const path = rustAttributePath(attribute?.namedChild(0));
301
+ if (!path) { paths.push('?'); continue; }
302
+ if (path !== 'cfg_attr') { paths.push(path); continue; }
303
+ const args = attribute.childForFieldName('arguments');
304
+ let group = 0;
305
+ let expectPath = false;
306
+ let current = '';
307
+ for (let i = 0; args && i < args.childCount; i++) {
308
+ const token = args.child(i);
309
+ if (token.type === ',') {
310
+ if (current) paths.push(current);
311
+ current = '';
312
+ group++;
313
+ expectPath = true;
314
+ continue;
315
+ }
316
+ if (group === 0 || !expectPath) continue;
317
+ if (token.type === 'identifier' || token.type === '::') current += token.text;
318
+ else { if (current) paths.push(current); current = ''; expectPath = false; }
319
+ }
320
+ if (current) paths.push(current);
321
+ }
322
+ return paths;
323
+ }
324
+
325
+ /**
326
+ * What calling a function returns when that is a future (fix #370):
327
+ * `async fn` -> { kind: 'async', output }, a future-shaped return type ->
328
+ * { kind: 'future', output }. Null otherwise (named return types are
329
+ * resolved against project `impl Future` types and aliases at query time).
330
+ */
331
+ function rustFutureReturn(fnNode, qualifiers) {
332
+ const returnNode = fnNode.childForFieldName('return_type');
333
+ let result = null;
334
+ if (qualifiers.async) {
335
+ result = { kind: 'async', output: returnNode ? nodeTextWithoutComments(returnNode).trim() : '()' };
336
+ } else {
337
+ const shape = rustFutureShape(returnNode);
338
+ if (shape) result = { kind: 'future', output: shape.output };
339
+ }
340
+ if (result) {
341
+ const wrapping = rustOuterAttributePaths(fnNode).find(path => !RUST_TRANSPARENT_FN_ATTRIBUTES.has(path));
342
+ if (wrapping) result = { kind: 'wrapped', output: result.output, attribute: wrapping };
343
+ }
344
+ return result;
345
+ }
346
+
126
347
  /**
127
348
  * Extract return type from Rust function
128
349
  */
@@ -203,16 +424,82 @@ function extractRustParams(paramsNode) {
203
424
  return text.replace(/^\(|\)$/g, '').trim();
204
425
  }
205
426
 
206
- function extractRustCallbackParamTypes(paramsNode) {
427
+ /**
428
+ * Generic parameters bounded by a closure trait (fix #368):
429
+ * `fn join<A: FnOnce(FnContext) -> R>(a: A)` and `where A: FnOnce(...)`.
430
+ * Returns name -> function_type node, plus every declared type-parameter
431
+ * name (a closure argument typed by a generic parameter has no concrete
432
+ * identity, so such slots stay unknown).
433
+ */
434
+ function rustGenericClosureBounds(ownerNode) {
435
+ const bounds = new Map();
436
+ const declared = new Set();
437
+ if (!ownerNode) return { bounds, declared };
438
+ const functionTypeIn = (boundsNode) => {
439
+ const pending = boundsNode ? [boundsNode] : [];
440
+ while (pending.length > 0) {
441
+ const current = pending.pop();
442
+ if (current.type === 'function_type' &&
443
+ ['Fn', 'FnMut', 'FnOnce'].includes(current.childForFieldName('trait')?.text)) {
444
+ return current;
445
+ }
446
+ for (let j = 0; j < current.namedChildCount; j++) pending.push(current.namedChild(j));
447
+ }
448
+ return null;
449
+ };
450
+ const record = (nameNode, boundsNode) => {
451
+ if (nameNode?.type !== 'type_identifier') return;
452
+ const functionType = functionTypeIn(boundsNode);
453
+ if (!functionType) return;
454
+ if (bounds.has(nameNode.text) && bounds.get(nameNode.text) !== functionType) {
455
+ bounds.set(nameNode.text, null); // two closure bounds: ambiguous
456
+ } else {
457
+ bounds.set(nameNode.text, functionType);
458
+ }
459
+ };
460
+ const owners = [ownerNode];
461
+ for (let a = ownerNode.parent; a; a = a.parent) {
462
+ if (a.type === 'impl_item' || a.type === 'trait_item') {
463
+ owners.push(a);
464
+ break;
465
+ }
466
+ if (a.type === 'function_item') break;
467
+ }
468
+ for (const owner of owners) {
469
+ const typeParameters = owner.childForFieldName('type_parameters');
470
+ for (const child of typeParameters?.namedChildren || []) {
471
+ if (child.type === 'type_identifier') declared.add(child.text);
472
+ else if (child.type === 'constrained_type_parameter') {
473
+ const left = child.childForFieldName('left');
474
+ if (left?.type === 'type_identifier') declared.add(left.text);
475
+ if (owner === ownerNode) record(left, child.childForFieldName('bounds'));
476
+ } else if (child.type === 'optional_type_parameter') {
477
+ const name = child.childForFieldName('name');
478
+ if (name?.type === 'type_identifier') declared.add(name.text);
479
+ }
480
+ }
481
+ if (owner !== ownerNode) continue;
482
+ const whereClause = owner.namedChildren.find(child => child.type === 'where_clause');
483
+ for (const predicate of whereClause?.namedChildren || []) {
484
+ if (predicate.type !== 'where_predicate') continue;
485
+ record(predicate.childForFieldName('left'), predicate.childForFieldName('bounds'));
486
+ }
487
+ }
488
+ return { bounds, declared };
489
+ }
490
+
491
+ function extractRustCallbackParamTypes(paramsNode, ownerNode = null) {
207
492
  if (!paramsNode) return undefined;
208
493
  const callbacks = {};
209
494
  let callArgumentIndex = 0;
495
+ let generic = null;
210
496
  for (let i = 0; i < paramsNode.namedChildCount; i++) {
211
497
  const parameter = paramsNode.namedChild(i);
212
498
  if (parameter.type === 'self_parameter') continue;
213
499
  if (parameter.type !== 'parameter') continue;
214
500
  const typeNode = parameter.childForFieldName('type');
215
501
  let functionType = null;
502
+ let viaBound = false;
216
503
  const pending = typeNode ? [typeNode] : [];
217
504
  while (pending.length > 0 && !functionType) {
218
505
  const current = pending.pop();
@@ -224,19 +511,52 @@ function extractRustCallbackParamTypes(paramsNode) {
224
511
  pending.push(current.namedChild(j));
225
512
  }
226
513
  }
514
+ if (!functionType && ownerNode) {
515
+ // `op: OP` / `op: &OP` / `op: &mut OP` with OP bounded by a
516
+ // closure trait on this function (fix #368).
517
+ let head = typeNode;
518
+ while (head?.type === 'reference_type') {
519
+ head = head.childForFieldName('type');
520
+ }
521
+ if (head?.type === 'type_identifier') {
522
+ generic = generic || rustGenericClosureBounds(ownerNode);
523
+ const bound = generic.bounds.get(head.text);
524
+ if (bound) {
525
+ functionType = bound;
526
+ viaBound = true;
527
+ }
528
+ }
529
+ }
227
530
  const callbackParams = functionType?.childForFieldName('parameters');
228
531
  if (callbackParams) {
532
+ if (viaBound || ownerNode) {
533
+ generic = generic || rustGenericClosureBounds(ownerNode);
534
+ }
229
535
  const types = [];
230
536
  let complete = true;
537
+ let known = 0;
231
538
  for (let j = 0; j < callbackParams.namedChildCount; j++) {
232
- const name = aliasBaseTypeName(callbackParams.namedChild(j));
539
+ const slot = callbackParams.namedChild(j);
540
+ if (slot.type.endsWith('comment')) continue;
541
+ const name = aliasBaseTypeName(slot);
233
542
  if (!name) {
234
- complete = false;
235
- break;
543
+ if (!ownerNode) {
544
+ complete = false;
545
+ break;
546
+ }
547
+ types.push(null);
548
+ continue;
549
+ }
550
+ // A slot naming a declared type parameter is not a concrete
551
+ // type; keep its position, never its spelling.
552
+ if (generic && generic.declared.has(name)) {
553
+ types.push(null);
554
+ continue;
236
555
  }
237
556
  types.push(name);
557
+ known++;
238
558
  }
239
- if (complete && types.length > 0) callbacks[callArgumentIndex] = types;
559
+ if (complete && known > 0) callbacks[callArgumentIndex] = types;
240
560
  }
241
561
  callArgumentIndex++;
242
562
  }
@@ -337,7 +657,9 @@ function extractAttributesWithArgs(node, codeOrLines) {
337
657
  if (line.startsWith('#[')) {
338
658
  // Match #[name(...args...)] or #[name]
339
659
  // Need to handle nested parens; use a simple bracket-matching approach.
340
- const m = line.match(/^#\[(.+)\]\s*$/);
660
+ // (The line is trimmed: only a line ending in ']' can match; a
661
+ // macro expansion's generated line can be very long.)
662
+ const m = line.charCodeAt(line.length - 1) === 93 ? line.match(/^#\[(.+)\]\s*$/) : null;
341
663
  if (m) {
342
664
  const attrContent = m[1];
343
665
  const parenIdx = attrContent.indexOf('(');
@@ -437,12 +759,13 @@ function _processFunction(node, functions, processedRanges, lines, code) {
437
759
  if (nameNode) {
438
760
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
439
761
  const text = node.text;
440
- const firstLine = text.split('\n')[0];
441
762
 
442
- const isAsync = firstLine.includes('async ');
443
- const isUnsafe = firstLine.includes('unsafe ');
444
- const isConst = firstLine.includes('const fn');
445
- const isExtern = firstLine.includes('extern ');
763
+ const qualifiers = rustFunctionQualifiers(node);
764
+ const isAsync = qualifiers.async;
765
+ const isUnsafe = qualifiers.unsafe;
766
+ const isConst = qualifiers.const;
767
+ const isExtern = qualifiers.extern;
768
+ const futureReturn = rustFutureReturn(node, qualifiers);
446
769
  const visibility = extractVisibility(text);
447
770
  const returnType = extractReturnType(node);
448
771
  const iteratorItemType = extractRustIteratorItemType(node);
@@ -452,7 +775,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
452
775
  const attributes = extractAttributes(node, lines);
453
776
  const attributesWithArgs = extractAttributesWithArgs(node, lines);
454
777
  const inCfgTest = _isInsideCfgTestModule(node, lines);
455
- const callbackParamTypes = extractRustCallbackParamTypes(paramsNode);
778
+ const callbackParamTypes = extractRustCallbackParamTypes(paramsNode, node);
456
779
 
457
780
  const modifiers = [];
458
781
  if (visibility) modifiers.push(visibility);
@@ -478,6 +801,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
478
801
  indent,
479
802
  modifiers,
480
803
  ...(returnType && { returnType }),
804
+ ...(futureReturn && { futureReturn }),
481
805
  ...(iteratorItemType && { iteratorItemType }),
482
806
  ...(docstring && { docstring }),
483
807
  ...(generics && { generics }),
@@ -506,7 +830,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
506
830
  const visibility = extractVisibility(child.text);
507
831
  const returnType = extractReturnType(child);
508
832
  const docstring = extractRustDocstring(lines, startLine);
509
- const callbackParamTypes = extractRustCallbackParamTypes(fParams);
833
+ const callbackParamTypes = extractRustCallbackParamTypes(fParams, child);
510
834
  const iteratorItemType = extractRustIteratorItemType(child);
511
835
  const modifiers = ['extern'];
512
836
  if (visibility) modifiers.push(visibility);
@@ -715,9 +1039,39 @@ function _inferMacroReturn(node, macroName) {
715
1039
  * Returns true if node was matched, false otherwise
716
1040
  * Note: for impl_item, caller should NOT skip subtrees (parse() always returns true)
717
1041
  */
1042
+ /**
1043
+ * Lexical scope of an item declared in a function body (fix #381): Rust
1044
+ * items in a block are visible throughout that block and nowhere else.
1045
+ */
1046
+ function rustBlockItemScope(node) {
1047
+ for (let parent = node.parent; parent; parent = parent.parent) {
1048
+ if (parent.type === 'declaration_list' || parent.type === 'source_file') return {};
1049
+ if (parent.type === 'block') {
1050
+ return {
1051
+ lexicalScopeStartLine: parent.startPosition.row + 1,
1052
+ lexicalScopeEndLine: parent.endPosition.row + 1,
1053
+ };
1054
+ }
1055
+ }
1056
+ return {};
1057
+ }
1058
+
1059
+ /**
1060
+ * How a struct's name works as a VALUE (fix #389): a unit struct (`struct
1061
+ * S;`) names its only value, a tuple struct (`struct T(u8);`) names its
1062
+ * constructor function; a braced struct's name is a type only.
1063
+ */
1064
+ function rustStructValueShape(node) {
1065
+ const body = node.childForFieldName('body');
1066
+ if (!body) return 'unit';
1067
+ if (body.type === 'ordered_field_declaration_list') return 'tuple';
1068
+ return null;
1069
+ }
1070
+
718
1071
  function _processClass(node, types, processedRanges, lines, code) {
719
- // Struct items
720
- if (node.type === 'struct_item') {
1072
+ // Struct and union items (a union is declared like a braced struct;
1073
+ // its fields are its members, fix #389)
1074
+ if (node.type === 'struct_item' || node.type === 'union_item') {
721
1075
  const rangeKey = `${node.startIndex}-${node.endIndex}`;
722
1076
  if (processedRanges.has(rangeKey)) return true;
723
1077
  processedRanges.add(rangeKey);
@@ -732,14 +1086,17 @@ function _processClass(node, types, processedRanges, lines, code) {
732
1086
  const attributes = extractAttributes(node, lines);
733
1087
  const modifiers = visibility ? [visibility] : [];
734
1088
  for (const attr of attributes) modifiers.push(attr);
1089
+ const valueShape = node.type === 'struct_item' ? rustStructValueShape(node) : null;
735
1090
 
736
1091
  types.push({
737
1092
  name: nameNode.text,
738
1093
  startLine,
739
1094
  endLine,
740
- type: 'struct',
1095
+ type: node.type === 'union_item' ? 'union' : 'struct',
741
1096
  members,
742
1097
  modifiers,
1098
+ ...(valueShape && { valueShape }),
1099
+ ...rustBlockItemScope(node),
743
1100
  ...(docstring && { docstring }),
744
1101
  ...(generics && { generics })
745
1102
  });
@@ -770,6 +1127,7 @@ function _processClass(node, types, processedRanges, lines, code) {
770
1127
  type: 'enum',
771
1128
  members: extractEnumVariants(node, lines),
772
1129
  modifiers,
1130
+ ...rustBlockItemScope(node),
773
1131
  ...(docstring && { docstring }),
774
1132
  ...(generics && { generics })
775
1133
  });
@@ -789,6 +1147,7 @@ function _processClass(node, types, processedRanges, lines, code) {
789
1147
  const docstring = extractRustDocstring(lines, startLine);
790
1148
  const visibility = extractVisibility(node.text);
791
1149
  const generics = extractGenerics(node);
1150
+ const supertraits = extractRustSupertraits(node);
792
1151
 
793
1152
  types.push({
794
1153
  name: nameNode.text,
@@ -797,8 +1156,10 @@ function _processClass(node, types, processedRanges, lines, code) {
797
1156
  type: 'trait',
798
1157
  members: extractTraitMembers(node, lines),
799
1158
  modifiers: visibility ? [visibility] : [],
1159
+ ...rustBlockItemScope(node),
800
1160
  ...(docstring && { docstring }),
801
- ...(generics && { generics })
1161
+ ...(generics && { generics }),
1162
+ ...(supertraits && { supertraits }),
802
1163
  });
803
1164
  }
804
1165
  return true;
@@ -814,6 +1175,13 @@ function _processClass(node, types, processedRanges, lines, code) {
814
1175
  const implInfo = extractImplInfo(node);
815
1176
  const docstring = extractRustDocstring(lines, startLine);
816
1177
  const derefTarget = extractDerefTarget(node, implInfo.traitName);
1178
+ const implSelfTypeNode = node.childForFieldName('type');
1179
+ const implSelfRef = implSelfTypeNode?.type === 'reference_type'
1180
+ ? (implSelfTypeNode.namedChildren.some(child => child.type === 'mutable_specifier')
1181
+ ? '&mut' : '&')
1182
+ : null;
1183
+ const blanketSelfBounds = implInfo.traitName
1184
+ ? extractRustBlanketSelfBounds(node, implSelfTypeNode) : null;
817
1185
 
818
1186
  types.push({
819
1187
  name: implInfo.name,
@@ -821,10 +1189,13 @@ function _processClass(node, types, processedRanges, lines, code) {
821
1189
  endLine,
822
1190
  type: 'impl',
823
1191
  traitName: implInfo.traitName,
1192
+ ...rustBlockItemScope(node),
824
1193
  typeName: implInfo.typeName,
825
1194
  members: extractImplMembers(node, lines, implInfo.typeName),
826
1195
  modifiers: [],
827
1196
  ...(implInfo.generics && { generics: implInfo.generics }),
1197
+ ...(implSelfRef && { implSelfRef }),
1198
+ ...(blanketSelfBounds && { blanketSelfBounds }),
828
1199
  ...(derefTarget && { derefTarget }),
829
1200
  ...(docstring && { docstring })
830
1201
  });
@@ -871,13 +1242,33 @@ function _processClass(node, types, processedRanges, lines, code) {
871
1242
  const docstring = extractRustDocstring(lines, startLine);
872
1243
  const inferred = _inferMacroReturn(node, nameNode.text);
873
1244
 
1245
+ // Attributes and the textual scope a macro_rules! definition is
1246
+ // visible in (fix #374: macro expansion resolves invocations by
1247
+ // Rust's textual scoping without re-parsing the defining file).
1248
+ const modifiers = rustAttributeNames(node).filter(name => name === 'macro_export' || name === 'cfg');
1249
+ let macroScope = null;
1250
+ for (let parent = node.parent; parent && parent.type !== 'source_file'; parent = parent.parent) {
1251
+ if (parent.type === 'block' ||
1252
+ (parent.type === 'declaration_list' && parent.parent?.type === 'mod_item')) {
1253
+ const mod = parent.type === 'declaration_list';
1254
+ const macroUse = mod && rustAttributeNames(parent.parent).includes('macro_use');
1255
+ macroScope = {
1256
+ kind: mod ? 'mod' : 'block',
1257
+ startLine: parent.startPosition.row + 1, endLine: parent.endPosition.row + 1,
1258
+ ...(macroUse && { macroUse: true }),
1259
+ };
1260
+ break;
1261
+ }
1262
+ }
1263
+
874
1264
  types.push({
875
1265
  name: nameNode.text,
876
1266
  startLine,
877
1267
  endLine,
878
1268
  type: 'macro',
879
1269
  members: [],
880
- modifiers: [],
1270
+ modifiers,
1271
+ ...(macroScope && { macroScope }),
881
1272
  ...inferred,
882
1273
  ...(docstring && { docstring })
883
1274
  });
@@ -911,6 +1302,9 @@ function _processClass(node, types, processedRanges, lines, code) {
911
1302
  // callers can treat alias-qualified receivers as the base type
912
1303
  // (fix #208 — cursive StyledString::plain).
913
1304
  const aliasOf = aliasBaseTypeName(node.childForFieldName('type'));
1305
+ // An alias of a future type (`type Fut = Pin<Box<dyn Future..>>`)
1306
+ // makes functions returning it future producers (fix #370).
1307
+ const futureShape = rustFutureShape(node.childForFieldName('type'));
914
1308
 
915
1309
  types.push({
916
1310
  name: nameNode.text,
@@ -920,6 +1314,7 @@ function _processClass(node, types, processedRanges, lines, code) {
920
1314
  members: [],
921
1315
  modifiers: visibility ? [visibility] : [],
922
1316
  ...(aliasOf && { aliasOf }),
1317
+ ...(futureShape && { futureReturn: { kind: 'future', output: futureShape.output } }),
923
1318
  aliasTypeText: node.childForFieldName('type')?.text,
924
1319
  aliasTypeParameters: (node.childForFieldName('type_parameters')?.namedChildren || [])
925
1320
  .map(parameter => parameter.type === 'type_identifier' ? parameter.text
@@ -982,29 +1377,31 @@ function extractDerefTarget(implNode, traitName) {
982
1377
  * Returns true if node was matched, false otherwise
983
1378
  */
984
1379
  function _processState(node, objects, lines) {
985
- // Handle const items (only top-level)
986
- if (node.type === 'const_item') {
1380
+ // Handle const and static items (only top-level). The declared type
1381
+ // (fix #392) types the item when it is a method receiver (`FLAGS.iter()`).
1382
+ if (node.type === 'const_item' || node.type === 'static_item') {
987
1383
  if (!node.parent || node.parent.type !== 'source_file') return false;
988
1384
  const nameNode = node.childForFieldName('name');
989
1385
  if (nameNode) {
990
1386
  const name = nameNode.text;
991
1387
  if (_STATE_PATTERN.test(name)) {
992
1388
  const { startLine, endLine } = nodeToLocation(node, lines);
993
- objects.push({ name, startLine, endLine });
994
- }
995
- }
996
- return true;
997
- }
998
-
999
- // Handle static items (only top-level)
1000
- if (node.type === 'static_item') {
1001
- if (!node.parent || node.parent.type !== 'source_file') return false;
1002
- const nameNode = node.childForFieldName('name');
1003
- if (nameNode) {
1004
- const name = nameNode.text;
1005
- if (_STATE_PATTERN.test(name)) {
1006
- const { startLine, endLine } = nodeToLocation(node, lines);
1007
- objects.push({ name, startLine, endLine });
1389
+ // Only a plain type path (behind any references) is recorded:
1390
+ // a generic wrapper (`Lazy<T>`, `Mutex<T>`) owns the methods
1391
+ // or derefs to its argument. A `static mut` is never typed.
1392
+ let typeNode = node.childForFieldName('type');
1393
+ // A std deref wrapper (`LazyLock<Store>`, fix #401) receives
1394
+ // as its target; the chain is recorded for the query.
1395
+ const deref = rustStdDerefChain(typeNode);
1396
+ if (deref) typeNode = deref.inner;
1397
+ while (typeNode?.type === 'reference_type') typeNode = typeNode.childForFieldName('type');
1398
+ const mutable = node.type === 'static_item' &&
1399
+ node.children.some(child => child.type === 'mutable_specifier');
1400
+ const plain = typeNode && (typeNode.type === 'type_identifier' ||
1401
+ typeNode.type === 'scoped_type_identifier');
1402
+ objects.push({ name, startLine, endLine,
1403
+ ...(plain && !mutable && { valueType: typeNode.text,
1404
+ ...(deref && { valueDerefVia: deref.via }) }) });
1008
1405
  }
1009
1406
  }
1010
1407
  return true;
@@ -1054,7 +1451,10 @@ function extractGenericBounds(node) {
1054
1451
  const record = declaration => {
1055
1452
  if (!declaration) return;
1056
1453
  const children = declaration.namedChildren || [];
1057
- const parameter = children.find(child => child.type === 'type_identifier');
1454
+ // `where for<'a> C: Trait<'a>` bounds C itself (fix #368).
1455
+ const higherRanked = children.find(child => child.type === 'higher_ranked_trait_bound');
1456
+ const parameter = children.find(child => child.type === 'type_identifier') ||
1457
+ (higherRanked?.namedChildren || []).find(child => child.type === 'type_identifier');
1058
1458
  const bounds = children.find(child => child.type === 'trait_bounds');
1059
1459
  if (!parameter || !bounds) return;
1060
1460
  const names = bounds.namedChildren
@@ -1077,6 +1477,170 @@ function extractGenericBounds(node) {
1077
1477
  [name, [...bounds].sort()]));
1078
1478
  }
1079
1479
 
1480
+ /**
1481
+ * Declared supertraits of a trait (fix #368): `trait C<I>: Send + Sized`
1482
+ * and `where Self: P`. Nominal trait heads only (paths keep their terminal
1483
+ * name, `?Sized` and lifetimes add nothing).
1484
+ */
1485
+ function extractRustSupertraits(traitNode) {
1486
+ const names = new Set();
1487
+ const addBounds = (boundsNode) => {
1488
+ for (const bound of boundsNode?.namedChildren || []) {
1489
+ if (bound.type === 'removed_trait_bound' || bound.type === 'lifetime') continue;
1490
+ const name = aliasBaseTypeName(bound.type === 'higher_ranked_trait_bound'
1491
+ ? bound.childForFieldName('type') || bound.namedChildren.at(-1) : bound);
1492
+ if (name) names.add(name);
1493
+ }
1494
+ };
1495
+ addBounds(traitNode.childForFieldName('bounds'));
1496
+ const whereClause = traitNode.namedChildren.find(child => child.type === 'where_clause');
1497
+ for (const predicate of whereClause?.namedChildren || []) {
1498
+ if (predicate.type !== 'where_predicate') continue;
1499
+ if (predicate.childForFieldName('left')?.text !== 'Self') continue;
1500
+ addBounds(predicate.childForFieldName('bounds'));
1501
+ }
1502
+ return names.size > 0 ? [...names].sort() : null;
1503
+ }
1504
+
1505
+ /**
1506
+ * `[T]` behind any reference layers (`&[T]`, `&mut [T]`) is the primitive
1507
+ * slice type (fix #368). Its methods are the slice's inherent methods and
1508
+ * trait impls written `for [T]`; the canonical receiver name is 'slice'.
1509
+ * Arrays (`[T; N]`) unsize to slices during method probing and abstain.
1510
+ */
1511
+ function rustSliceTypeOf(typeNode) {
1512
+ let current = typeNode;
1513
+ while (current?.type === 'reference_type') current = current.childForFieldName('type');
1514
+ return current?.type === 'array_type' && !current.childForFieldName('length') ? 'slice' : null;
1515
+ }
1516
+
1517
+ /**
1518
+ * A raw pointer type behind any reference layers (`*const T`, `*mut T`) is a
1519
+ * primitive type (fix #399): its methods are the pointer's inherent methods
1520
+ * and trait impls written `for *const T` / `for *mut T`. Canonical receiver
1521
+ * names '*const' and '*mut'; raw pointers never auto-deref.
1522
+ */
1523
+ function rustRawPointerTypeOf(typeNode) {
1524
+ let current = typeNode;
1525
+ while (current?.type === 'reference_type') current = current.childForFieldName('type');
1526
+ if (current?.type !== 'pointer_type') return null;
1527
+ return current.children.some(child => child.type === 'mutable_specifier') ? '*mut' : '*const';
1528
+ }
1529
+
1530
+ /**
1531
+ * Standard-library wrappers whose `Deref::Target` is their (first) type
1532
+ * argument (fix #401): smart pointers, `ManuallyDrop`, lazy cells, `RefCell`
1533
+ * borrow guards, lock guards, `AssertUnwindSafe`, `Cow` (Target = its
1534
+ * borrowed form) and `Pin<P>` (Target = P's own target). Method probing
1535
+ * visits the wrapper first, then its target. Only the written name is
1536
+ * recorded here; the query checks that it denotes the std type.
1537
+ */
1538
+ const RUST_STD_DEREF_WRAPPERS = new Set(['Box', 'Rc', 'Arc', 'ManuallyDrop', 'LazyLock',
1539
+ 'LazyCell', 'Ref', 'RefMut', 'MutexGuard', 'RwLockReadGuard', 'RwLockWriteGuard',
1540
+ 'ReentrantLockGuard', 'AssertUnwindSafe', 'Cow', 'Pin']);
1541
+
1542
+ /**
1543
+ * The std deref wrappers a declared type is written with, outermost first,
1544
+ * and the type they deref to: `&Arc<Store>` -> { via: ['Arc'], inner:
1545
+ * Store }, `Pin<Box<T>>` -> via ['Pin', 'Box']. null when the outermost
1546
+ * named type is not such a wrapper.
1547
+ */
1548
+ function rustStdDerefChain(typeNode) {
1549
+ let current = typeNode;
1550
+ const via = [];
1551
+ for (let hop = 0; hop < 6; hop++) {
1552
+ while (current?.type === 'reference_type') current = current.childForFieldName('type');
1553
+ if (current?.type !== 'generic_type') break;
1554
+ const head = current.childForFieldName('type') || current.namedChild(0);
1555
+ const name = head?.type === 'type_identifier' ? head.text
1556
+ : head?.type === 'scoped_type_identifier' ? head.childForFieldName('name')?.text : null;
1557
+ if (!name || !RUST_STD_DEREF_WRAPPERS.has(name)) break;
1558
+ const argsNode = current.childForFieldName('type_arguments') ||
1559
+ current.namedChildren.find(child => child.type === 'type_arguments');
1560
+ const args = (argsNode?.namedChildren || []).filter(child =>
1561
+ child.type !== 'lifetime' && !child.type.endsWith('comment'));
1562
+ if (args.length === 0) break;
1563
+ via.push(head.text.replace(/\s+/g, ''));
1564
+ current = args[0];
1565
+ }
1566
+ return via.length > 0 ? { via, inner: current } : null;
1567
+ }
1568
+
1569
+ /** Is `name` a type parameter declared by an item enclosing `node`? */
1570
+ function rustTypeParamInScope(node, name) {
1571
+ for (let parent = node?.parent; parent; parent = parent.parent) {
1572
+ if (!['function_item', 'impl_item', 'trait_item', 'struct_item', 'enum_item',
1573
+ 'function_signature_item'].includes(parent.type)) continue;
1574
+ const params = parent.childForFieldName('type_parameters');
1575
+ for (const param of params?.namedChildren || []) {
1576
+ const nameNode = param.type === 'type_identifier' ? param
1577
+ : param.childForFieldName('name') || param.namedChildren.find(child => child.type === 'type_identifier');
1578
+ if (nameNode?.text === name) return true;
1579
+ }
1580
+ }
1581
+ return false;
1582
+ }
1583
+
1584
+ /**
1585
+ * A declared type behind std deref wrappers (fix #401): the target's type
1586
+ * name and the wrapper chain, or null. A target that is a type parameter
1587
+ * of an enclosing item (`Box<T>`) has no name: { via, typeName: null }.
1588
+ */
1589
+ function rustDerefTypedName(typeNode) {
1590
+ const chain = rustStdDerefChain(typeNode);
1591
+ if (!chain) return null;
1592
+ const written = extractTypeName(chain.inner);
1593
+ // A slice or raw pointer target (`Box<[u8]>`) has its canonical name.
1594
+ const primitive = !written ? rustSliceTypeOf(chain.inner) || rustRawPointerTypeOf(chain.inner) : null;
1595
+ const typeName = primitive || (written === 'Self' ? findEnclosingImplType(chain.inner) || null
1596
+ : written && !rustTypeParamInScope(chain.inner, written) ? written : null);
1597
+ return { via: chain.via, typeName, inner: chain.inner, ...(primitive && { std: true }),
1598
+ outerRef: rustTypeRefKind(typeNode) };
1599
+ }
1600
+
1601
+ /** Reference layer of a declared type node: 'owned', '&' or '&mut'. */
1602
+ function rustTypeRefKind(typeNode) {
1603
+ if (typeNode?.type !== 'reference_type') return 'owned';
1604
+ return typeNode.namedChildren.some(child => child.type === 'mutable_specifier') ? '&mut' : '&';
1605
+ }
1606
+
1607
+ /**
1608
+ * How a method takes `self`: 'value' (`self`, `mut self`), '&' (`&self`),
1609
+ * '&mut' (`&mut self`); null for associated functions or typed self
1610
+ * (`self: Box<Self>`), which method probing here does not model.
1611
+ */
1612
+ function rustSelfParamKind(paramsNode) {
1613
+ const selfParameter = paramsNode?.namedChildren?.find(child => child.type === 'self_parameter');
1614
+ if (!selfParameter) return null;
1615
+ let reference = false;
1616
+ let mutable = false;
1617
+ for (let i = 0; i < selfParameter.childCount; i++) {
1618
+ const child = selfParameter.child(i);
1619
+ if (child.type === '&') reference = true;
1620
+ else if (child.type === 'mutable_specifier' && reference) mutable = true;
1621
+ }
1622
+ if (!reference) return 'value';
1623
+ return mutable ? '&mut' : '&';
1624
+ }
1625
+
1626
+ /**
1627
+ * Generic arguments of an impl's self type, in declaration order, lifetimes
1628
+ * excluded: `impl<'f, T, C> Consumer<T> for MapWith<'f, C, U>` -> ['C', 'U'].
1629
+ * Maps a struct's positional type parameters to the impl's own names.
1630
+ */
1631
+ function extractRustImplSelfArgs(implNode) {
1632
+ let typeNode = implNode.childForFieldName('type');
1633
+ while (typeNode?.type === 'reference_type') typeNode = typeNode.childForFieldName('type');
1634
+ if (typeNode?.type !== 'generic_type') return null;
1635
+ const argsNode = typeNode.childForFieldName('type_arguments');
1636
+ const args = [];
1637
+ for (const arg of argsNode?.namedChildren || []) {
1638
+ if (arg.type === 'lifetime' || arg.type.endsWith('comment')) continue;
1639
+ args.push(arg.text);
1640
+ }
1641
+ return args.length > 0 ? args : null;
1642
+ }
1643
+
1080
1644
  /**
1081
1645
  * Find all types (structs, enums, traits, impls) in Rust code
1082
1646
  */
@@ -1160,6 +1724,58 @@ function extractStructFields(structNode, codeOrLines) {
1160
1724
  /**
1161
1725
  * Extract impl block info
1162
1726
  */
1727
+ /**
1728
+ * Bounds of a blanket impl's self parameter (fix #369): for `impl<I: A>
1729
+ * Trait for I where &'a mut I: B, I: C`, one entry per subject reference
1730
+ * layer: [{ ref: 'owned', traits: ['A', 'C'] }, { ref: '&mut', traits:
1731
+ * ['B'] }]. `?Sized` and lifetimes add nothing. null when the impl's self
1732
+ * type is not one of its own type parameters.
1733
+ */
1734
+ function extractRustBlanketSelfBounds(implNode, selfTypeNode) {
1735
+ if (!selfTypeNode || selfTypeNode.type !== 'type_identifier') return null;
1736
+ const param = selfTypeNode.text;
1737
+ const typeParameters = implNode.childForFieldName('type_parameters');
1738
+ const declared = (typeParameters?.namedChildren || []).some(child =>
1739
+ (child.type === 'constrained_type_parameter' &&
1740
+ child.namedChildren.some(n => n.type === 'type_identifier' && n.text === param)) ||
1741
+ (child.type === 'type_identifier' && child.text === param) ||
1742
+ (child.type === 'type_parameter' && child.text.split(/[\s:=]/)[0] === param));
1743
+ if (!declared) return null;
1744
+ const layers = new Map();
1745
+ const add = (layer, boundsNode) => {
1746
+ for (const bound of boundsNode?.namedChildren || []) {
1747
+ if (bound.type === 'removed_trait_bound' || bound.type === 'lifetime') continue;
1748
+ const name = aliasBaseTypeName(bound.type === 'higher_ranked_trait_bound'
1749
+ ? bound.childForFieldName('type') || bound.namedChildren.at(-1) : bound);
1750
+ if (!name) continue;
1751
+ if (!layers.has(layer)) layers.set(layer, new Set());
1752
+ layers.get(layer).add(name);
1753
+ }
1754
+ };
1755
+ for (const child of typeParameters?.namedChildren || []) {
1756
+ if (child.type !== 'constrained_type_parameter') continue;
1757
+ if (!child.namedChildren.some(n => n.type === 'type_identifier' && n.text === param)) continue;
1758
+ add('owned', child.namedChildren.find(n => n.type === 'trait_bounds'));
1759
+ }
1760
+ const whereClause = implNode.namedChildren.find(child => child.type === 'where_clause');
1761
+ for (const predicate of whereClause?.namedChildren || []) {
1762
+ if (predicate.type !== 'where_predicate') continue;
1763
+ const left = predicate.childForFieldName('left') || predicate.namedChild(0);
1764
+ const bounds = predicate.childForFieldName('bounds') ||
1765
+ predicate.namedChildren.find(n => n.type === 'trait_bounds');
1766
+ if (!left) continue;
1767
+ if (left.type === 'type_identifier' && left.text === param) {
1768
+ add('owned', bounds);
1769
+ } else if (left.type === 'reference_type') {
1770
+ const inner = left.namedChildren.find(n => n.type === 'type_identifier');
1771
+ if (inner?.text !== param) continue;
1772
+ add(left.namedChildren.some(n => n.type === 'mutable_specifier') ? '&mut' : '&', bounds);
1773
+ }
1774
+ }
1775
+ return [...layers].map(([ref, traits]) => ({ ref, traits: [...traits].sort() }))
1776
+ .sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
1777
+ }
1778
+
1163
1779
  function extractImplInfo(implNode) {
1164
1780
  let traitName = null;
1165
1781
  let typeName = null;
@@ -1250,10 +1866,14 @@ function extractEnumVariants(enumNode, codeOrLines) {
1250
1866
  const { startLine, endLine } = nodeToLocation(child, code);
1251
1867
  // Check for tuple/struct variant data
1252
1868
  let params = undefined;
1869
+ // A unit variant names its value, a tuple variant its
1870
+ // constructor (fix #389); a struct variant only a path.
1871
+ let valueShape = 'unit';
1253
1872
  for (let j = 0; j < child.namedChildCount; j++) {
1254
1873
  const variantChild = child.namedChild(j);
1255
1874
  if (variantChild.type === 'field_declaration_list' || variantChild.type === 'ordered_field_declaration_list') {
1256
1875
  params = variantChild.text.slice(1, -1);
1876
+ valueShape = variantChild.type === 'ordered_field_declaration_list' ? 'tuple' : null;
1257
1877
  }
1258
1878
  }
1259
1879
  variants.push({
@@ -1261,7 +1881,8 @@ function extractEnumVariants(enumNode, codeOrLines) {
1261
1881
  startLine,
1262
1882
  endLine,
1263
1883
  memberType: 'variant',
1264
- ...(params !== undefined && { params })
1884
+ ...(params !== undefined && { params }),
1885
+ ...(valueShape && { valueShape }),
1265
1886
  });
1266
1887
  }
1267
1888
  }
@@ -1288,7 +1909,9 @@ function extractTraitMembers(traitNode, codeOrLines) {
1288
1909
  const returnType = extractReturnType(child);
1289
1910
  const iteratorItemType = extractRustIteratorItemType(child);
1290
1911
  const hasSelf = paramsNode && paramsNode.text.includes('self');
1291
- const callbackParamTypes = extractRustCallbackParamTypes(paramsNode);
1912
+ const callbackParamTypes = extractRustCallbackParamTypes(paramsNode, child);
1913
+ const qualifiers = rustFunctionQualifiers(child);
1914
+ const futureReturn = rustFutureReturn(child, qualifiers);
1292
1915
 
1293
1916
  // Rust vocabulary (fix #248): trait members carry the trait's
1294
1917
  // OWN visibility — a method of a private trait is not `pub`,
@@ -1300,7 +1923,9 @@ function extractTraitMembers(traitNode, codeOrLines) {
1300
1923
  endLine,
1301
1924
  memberType: 'method',
1302
1925
  isMethod: true,
1303
- modifiers: traitVisibility ? [traitVisibility] : [],
1926
+ ...(qualifiers.async && { isAsync: true }),
1927
+ modifiers: [...(traitVisibility ? [traitVisibility] : []), ...(qualifiers.async ? ['async'] : [])],
1928
+ ...(futureReturn && { futureReturn }),
1304
1929
  ...(paramsNode && { params: extractRustParams(paramsNode) }),
1305
1930
  ...(paramsNode && { paramsStructured: parseStructuredParams(paramsNode, 'rust') }),
1306
1931
  ...(callbackParamTypes && { callbackParamTypes }),
@@ -1320,12 +1945,112 @@ function extractTraitMembers(traitNode, codeOrLines) {
1320
1945
  * @param {string} code - Source code
1321
1946
  * @param {string} [typeName] - The type this impl is for (e.g., "MyStruct")
1322
1947
  */
1948
+ /**
1949
+ * `Self::Assoc` projections in an impl member's return type, resolved
1950
+ * through the SAME impl block's `type Assoc = T;` items (fix #368):
1951
+ * `type Folder = CollectResult<'c, T>; fn into_folder(self) -> Self::Folder`
1952
+ * declares the concrete `CollectResult<'c, T>`. Returns the substituted
1953
+ * return-type text, or null when nothing (or not everything) resolves.
1954
+ */
1955
+ function rustResolveSelfProjections(functionNode, associatedTypes) {
1956
+ const returnTypeNode = functionNode.childForFieldName('return_type');
1957
+ if (!returnTypeNode || associatedTypes.size === 0) return null;
1958
+ const replacements = [];
1959
+ let unresolved = false;
1960
+ const pending = [returnTypeNode];
1961
+ while (pending.length > 0) {
1962
+ const current = pending.pop();
1963
+ if (current.type.endsWith('comment')) return null;
1964
+ if (current.type === 'scoped_type_identifier' &&
1965
+ current.childForFieldName('path')?.text === 'Self') {
1966
+ const assoc = associatedTypes.get(current.childForFieldName('name')?.text);
1967
+ if (!assoc) unresolved = true;
1968
+ else replacements.push({ start: current.startIndex, end: current.endIndex, text: assoc });
1969
+ continue;
1970
+ }
1971
+ for (let i = 0; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
1972
+ }
1973
+ if (unresolved || replacements.length === 0) return null;
1974
+ replacements.sort((a, b) => b.start - a.start);
1975
+ const base = returnTypeNode.startIndex;
1976
+ let text = returnTypeNode.text;
1977
+ for (const replacement of replacements) {
1978
+ text = text.slice(0, replacement.start - base) + replacement.text +
1979
+ text.slice(replacement.end - base);
1980
+ }
1981
+ return text.trim() || null;
1982
+ }
1983
+
1984
+ /**
1985
+ * Associated types visible as `Self::X` inside an impl: its own items plus
1986
+ * those of sibling impl blocks for the same self type in the same module
1987
+ * scope (supertrait associated types). A name defined with different values
1988
+ * by two sibling impls is ambiguous and dropped.
1989
+ */
1990
+ function rustSiblingAssociatedTypes(implNode, own) {
1991
+ const selfText = implNode.childForFieldName('type')?.text;
1992
+ const merged = new Map(own);
1993
+ const scope = implNode.parent;
1994
+ if (!selfText || !scope) return merged;
1995
+ const conflicting = new Set();
1996
+ for (let i = 0; i < scope.namedChildCount; i++) {
1997
+ const sibling = scope.namedChild(i);
1998
+ if (sibling.type !== 'impl_item' || sibling.id === implNode.id ||
1999
+ sibling.childForFieldName('type')?.text !== selfText) continue;
2000
+ const body = sibling.childForFieldName('body');
2001
+ for (let j = 0; j < (body?.namedChildCount || 0); j++) {
2002
+ const item = body.namedChild(j);
2003
+ if (item.type !== 'type_item') continue;
2004
+ const name = item.childForFieldName('name')?.text;
2005
+ const value = item.childForFieldName('type')?.text;
2006
+ if (!name || !value || own.has(name) || /\/[/*]/.test(value)) continue;
2007
+ if (merged.has(name) && merged.get(name) !== value) conflicting.add(name);
2008
+ else merged.set(name, value);
2009
+ }
2010
+ }
2011
+ for (const name of conflicting) merged.delete(name);
2012
+ return merged;
2013
+ }
2014
+
2015
+ /**
2016
+ * The module path an impl's self type is written under (fix #371):
2017
+ * `impl Ext for std::fs::File` -> 'std::fs', through `&`/`&mut` and generic
2018
+ * arguments. null for an unqualified self type.
2019
+ */
2020
+ function rustImplSelfQualifier(implSelfTypeNode) {
2021
+ let node = implSelfTypeNode;
2022
+ while (node?.type === 'reference_type') node = node.childForFieldName('type');
2023
+ if (node?.type === 'generic_type') node = node.childForFieldName('type');
2024
+ if (node?.type !== 'scoped_type_identifier') return null;
2025
+ return node.childForFieldName('path')?.text || null;
2026
+ }
2027
+
1323
2028
  function extractImplMembers(implNode, codeOrLines, typeName) {
1324
2029
  const code = codeOrLines;
1325
2030
  const members = [];
1326
2031
  const bodyNode = implNode.childForFieldName('body');
1327
2032
  if (!bodyNode) return members;
1328
2033
  const implAttributes = extractAttributes(implNode, codeOrLines);
2034
+ const ownerGenericBounds = extractGenericBounds(implNode);
2035
+ const ownerSelfArgs = extractRustImplSelfArgs(implNode);
2036
+ // Reference impls (`impl Trait for &'a Vec<T>`) share the owner name with
2037
+ // the owned impl; the reference layer decides method probing (fix #368).
2038
+ const implSelfTypeNode = implNode.childForFieldName('type');
2039
+ const implSelfRef = implSelfTypeNode?.type === 'reference_type'
2040
+ ? (implSelfTypeNode.namedChildren.some(child => child.type === 'mutable_specifier')
2041
+ ? '&mut' : '&')
2042
+ : null;
2043
+ const implSelfQualifier = rustImplSelfQualifier(implSelfTypeNode);
2044
+ const associatedTypes = new Map();
2045
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
2046
+ const item = bodyNode.namedChild(i);
2047
+ if (item.type !== 'type_item') continue;
2048
+ const nameNode = item.childForFieldName('name');
2049
+ const valueNode = item.childForFieldName('type');
2050
+ if (nameNode && valueNode && !/\/[/*]/.test(valueNode.text)) {
2051
+ associatedTypes.set(nameNode.text, valueNode.text);
2052
+ }
2053
+ }
1329
2054
 
1330
2055
  for (let i = 0; i < bodyNode.namedChildCount; i++) {
1331
2056
  const child = bodyNode.namedChild(i);
@@ -1337,7 +2062,6 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1337
2062
  if (nameNode) {
1338
2063
  const { startLine, endLine } = nodeToLocation(child, code);
1339
2064
  const text = child.text;
1340
- const firstLine = text.split('\n')[0];
1341
2065
  const returnType = extractReturnType(child);
1342
2066
  const iteratorItemType = extractRustIteratorItemType(child);
1343
2067
  const docstring = extractRustDocstring(code, startLine);
@@ -1354,10 +2078,12 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1354
2078
  // Function qualifiers, same vocabulary as free functions
1355
2079
  // (fix #248: `pub async fn get` rendered as `pub get(...)`;
1356
2080
  // const/unsafe methods had no machine-readable qualifier).
1357
- if (firstLine.includes('async ')) modifiers.push('async');
1358
- if (firstLine.includes('unsafe ')) modifiers.push('unsafe');
1359
- if (firstLine.includes('const fn')) modifiers.push('const');
1360
- if (firstLine.includes('extern ')) modifiers.push('extern');
2081
+ const qualifiers = rustFunctionQualifiers(child);
2082
+ const futureReturn = rustFutureReturn(child, qualifiers);
2083
+ if (qualifiers.async) modifiers.push('async');
2084
+ if (qualifiers.unsafe) modifiers.push('unsafe');
2085
+ if (qualifiers.const) modifiers.push('const');
2086
+ if (qualifiers.extern) modifiers.push('extern');
1361
2087
  for (const attr of attributes) modifiers.push(attr);
1362
2088
  for (const attr of implAttributes) {
1363
2089
  if (!modifiers.includes(attr)) modifiers.push(attr);
@@ -1366,7 +2092,17 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1366
2092
 
1367
2093
  const memberGenerics = extractGenerics(child);
1368
2094
  const genericBounds = extractGenericBounds(child);
1369
- const callbackParamTypes = extractRustCallbackParamTypes(paramsNode);
2095
+ const callbackParamTypes = extractRustCallbackParamTypes(paramsNode, child);
2096
+ let returnTypeResolved = returnType
2097
+ ? rustResolveSelfProjections(child, associatedTypes) : null;
2098
+ if (returnType && !returnTypeResolved && /\bSelf\s*::/.test(returnType)) {
2099
+ // A supertrait's associated type is defined by a sibling
2100
+ // impl block for the same self type (`impl Consumer for C
2101
+ // { type Reducer = R; }` + `impl UnindexedConsumer for C
2102
+ // { fn to_reducer(&self) -> Self::Reducer }`).
2103
+ returnTypeResolved = rustResolveSelfProjections(child,
2104
+ rustSiblingAssociatedTypes(implNode, associatedTypes));
2105
+ }
1370
2106
  members.push({
1371
2107
  name: nameNode.text,
1372
2108
  params: extractRustParams(paramsNode),
@@ -1375,11 +2111,18 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1375
2111
  startLine,
1376
2112
  endLine,
1377
2113
  memberType: 'method',
1378
- isAsync: firstLine.includes('async '),
2114
+ isAsync: qualifiers.async,
1379
2115
  isMethod: hasSelf, // Only true methods (with self) — associated functions are false
1380
2116
  modifiers,
1381
2117
  ...(typeName && { receiver: typeName }), // All impl members get receiver for findMethodsForType
1382
2118
  ...(returnType && { returnType }),
2119
+ ...(returnTypeResolved && { returnTypeResolved }),
2120
+ ...(futureReturn && { futureReturn }),
2121
+ ...(ownerGenericBounds && { ownerGenericBounds }),
2122
+ ...(ownerSelfArgs && { ownerSelfArgs }),
2123
+ ...(implSelfRef && { implSelfRef }),
2124
+ ...(implSelfQualifier && { implSelfQualifier }),
2125
+ ...(rustSelfParamKind(paramsNode) && { selfParamKind: rustSelfParamKind(paramsNode) }),
1383
2126
  ...(iteratorItemType && { iteratorItemType }),
1384
2127
  ...(docstring && { docstring }),
1385
2128
  // Method-level type params (fix #229): generic-param receiver
@@ -1441,10 +2184,80 @@ function parse(code, parser) {
1441
2184
  ...((tree.rootNode.hasError || declaration.macroItemRecovery) && {
1442
2185
  parseRecovery: true,
1443
2186
  }),
2187
+ ...(tree.rootNode.hasError && { parseErrorRegions: parseErrorRegions(tree.rootNode) }),
2188
+ ...(() => {
2189
+ const names = rustAsyncClosureNames(tree);
2190
+ return names.length > 0 ? { asyncClosureNames: names } : {};
2191
+ })(),
1444
2192
  imports: [], exports: [],
1445
2193
  };
1446
2194
  }
1447
2195
 
2196
+ const DECLARATION_NODE_TYPES = [
2197
+ 'function_item', 'foreign_mod_item', 'struct_item', 'union_item', 'enum_item', 'trait_item', 'impl_item',
2198
+ 'mod_item', 'macro_definition', 'type_item', 'const_item', 'static_item',
2199
+ ];
2200
+
2201
+ /**
2202
+ * Declarations of the given subtrees only, extracted exactly as parse()
2203
+ * extracts them (fix #374: the declarations a macro expansion generates,
2204
+ * without walking the rest of the expanded file).
2205
+ */
2206
+ function parseDeclarationsIn(code, roots, { descendants = null } = {}) {
2207
+ const lines = code.split('\n');
2208
+ const functions = [], classes = [], stateObjects = [];
2209
+ const processedFn = new Set(), processedCls = new Set();
2210
+ // `descendants(root, i)`: the root's descendant nodes the caller already
2211
+ // listed (a superset of DECLARATION_NODE_TYPES, in document order).
2212
+ for (let i = 0; i < roots.length; i++) {
2213
+ const root = roots[i];
2214
+ const nodes = DECLARATION_NODE_TYPES.includes(root.type) ? [root] : [];
2215
+ const below = descendants
2216
+ ? descendants(root, i).filter(node => DECLARATION_NODE_TYPES.includes(node.type))
2217
+ : root.descendantsOfType(DECLARATION_NODE_TYPES);
2218
+ for (const node of nodes.concat(below)) {
2219
+ _processFunction(node, functions, processedFn, lines, code);
2220
+ _processClass(node, classes, processedCls, lines, code);
2221
+ _processState(node, stateObjects, lines);
2222
+ }
2223
+ }
2224
+ _postProcessTraitImpls(classes);
2225
+ functions.sort((a, b) => a.startLine - b.startLine);
2226
+ classes.sort((a, b) => a.startLine - b.startLine);
2227
+ stateObjects.sort((a, b) => a.startLine - b.startLine);
2228
+ return { language: 'rust', totalLines: lines.length, functions, classes, stateObjects, imports: [], exports: [] };
2229
+ }
2230
+
2231
+ /**
2232
+ * Local names bound to closures whose body is an async block
2233
+ * (`let f = || async { .. }`, fix #370): calling one creates a future.
2234
+ */
2235
+ function rustAsyncClosureNames(tree) {
2236
+ const names = new Set();
2237
+ // The flat node list the declaration pass built for this tree holds the
2238
+ // same nodes in document order (fix #388: no second native walk).
2239
+ const range = cachedNodeRange(tree.rootNode);
2240
+ let blocks;
2241
+ if (range) {
2242
+ blocks = [];
2243
+ const { nodes, subtreeEnds, index } = range;
2244
+ for (let i = index, end = subtreeEnds[index]; i < end; i++) {
2245
+ if (nodes[i].type === 'async_block') blocks.push(nodes[i]);
2246
+ }
2247
+ } else {
2248
+ blocks = tree.rootNode.descendantsOfType('async_block');
2249
+ }
2250
+ for (const block of blocks) {
2251
+ const closure = block.parent;
2252
+ if (closure?.type !== 'closure_expression' || !sameNode(closure.childForFieldName('body'), block)) continue;
2253
+ const binding = closure.parent;
2254
+ if (binding?.type !== 'let_declaration') continue;
2255
+ const pattern = binding.childForFieldName('pattern');
2256
+ if (pattern?.type === 'identifier') names.add(pattern.text);
2257
+ }
2258
+ return [...names].sort();
2259
+ }
2260
+
1448
2261
  /**
1449
2262
  * Walk a Rust call chain to find its root constructor type.
1450
2263
  *
@@ -1551,25 +2364,260 @@ function extractTypeName(typeNode) {
1551
2364
  if (r) return r;
1552
2365
  }
1553
2366
  }
1554
- if (typeNode.type === 'abstract_type' || typeNode.type === 'dynamic_type') {
1555
- for (let i = 0; i < typeNode.namedChildCount; i++) {
1556
- const r = extractTypeName(typeNode.namedChild(i));
1557
- if (r) return r;
2367
+ if (typeNode.type === 'abstract_type' || typeNode.type === 'dynamic_type') {
2368
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
2369
+ const r = extractTypeName(typeNode.namedChild(i));
2370
+ if (r) return r;
2371
+ }
2372
+ }
2373
+ if (typeNode.type === 'generic_type') {
2374
+ // Box<Filter> -> Filter (or get the outer type)
2375
+ return extractTypeName(typeNode.namedChild(0));
2376
+ }
2377
+ if (typeNode.type === 'scoped_type_identifier') {
2378
+ // module::Type -> Type
2379
+ const nameNode = typeNode.childForFieldName('name');
2380
+ return nameNode?.text || null;
2381
+ }
2382
+ return null;
2383
+ }
2384
+
2385
+
2386
+ // Argument type descriptors (fix #384): impls of one generic trait for one
2387
+ // self type (`impl From<Bytes> for Vec<u8>`, `impl From<BytesMut> for
2388
+ // Vec<u8>`) are selected by argument type, so method and path calls record
2389
+ // what each argument's type is known to be: '<refs><base>' where refs is a
2390
+ // run of '&' / '&mut ' and base a type name the site proves (a declared
2391
+ // parameter or local, a struct literal), '?<start>' for a local bound from
2392
+ // the path call starting at byte <start> (its declared return type decides
2393
+ // at query time), 'str'/'char'/'bool'/a suffixed numeric type for literals,
2394
+ // '#int'/'#float' for unsuffixed numeric literals, '#array' for a byte
2395
+ // string's array, '#slice' for a range-indexed slice; null when unknown.
2396
+ const RUST_NUMERIC_TYPES = ['i8', 'i16', 'i32', 'i64', 'i128', 'isize',
2397
+ 'u8', 'u16', 'u32', 'u64', 'u128', 'usize', 'f32', 'f64'];
2398
+
2399
+ function rustLiteralArgType(node) {
2400
+ switch (node.type) {
2401
+ case 'string_literal':
2402
+ case 'raw_string_literal':
2403
+ return node.text.startsWith('b') ? '&#array:u8' : node.text.startsWith('c') ? null : '&str';
2404
+ case 'char_literal':
2405
+ return node.text.startsWith('b') ? 'u8' : 'char';
2406
+ case 'boolean_literal':
2407
+ return 'bool';
2408
+ case 'integer_literal':
2409
+ case 'float_literal': {
2410
+ const text = node.text.replace(/_/g, '');
2411
+ if (!/^[0-9]/.test(text) || /^0[xob]/i.test(text)) {
2412
+ return node.type === 'integer_literal' ? '#int' : '#float';
2413
+ }
2414
+ const suffix = RUST_NUMERIC_TYPES.find(type => text.endsWith(type) &&
2415
+ /[0-9.]$/.test(text.slice(0, -type.length)));
2416
+ return suffix || (node.type === 'integer_literal' ? '#int' : '#float');
2417
+ }
2418
+ default:
2419
+ return null;
2420
+ }
2421
+ }
2422
+
2423
+ /**
2424
+ * Base of a range index (`&s[..]`, `&v[1..]`): 'str', '#slice:u8' for a
2425
+ * byte string, '#slice' when the element type is unknown.
2426
+ */
2427
+ function rustRangeIndexBase(container, typeOfName) {
2428
+ if (!container) return null;
2429
+ if (container.type === 'string_literal' || container.type === 'raw_string_literal') {
2430
+ return container.text.startsWith('b') ? '#slice:u8' : 'str';
2431
+ }
2432
+ if (container.type !== 'identifier') return null;
2433
+ const known = typeOfName(container.text, container);
2434
+ const base = known ? known.replace(/^(?:&mut |&)+/, '') : null;
2435
+ if (base === 'String' || base === 'str') return 'str';
2436
+ if (base === 'Vec' || base?.startsWith('#slice') || base?.startsWith('#array')) {
2437
+ return base?.includes(':') ? `#slice:${base.split(':')[1]}` : '#slice';
2438
+ }
2439
+ return null;
2440
+ }
2441
+
2442
+ /**
2443
+ * A local's type as an argument descriptor: declared (parameter, annotated
2444
+ * `let`), constructed (struct literal) or literal types with their
2445
+ * reference layer; a local bound from a path call as '?<start>'.
2446
+ */
2447
+ function rustKnownArgNameType(name, atNode, getReceiverType, isPatternShadow, tokenTypes) {
2448
+ // One evidence lookup (it checks pattern shadows itself); a declared or
2449
+ // constructed type carries its name in the evidence.
2450
+ let fields = getReceiverType?.(name, atNode, true);
2451
+ let type = fields?.receiverTypeEvidence?.type;
2452
+ if (!fields && tokenTypes?.has(name) && !isPatternShadow?.(atNode, name)) {
2453
+ type = tokenTypes.get(name);
2454
+ fields = tokenTypes.fields(name, type);
2455
+ }
2456
+ if (!type || !fields) return null;
2457
+ const source = fields.receiverTypeSource;
2458
+ const evidence = fields.receiverTypeEvidence;
2459
+ // A value behind a std wrapper is passed as the wrapper itself (fix #401).
2460
+ if (Array.isArray(evidence?.derefVia) && evidence.derefVia.length > 0) {
2461
+ const wrapper = String(evidence.derefVia[0]).replace(/^=/, '').split('::').pop();
2462
+ const outer = evidence.derefOuterRef || 'owned';
2463
+ return (outer === '&' ? '&' : outer === '&mut' ? '&mut ' : '') + wrapper;
2464
+ }
2465
+ if (source === 'guess') {
2466
+ return evidence?.nodeType === 'call_expression' && Number.isInteger(evidence.start)
2467
+ ? `?${evidence.start}` : null;
2468
+ }
2469
+ if (source !== 'annotation' && source !== 'constructor' && source !== 'literal') return null;
2470
+ const ref = fields.receiverTypeRef;
2471
+ if (!ref) return null;
2472
+ const base = type === 'slice' ? '#slice' : type;
2473
+ return (ref === '&' ? '&' : ref === '&mut' ? '&mut ' : '') + base;
2474
+ }
2475
+
2476
+ function rustArgTypeOfNode(argNode, typeOfName) {
2477
+ let node = argNode;
2478
+ let refs = '';
2479
+ let type = node?.type;
2480
+ while (node) {
2481
+ if (type === 'parenthesized_expression') {
2482
+ node = node.namedChild(0);
2483
+ } else if (type === 'reference_expression') {
2484
+ refs += node.namedChildren.some(child => child.type === 'mutable_specifier') ? '&mut ' : '&';
2485
+ node = node.childForFieldName('value');
2486
+ } else {
2487
+ break;
2488
+ }
2489
+ type = node?.type;
2490
+ }
2491
+ if (!node) return null;
2492
+ if (type === 'identifier') {
2493
+ const known = typeOfName(node.text, node);
2494
+ return known ? refs + known : null;
2495
+ }
2496
+ const literal = rustLiteralArgType(node);
2497
+ if (literal) return refs + literal;
2498
+ if (type === 'identifier') {
2499
+ const known = typeOfName(node.text, node);
2500
+ return known ? refs + known : null;
2501
+ }
2502
+ if (node.type === 'struct_expression') {
2503
+ const nameNode = node.childForFieldName('name');
2504
+ let name = nameNode?.type === 'scoped_type_identifier'
2505
+ ? nameNode.childForFieldName('name')?.text
2506
+ : nameNode?.type === 'type_identifier' ? nameNode.text : null;
2507
+ if (name === 'Self') name = findEnclosingImplType(node) || null;
2508
+ return name ? refs + name : null;
2509
+ }
2510
+ if (node.type === 'index_expression' && node.namedChild(1)?.type === 'range_expression') {
2511
+ const base = rustRangeIndexBase(node.namedChild(0), typeOfName);
2512
+ return base ? refs + base : null;
2513
+ }
2514
+ // A value produced by a macro or a path call (`vec![..]`,
2515
+ // `Bytes::from(v)`): its record at this byte decides at query time.
2516
+ if (node.type === 'macro_invocation' ||
2517
+ (node.type === 'call_expression' && node.childForFieldName('function')?.type === 'scoped_identifier')) {
2518
+ return `${refs}?${node.startIndex}`;
2519
+ }
2520
+ // `x.clone()` of an owned local has the local's type.
2521
+ if (node.type === 'call_expression' && node.childForFieldName('arguments')?.namedChildCount === 0) {
2522
+ const callee = node.childForFieldName('function');
2523
+ const object = callee?.type === 'field_expression' ? callee.childForFieldName('value') : null;
2524
+ if (callee?.childForFieldName('field')?.text === 'clone' && object?.type === 'identifier') {
2525
+ const known = typeOfName(object.text, object);
2526
+ return known && !known.startsWith('&') ? refs + known : null;
2527
+ }
2528
+ }
2529
+ return null;
2530
+ }
2531
+
2532
+ function rustArgTypesOfNode(argNodes, typeOfName) {
2533
+ if (argNodes.length === 0) return null;
2534
+ let types = null;
2535
+ for (let i = 0; i < argNodes.length; i++) {
2536
+ const descriptor = rustArgTypeOfNode(argNodes[i], typeOfName);
2537
+ if (descriptor) {
2538
+ if (!types) types = new Array(argNodes.length).fill(null);
2539
+ types[i] = descriptor;
2540
+ }
2541
+ }
2542
+ return types;
2543
+ }
2544
+
2545
+ /** The same descriptors read from a macro argument token tree. */
2546
+ function rustArgTypesOfTokens(argsTree, typeOfName) {
2547
+ if (!argsTree) return null;
2548
+ const children = argsTree.children;
2549
+ let types = null;
2550
+ let index = 0;
2551
+ let start = 1; // skip `(`
2552
+ for (let i = 1; i <= children.length - 1; i++) {
2553
+ if (i < children.length - 1 && children[i].type !== ',') continue;
2554
+ if (i > start) {
2555
+ const descriptor = rustTokenSegmentType(children, start, i, typeOfName);
2556
+ if (descriptor) {
2557
+ if (!types) types = [];
2558
+ types[index] = descriptor;
2559
+ }
2560
+ }
2561
+ index++;
2562
+ start = i + 1;
2563
+ }
2564
+ if (!types) return null;
2565
+ for (let i = 0; i < index; i++) if (types[i] === undefined) types[i] = null;
2566
+ types.length = Math.min(types.length, index);
2567
+ return types;
2568
+ }
2569
+
2570
+ /** Descriptor of one macro argument: the tokens children[from..to). */
2571
+ function rustTokenSegmentType(children, from, to, typeOfName) {
2572
+ let refs = '';
2573
+ let at = from;
2574
+ while (at < to && (children[at].type === '&' || children[at].type === '&&')) {
2575
+ const layers = children[at].type === '&&' ? 2 : 1;
2576
+ const mutable = at + 1 < to && children[at + 1].type === 'mutable_specifier';
2577
+ refs += '&'.repeat(layers - 1) + (mutable ? '&mut ' : '&');
2578
+ at += mutable ? 2 : 1;
2579
+ }
2580
+ const count = to - at;
2581
+ const first = children[at];
2582
+ if (count === 1) {
2583
+ if (first.type === 'identifier') {
2584
+ const known = typeOfName(first.text, first);
2585
+ return known ? refs + known : null;
2586
+ }
2587
+ const literal = rustLiteralArgType(first);
2588
+ return literal ? refs + literal : null;
2589
+ }
2590
+ if (count === 2 && children[at + 1].type === 'token_tree') {
2591
+ const group = children[at + 1];
2592
+ const text = group.text;
2593
+ if (text.startsWith('[')) {
2594
+ let ranged = false;
2595
+ for (const token of group.children) if (token.type === '..') { ranged = true; break; }
2596
+ if (!ranged) return null;
2597
+ const base = rustRangeIndexBase(first, typeOfName);
2598
+ return base ? refs + base : null;
2599
+ }
2600
+ if (text.startsWith('{') && first.type === 'identifier') {
2601
+ const name = first.text === 'Self' ? findEnclosingImplType(first) : first.text;
2602
+ return name ? refs + name : null;
1558
2603
  }
2604
+ return null;
1559
2605
  }
1560
- if (typeNode.type === 'generic_type') {
1561
- // Box<Filter> -> Filter (or get the outer type)
1562
- return extractTypeName(typeNode.namedChild(0));
2606
+ // `vec![..]` / `name!(..)`: the nested macro's own record.
2607
+ if (count === 3 && first.type === 'identifier' && children[at + 1].type === '!' &&
2608
+ children[at + 2].type === 'token_tree') {
2609
+ return `${refs}?${first.startIndex}`;
1563
2610
  }
1564
- if (typeNode.type === 'scoped_type_identifier') {
1565
- // module::Type -> Type
1566
- const nameNode = typeNode.childForFieldName('name');
1567
- return nameNode?.text || null;
2611
+ // `x.clone()` of an owned local.
2612
+ if (count === 4 && first.type === 'identifier' && children[at + 1].type === '.' &&
2613
+ children[at + 2].text === 'clone' && children[at + 3].type === 'token_tree' &&
2614
+ children[at + 3].text.replace(/\s+/g, '') === '()') {
2615
+ const known = typeOfName(first.text, first);
2616
+ return known && !known.startsWith('&') ? refs + known : null;
1568
2617
  }
1569
2618
  return null;
1570
2619
  }
1571
2620
 
1572
-
1573
2621
  // Shared by ordinary AST calls and macro token-tree calls. Both carry the
1574
2622
  // same self-field owner identity; a field spelling is never a local receiver.
1575
2623
  function findEnclosingImplType(node) {
@@ -1629,6 +2677,9 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1629
2677
  i = close;
1630
2678
  }
1631
2679
  let lastProducer = null;
2680
+ const scopedName = typeof context === 'object' ? context.scopeTypesName : null;
2681
+ const argTypeOfName = (name, atNode) => (!scopedName || scopedName(name) || inheritedTokenTypes.has(name))
2682
+ ? rustKnownArgNameType(name, atNode, getReceiverType, isPatternShadow, inheritedTokenTypes) : null;
1632
2683
  const macroFields = {
1633
2684
  inMacro: true,
1634
2685
  ...(contextKind === 'definition' && { inMacroDefinition: true }),
@@ -1643,6 +2694,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1643
2694
  kind: contextKind,
1644
2695
  ...(containerMacro && { containerMacro }),
1645
2696
  tokenTypes,
2697
+ ...(scopedName && { scopeTypesName: scopedName }),
1646
2698
  });
1647
2699
  continue;
1648
2700
  }
@@ -1725,6 +2777,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1725
2777
  startNode = children[k];
1726
2778
  j = k - 1;
1727
2779
  }
2780
+ const tokenArgTypes = segments.length > 0 ? rustArgTypesOfTokens(callArgs, argTypeOfName) : null;
1728
2781
  const record = {
1729
2782
  name: tok.text,
1730
2783
  line: tok.startPosition.row + 1,
@@ -1733,6 +2786,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1733
2786
  isMethod: segments.length > 0,
1734
2787
  isPathCall: true,
1735
2788
  receiver: segments.length > 0 ? segments.join('::') : undefined,
2789
+ ...(tokenArgTypes && { argTypes: tokenArgTypes }),
1736
2790
  ...macroFields,
1737
2791
  enclosingFunction
1738
2792
  };
@@ -1756,9 +2810,14 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1756
2810
  }
1757
2811
  // Literal receivers type as builtins inside macros too (fix #220,
1758
2812
  // ripgrep-measured: assert_eq!(.., vec!["match:fg".parse()...]))
2813
+ const groupedReceiver = recvTok?.type === 'token_tree' &&
2814
+ rustTokenTreeIsGroupedExpression(children, i - 2);
2815
+ const rangeType = groupedReceiver
2816
+ ? (rustTokenTreeRangeType(recvTok) || rustTokenTreeTupleType(recvTok)) : null;
1759
2817
  const litType = recvTok
1760
- ? ({ string_literal: 'str', raw_string_literal: 'str',
1761
- char_literal: 'char', boolean_literal: 'bool' })[recvTok.type]
2818
+ ? (({ string_literal: 'str', raw_string_literal: 'str',
2819
+ char_literal: 'char', boolean_literal: 'bool' })[recvTok.type] ||
2820
+ rangeType || undefined)
1762
2821
  : undefined;
1763
2822
  const receiverRootType = receiverRoot === 'self'
1764
2823
  ? findEnclosingImplType(tok) : getReceiverType?.(receiverRoot, tok);
@@ -1771,16 +2830,21 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1771
2830
  const patternSource = rustPatternBindingOf(tok, receiver);
1772
2831
  const producer = !receiver && lastProducer &&
1773
2832
  lastProducer.callEnd === recvTok?.endIndex ? lastProducer : null;
2833
+ const tokenArgTypes = rustArgTypesOfTokens(callArgs, argTypeOfName);
1774
2834
  const record = {
1775
2835
  name: tok.text,
1776
2836
  line: tok.startPosition.row + 1,
1777
2837
  callStart: producer?.callStart ?? recvTok?.startIndex ?? tok.startIndex,
1778
2838
  callEnd: callArgs.endIndex,
1779
2839
  isMethod: true,
2840
+ ...(tokenArgTypes && { argTypes: tokenArgTypes }),
1780
2841
  receiver: receiverField ? undefined : receiver,
1781
2842
  ...(receiverField && { receiverRoot, receiverField }),
1782
2843
  ...(receiverRootType && { receiverRootType }),
1783
- ...(receiverType && { receiverType, ...(getReceiverType?.(receiver, tok, true) || inheritedTokenTypes.fields(receiver, receiverType)) }),
2844
+ ...(receiverType && { receiverType, ...((rangeType && receiverType === rangeType && !receiver)
2845
+ ? { receiverTypeSource: 'literal', receiverTypeEvidence: typeOrigin('literal', recvTok),
2846
+ receiverTypeStd: true, receiverTypeRef: 'owned' }
2847
+ : (getReceiverType?.(receiver, tok, true) || inheritedTokenTypes.fields(receiver, receiverType))) }),
1784
2848
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
1785
2849
  ...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
1786
2850
  ...(iterationSource || {}),
@@ -1799,6 +2863,11 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1799
2863
  calls.push(record);
1800
2864
  lastProducer = record;
1801
2865
  } else {
2866
+ // `fn name(...)` inside a macro token tree is a DEFINITION
2867
+ // template (deref-forwarding / impl-generating macros), not a
2868
+ // call (fix #360): recording it as a call listed the definition
2869
+ // line as a caller of every same-name function.
2870
+ if (prev && prev.type === 'fn') continue;
1802
2871
  // Plain call: func(...) — includes enum-variant constructors
1803
2872
  const record = {
1804
2873
  name: tok.text,
@@ -1903,11 +2972,49 @@ function rustAssignmentTargetOf(callNode) {
1903
2972
  const bindings = pattern.namedChildren
1904
2973
  .filter(child => child.type === 'identifier')
1905
2974
  .map(child => child.text);
2975
+ // Positional targets (fix #368): `let (left, right, _) = c.split_at(i)`
2976
+ // binds element i of the declared tuple return to each name.
2977
+ // `_` and non-identifier sub-patterns keep their position but
2978
+ // bind nothing typed.
2979
+ const tupleTargets = [];
2980
+ let position = 0;
2981
+ let positional = true;
2982
+ for (let i = 0; i < pattern.childCount; i++) {
2983
+ const child = pattern.child(i);
2984
+ if (['(', ')', ','].includes(child.type)) continue;
2985
+ if (child.type.endsWith('comment')) continue;
2986
+ if (child.type === 'remaining_field_pattern' || child.text === '..') {
2987
+ positional = false;
2988
+ break;
2989
+ }
2990
+ let binding = child;
2991
+ // `mut x`: the pattern's named children are the
2992
+ // `mutable_specifier` and then the identifier (fix #369).
2993
+ if (binding.type === 'mut_pattern') {
2994
+ binding = binding.namedChildren.find(c => c.type === 'identifier') || null;
2995
+ }
2996
+ if (binding?.type === 'identifier') {
2997
+ tupleTargets.push({ name: binding.text, index: position });
2998
+ }
2999
+ position++;
3000
+ }
1906
3001
  if (bindings.length === pattern.namedChildCount && bindings.length > 0) {
1907
3002
  return {
1908
3003
  assignedTo: bindings[0],
1909
3004
  tuple: true,
1910
3005
  ...(bindings.length > 1 && { tupleRest: bindings.slice(1) }),
3006
+ ...(positional && tupleTargets.length > 0 && { tupleTargets }),
3007
+ ...(unwrapped && { unwrapped: true }),
3008
+ };
3009
+ }
3010
+ if (positional && tupleTargets.length > 0) {
3011
+ return {
3012
+ assignedTo: tupleTargets[0].name,
3013
+ tuple: true,
3014
+ ...(tupleTargets.length > 1 && {
3015
+ tupleRest: tupleTargets.slice(1).map(target => target.name),
3016
+ }),
3017
+ tupleTargets,
1911
3018
  ...(unwrapped && { unwrapped: true }),
1912
3019
  };
1913
3020
  }
@@ -1924,6 +3031,114 @@ function rustAssignmentTargetOf(callNode) {
1924
3031
  return undefined;
1925
3032
  }
1926
3033
 
3034
+ /**
3035
+ * Range expressions have a language-fixed type (fix #368): `a..b` is
3036
+ * `std::ops::Range`, `a..=b` is `RangeInclusive`, `a..` / `..b` / `..=b` /
3037
+ * `..` are RangeFrom / RangeTo / RangeToInclusive / RangeFull. A method call
3038
+ * on a parenthesized range therefore dispatches on that std type exactly
3039
+ * like a string literal dispatches on `str`.
3040
+ */
3041
+ function rustRangeTypeName(operator, hasStart, hasEnd) {
3042
+ if (operator === '..=' || operator === '...') {
3043
+ if (!hasEnd) return null;
3044
+ return hasStart ? 'RangeInclusive' : 'RangeToInclusive';
3045
+ }
3046
+ if (operator !== '..') return null;
3047
+ if (hasStart && hasEnd) return 'Range';
3048
+ if (hasStart) return 'RangeFrom';
3049
+ if (hasEnd) return 'RangeTo';
3050
+ return 'RangeFull';
3051
+ }
3052
+
3053
+ function rustRangeLiteralType(node, allowBare = false) {
3054
+ let current = node;
3055
+ while (current?.type === 'parenthesized_expression' && current.namedChildCount === 1) {
3056
+ current = current.namedChild(0);
3057
+ }
3058
+ // `0..8.len()` parses as a range whose END is the call; only a
3059
+ // parenthesized range is a method receiver. A `let` value may be bare.
3060
+ if (current?.type !== 'range_expression' || (current === node && !allowBare)) return null;
3061
+ let operator = null;
3062
+ let operatorIndex = -1;
3063
+ for (let i = 0; i < current.childCount; i++) {
3064
+ const child = current.child(i);
3065
+ if (!child.isNamed && ['..', '..=', '...'].includes(child.type)) {
3066
+ operator = child.type;
3067
+ operatorIndex = child.startIndex;
3068
+ break;
3069
+ }
3070
+ }
3071
+ if (!operator) return null;
3072
+ let hasStart = false;
3073
+ let hasEnd = false;
3074
+ for (let i = 0; i < current.namedChildCount; i++) {
3075
+ const operand = current.namedChild(i);
3076
+ if (operand.type.endsWith('comment')) continue;
3077
+ if (operand.startIndex < operatorIndex) hasStart = true;
3078
+ else hasEnd = true;
3079
+ }
3080
+ return rustRangeTypeName(operator, hasStart, hasEnd);
3081
+ }
3082
+
3083
+ /**
3084
+ * Token-tree twin of rustRangeLiteralType for macro arguments
3085
+ * (`assert_eq!(4, (0..8).len())`): a parenthesized token tree holding exactly
3086
+ * one top-level range operator and nothing that binds looser than a range
3087
+ * (comma, assignment, closure bars, statement or control keywords).
3088
+ */
3089
+ /**
3090
+ * A parenthesized token tree is a grouped EXPRESSION only when nothing that
3091
+ * makes it an argument list precedes it (`f(0..8)`, `m!(..)`, `x[..](..)`).
3092
+ */
3093
+ const RUST_TOKEN_EXPRESSION_PRECEDERS = new Set(['(', '[', '{', ',', ';', '=', '=>', '&',
3094
+ '&&', '||', '+', '-', '*', '/', '%', '==', '!=', '<', '<=', '>=', 'return', 'in', 'move', '|']);
3095
+ function rustTokenTreeIsGroupedExpression(children, index) {
3096
+ const previous = children[index - 1];
3097
+ return !previous || RUST_TOKEN_EXPRESSION_PRECEDERS.has(previous.type);
3098
+ }
3099
+
3100
+ /**
3101
+ * Tuple expressions have the language's tuple type (fix #368): `(a, b)` and
3102
+ * `(a,)`. The canonical receiver name is 'tuple'.
3103
+ */
3104
+ function rustTokenTreeTupleType(tokenTree) {
3105
+ if (tokenTree?.type !== 'token_tree' || tokenTree.child(0)?.type !== '(' ||
3106
+ tokenTree.child(tokenTree.childCount - 1)?.type !== ')') return null;
3107
+ let commas = 0;
3108
+ let values = 0;
3109
+ for (let i = 1; i < tokenTree.childCount - 1; i++) {
3110
+ const token = tokenTree.child(i);
3111
+ if (token.type.endsWith('comment')) continue;
3112
+ if (['..', '..=', '...', ';', '=', '|', '||'].includes(token.type)) return null;
3113
+ if (token.type === ',') commas++;
3114
+ else values++;
3115
+ }
3116
+ return commas > 0 && values > 0 ? 'tuple' : null;
3117
+ }
3118
+
3119
+ function rustTokenTreeRangeType(tokenTree) {
3120
+ if (tokenTree?.type !== 'token_tree' || tokenTree.child(0)?.type !== '(') return null;
3121
+ const inner = [];
3122
+ for (let i = 1; i < tokenTree.childCount - 1; i++) inner.push(tokenTree.child(i));
3123
+ if (tokenTree.child(tokenTree.childCount - 1)?.type !== ')') return null;
3124
+ const LOOSER = new Set([',', ';', '=', '|', '||', '+=', '-=', '*=', '/=', '%=',
3125
+ '^=', '&=', '|=', '<<=', '>>=', 'return', 'break', 'continue', 'let', 'move']);
3126
+ let operatorIndex = -1;
3127
+ for (let i = 0; i < inner.length; i++) {
3128
+ const token = inner[i];
3129
+ if (LOOSER.has(token.type)) return null;
3130
+ if (['..', '..=', '...'].includes(token.type)) {
3131
+ if (operatorIndex >= 0) return null;
3132
+ operatorIndex = i;
3133
+ }
3134
+ }
3135
+ if (operatorIndex < 0) return null;
3136
+ const isComment = token => token.type.endsWith('comment');
3137
+ const hasStart = inner.slice(0, operatorIndex).some(token => !isComment(token));
3138
+ const hasEnd = inner.slice(operatorIndex + 1).some(token => !isComment(token));
3139
+ return rustRangeTypeName(inner[operatorIndex].type, hasStart, hasEnd);
3140
+ }
3141
+
1927
3142
  function rustCallIdentity(callNode) {
1928
3143
  if (!callNode || callNode.type !== 'call_expression') return null;
1929
3144
  const fn = callNode.childForFieldName('function');
@@ -2118,6 +3333,38 @@ function patternContainsIdentifier(pattern, name) {
2118
3333
  return false;
2119
3334
  }
2120
3335
 
3336
+ /**
3337
+ * Syntactic context a macro invocation's expansion is parsed in: 'items'
3338
+ * (module or impl/trait body), 'stmts' (statement or block tail), 'expr', or
3339
+ * null where no expansion can stand (patterns, types).
3340
+ */
3341
+ function rustMacroInvocationContext(node) {
3342
+ const parent = node.parent;
3343
+ if (!parent) return null;
3344
+ switch (parent.type) {
3345
+ case 'source_file':
3346
+ return 'items';
3347
+ case 'declaration_list':
3348
+ return 'items';
3349
+ case 'expression_statement':
3350
+ case 'block':
3351
+ return 'stmts';
3352
+ default:
3353
+ break;
3354
+ }
3355
+ if (/pattern/.test(parent.type)) return null;
3356
+ for (const field of ['type', 'return_type', 'trait']) {
3357
+ const typed = parent.childForFieldName(field);
3358
+ if (typed && typed.startIndex === node.startIndex && typed.endIndex === node.endIndex) return null;
3359
+ }
3360
+ if (parent.type === 'type_arguments') return null;
3361
+ if (parent.type === 'match_arm') {
3362
+ const value = parent.childForFieldName('value');
3363
+ return value && value.startIndex === node.startIndex && value.endIndex === node.endIndex ? 'expr' : null;
3364
+ }
3365
+ return 'expr';
3366
+ }
3367
+
2121
3368
  function rustMacroCallIdentity(macroNode) {
2122
3369
  if (!macroNode) return null;
2123
3370
  const parts = macroNode.text.replace(/!$/, '').split('::').filter(Boolean);
@@ -2131,9 +3378,400 @@ function rustMacroCallIdentity(macroNode) {
2131
3378
  };
2132
3379
  }
2133
3380
 
2134
- function findCallsInCode(code, parser) {
2135
- const tree = parseTree(parser, code);
3381
+ /**
3382
+ * Is this expression the value the enclosing fn item returns (fix #369)?
3383
+ * The fn body's tail expression, or the operand of a `return` whose nearest
3384
+ * function-like ancestor is that fn item (a closure boundary stops it). The
3385
+ * value then has the fn's declared return type, which lets a trait path call
3386
+ * (`Trait::assoc(..)`) infer its Self from the declaration.
3387
+ */
3388
+ function rustFunctionReturnPosition(node) {
3389
+ const parent = node.parent;
3390
+ if (!parent) return false;
3391
+ if (parent.type === 'return_expression') {
3392
+ for (let p = parent.parent; p; p = p.parent) {
3393
+ if (p.type === 'closure_expression') return false;
3394
+ if (p.type === 'function_item') return true;
3395
+ }
3396
+ return false;
3397
+ }
3398
+ if (parent.type !== 'block' || parent.parent?.type !== 'function_item') return false;
3399
+ const last = parent.namedChild(parent.namedChildCount - 1);
3400
+ return !!last && sameNode(last, node);
3401
+ }
3402
+
3403
+ /**
3404
+ * Does a local value binding shadow a bare callee name at this call (fix
3405
+ * #369)? `let bridge = bridge_impl; join(|c| bridge(c))` calls the local
3406
+ * function value, never the module item `bridge`. Walks the lexical scopes
3407
+ * outward to the enclosing fn item: `let` patterns of earlier statements in
3408
+ * each block, closure / fn parameters, `for` patterns, `if let` / `while let`
3409
+ * conditions and match-arm patterns that enclose the call. Items (`fn`
3410
+ * declared in a block) are not value bindings and do not count.
3411
+ */
3412
+ function rustPatternBindsName(pattern, name) {
3413
+ if (!pattern) return false;
3414
+ const pending = [pattern];
3415
+ while (pending.length > 0) {
3416
+ const current = pending.pop();
3417
+ if (current.type === 'identifier') {
3418
+ if (current.text === name) return true;
3419
+ continue;
3420
+ }
3421
+ // Type annotations, paths and literals inside a pattern bind nothing.
3422
+ if (/type|scoped_identifier|field_identifier|literal/.test(current.type) &&
3423
+ current.type !== 'tuple_struct_pattern') continue;
3424
+ if (current.type === 'tuple_struct_pattern' || current.type === 'struct_pattern') {
3425
+ // The first child is the constructor path, never a binding.
3426
+ for (let i = 1; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
3427
+ continue;
3428
+ }
3429
+ if (current.type === 'field_pattern') {
3430
+ const shorthand = current.namedChildren.find(c => c.type === 'shorthand_field_identifier');
3431
+ if (shorthand?.text === name) return true;
3432
+ const inner = current.childForFieldName('pattern');
3433
+ if (inner) pending.push(inner);
3434
+ continue;
3435
+ }
3436
+ if (current.type === 'shorthand_field_identifier') {
3437
+ if (current.text === name) return true;
3438
+ continue;
3439
+ }
3440
+ for (let i = 0; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
3441
+ }
3442
+ return false;
3443
+ }
3444
+
3445
+ function rustBareNameShadowedByLocal(callNode, name) {
3446
+ let child = callNode;
3447
+ for (let p = callNode.parent; p; child = p, p = p.parent) {
3448
+ if (p.type === 'block') {
3449
+ for (let i = 0; i < p.namedChildCount; i++) {
3450
+ const statement = p.namedChild(i);
3451
+ if (statement.startIndex >= child.startIndex) break;
3452
+ if (statement.type === 'let_declaration' &&
3453
+ rustPatternBindsName(statement.childForFieldName('pattern'), name)) return true;
3454
+ }
3455
+ } else if (p.type === 'closure_expression') {
3456
+ if (rustPatternBindsName(p.childForFieldName('parameters'), name)) return true;
3457
+ } else if (p.type === 'function_item') {
3458
+ const params = p.childForFieldName('parameters');
3459
+ for (const param of params?.namedChildren || []) {
3460
+ if (param.type === 'parameter' &&
3461
+ rustPatternBindsName(param.childForFieldName('pattern'), name)) return true;
3462
+ }
3463
+ return false;
3464
+ } else if (p.type === 'for_expression') {
3465
+ const body = p.childForFieldName('body');
3466
+ if (body && sameNode(body, child) &&
3467
+ rustPatternBindsName(p.childForFieldName('pattern'), name)) return true;
3468
+ } else if (p.type === 'match_arm') {
3469
+ const value = p.childForFieldName('value');
3470
+ if (value && sameNode(value, child) &&
3471
+ rustPatternBindsName(p.childForFieldName('pattern'), name)) return true;
3472
+ } else if (p.type === 'if_expression' || p.type === 'while_expression') {
3473
+ const condition = p.childForFieldName('condition');
3474
+ const consequence = p.childForFieldName('consequence') || p.childForFieldName('body');
3475
+ if (consequence && sameNode(consequence, child) && condition) {
3476
+ const lets = [condition];
3477
+ while (lets.length > 0) {
3478
+ const current = lets.pop();
3479
+ if (current.type === 'let_condition' &&
3480
+ rustPatternBindsName(current.childForFieldName('pattern'), name)) return true;
3481
+ if (current.type === 'let_chain') {
3482
+ for (let i = 0; i < current.namedChildCount; i++) lets.push(current.namedChild(i));
3483
+ }
3484
+ }
3485
+ }
3486
+ }
3487
+ }
3488
+ return false;
3489
+ }
3490
+
3491
+ const rustBindingFactsByTree = new WeakMap();
3492
+ const RUST_BINDING_HOLDERS = new Set(['let_declaration', 'parameter', 'closure_parameters',
3493
+ 'for_expression', 'let_condition', 'match_pattern', 'self_parameter']);
3494
+
3495
+ /**
3496
+ * Is `child` the binding of its holder rather than an expression inside it
3497
+ * (fix #389): the value of `let x = v;`, a parameter's type, a loop's
3498
+ * iterator or a match guard are not bindings.
3499
+ */
3500
+ function rustBindingPosition(parent, child) {
3501
+ const field = {
3502
+ let_declaration: 'pattern', parameter: 'pattern', for_expression: 'pattern',
3503
+ let_condition: 'pattern',
3504
+ }[parent.type];
3505
+ if (field) return sameNode(parent.childForFieldName(field), child);
3506
+ if (parent.type === 'match_pattern') return !sameNode(parent.childForFieldName('condition'), child);
3507
+ return true;
3508
+ }
3509
+
3510
+ /**
3511
+ * How many bindings of each name one function introduces (let patterns,
3512
+ * parameters, closure parameters, loop/match/if-let patterns; shadowing
3513
+ * `let` counts again) and which names are assigned (fix #381).
3514
+ */
3515
+ function rustBindingFacts(fnNode) {
3516
+ let byId = rustBindingFactsByTree.get(fnNode.tree);
3517
+ if (!byId) { byId = new Map(); rustBindingFactsByTree.set(fnNode.tree, byId); }
3518
+ let facts = byId.get(fnNode.id);
3519
+ if (facts) return facts;
3520
+ facts = { declared: new Map(), assigned: new Set() };
3521
+ const record = (child) => {
3522
+ if (child.type === 'identifier' || child.type === 'shorthand_field_identifier') {
3523
+ const parent = child.parent;
3524
+ if (parent && (RUST_BINDING_HOLDERS.has(parent.type) || parent.type.endsWith('pattern')) &&
3525
+ rustBindingPosition(parent, child)) {
3526
+ facts.declared.set(child.text, (facts.declared.get(child.text) || 0) + 1);
3527
+ }
3528
+ } else if (child.type === 'assignment_expression' || child.type === 'compound_assignment_expr') {
3529
+ const left = child.childForFieldName('left');
3530
+ if (left?.type === 'identifier') facts.assigned.add(left.text);
3531
+ }
3532
+ };
3533
+ // Every named descendant, from the flat node list when one is cached
3534
+ // (fix #388); the facts are counts and sets, so visit order is free.
3535
+ const range = cachedNodeRange(fnNode);
3536
+ if (range) {
3537
+ const { nodes, subtreeEnds, index } = range;
3538
+ for (let i = index + 1, end = subtreeEnds[index]; i < end; i++) record(nodes[i]);
3539
+ } else {
3540
+ const walk = (node) => {
3541
+ for (const child of node.namedChildren) {
3542
+ record(child);
3543
+ walk(child);
3544
+ }
3545
+ };
3546
+ walk(fnNode);
3547
+ }
3548
+ byId.set(fnNode.id, facts);
3549
+ return facts;
3550
+ }
3551
+
3552
+ // axum method routers (fix #383): `get(h)`, `post_service(svc)`, `any(h)`,
3553
+ // `on(MethodFilter::GET.or(MethodFilter::POST), h)`, chained with
3554
+ // `.post(h2)` and wrapped by combinators that return the same router.
3555
+ const RUST_METHOD_ROUTER_VERBS = {
3556
+ get: 'GET', post: 'POST', put: 'PUT', delete: 'DELETE', patch: 'PATCH', head: 'HEAD',
3557
+ options: 'OPTIONS', trace: 'TRACE', connect: 'CONNECT', any: 'ALL',
3558
+ };
3559
+ const RUST_METHOD_ROUTER_PASSTHROUGH = new Set(['layer', 'route_layer', 'with_state', 'handle_error']);
3560
+
3561
+ function rustRouteArgs(callNode) {
3562
+ const argsNode = callNode.childForFieldName('arguments');
3563
+ const args = [];
3564
+ for (let i = 0; argsNode && i < argsNode.namedChildCount; i++) {
3565
+ const arg = argsNode.namedChild(i);
3566
+ if (!arg.type.includes('comment') && arg.type !== 'attribute_item') args.push(arg);
3567
+ }
3568
+ return args;
3569
+ }
3570
+
3571
+ function rustHandlerName(node) {
3572
+ if (!node) return '<anonymous>';
3573
+ if (node.type === 'identifier') return node.text;
3574
+ if (node.type === 'scoped_identifier') return node.childForFieldName('name')?.text || node.text;
3575
+ if (node.type === 'generic_function') return rustHandlerName(node.childForFieldName('function'));
3576
+ if (node.type === 'field_expression') return node.childForFieldName('field')?.text || '<anonymous>';
3577
+ if (node.type === 'call_expression') return rustHandlerName(node.childForFieldName('function'));
3578
+ return '<anonymous>';
3579
+ }
3580
+
3581
+ /** Methods a `MethodFilter` expression selects, or null. */
3582
+ function rustMethodFilter(node) {
3583
+ if (!node) return null;
3584
+ if (node.type === 'scoped_identifier') {
3585
+ const name = node.childForFieldName('name')?.text;
3586
+ return name && /^[A-Z]+$/.test(name) ? [name] : null;
3587
+ }
3588
+ if (node.type === 'call_expression') {
3589
+ const fn = node.childForFieldName('function');
3590
+ if (fn?.type === 'field_expression' && fn.childForFieldName('field')?.text === 'or') {
3591
+ const left = rustMethodFilter(fn.childForFieldName('value'));
3592
+ const right = rustMethodFilter(rustRouteArgs(node)[0]);
3593
+ return left && right ? [...left, ...right] : null;
3594
+ }
3595
+ }
3596
+ return null;
3597
+ }
3598
+
3599
+ /** [{ method, handler }] a method-router expression serves, or null. */
3600
+ function rustMethodRouterEntries(node, depth = 0) {
3601
+ if (!node || depth > 32) return null;
3602
+ if (node.type === 'parenthesized_expression') return rustMethodRouterEntries(node.namedChild(0), depth + 1);
3603
+ if (node.type !== 'call_expression') return null;
3604
+ const fn = node.childForFieldName('function');
3605
+ const args = rustRouteArgs(node);
3606
+ const entriesFor = (name) => {
3607
+ const base = name.replace(/_service$/, '');
3608
+ if (RUST_METHOD_ROUTER_VERBS[base]) {
3609
+ return [{ method: RUST_METHOD_ROUTER_VERBS[base], handler: rustHandlerName(args[0]) }];
3610
+ }
3611
+ if (base === 'on') {
3612
+ const methods = rustMethodFilter(args[0]);
3613
+ return methods ? methods.map(method => ({ method, handler: rustHandlerName(args[1]) })) : null;
3614
+ }
3615
+ return null;
3616
+ };
3617
+ if (fn?.type === 'identifier' || fn?.type === 'scoped_identifier') {
3618
+ const name = fn.type === 'identifier' ? fn.text : fn.childForFieldName('name')?.text;
3619
+ return name ? entriesFor(name) : null;
3620
+ }
3621
+ if (fn?.type === 'field_expression') {
3622
+ const inner = rustMethodRouterEntries(fn.childForFieldName('value'), depth + 1);
3623
+ if (!inner) return null;
3624
+ const name = fn.childForFieldName('field')?.text || '';
3625
+ if (RUST_METHOD_ROUTER_PASSTHROUGH.has(name)) return inner;
3626
+ const added = entriesFor(name);
3627
+ return added ? [...inner, ...added] : null;
3628
+ }
3629
+ return null;
3630
+ }
3631
+
3632
+ /** axum `.route(path, <method router>)` / `.route_service(path, svc)`. */
3633
+ function rustMethodRouterOf(callNode, name) {
3634
+ const args = rustRouteArgs(callNode);
3635
+ if (args.length !== 2) return null;
3636
+ if (name === 'route_service') return [{ method: 'ALL', handler: rustHandlerName(args[1]) }];
3637
+ const entries = rustMethodRouterEntries(args[1]);
3638
+ if (!entries || entries.length === 0) return null;
3639
+ const seen = new Set();
3640
+ return entries.filter(entry => {
3641
+ const key = `${entry.method}\0${entry.handler}`;
3642
+ if (seen.has(key)) return false;
3643
+ seen.add(key);
3644
+ return true;
3645
+ });
3646
+ }
3647
+
3648
+ /**
3649
+ * Is a bare identifier in expression position an item path rather than a
3650
+ * local (fix #389)? A name no binding of the enclosing fn declares (let
3651
+ * patterns, parameters, closure parameters, match/for/if-let patterns) is
3652
+ * resolved in the item namespaces: a unit struct, a const, a variant... The
3653
+ * per-fn binding facts are a superset, so any binding of the name anywhere
3654
+ * in the fn keeps it a possible local.
3655
+ */
3656
+ function rustIdentifierIsItemPath(identNode) {
3657
+ let fnNode = null;
3658
+ for (let parent = identNode.parent; parent; parent = parent.parent) {
3659
+ if (parent.type === 'function_item') { fnNode = parent; break; }
3660
+ if (parent.type === 'token_tree' || parent.type === 'macro_invocation') return false;
3661
+ }
3662
+ if (!fnNode) return !rustBareNameShadowedByLocal(identNode, identNode.text);
3663
+ return !rustBindingFacts(fnNode).declared.has(identNode.text);
3664
+ }
3665
+
3666
+ /**
3667
+ * The item path a value receiver names (fix #389): a bare identifier that is
3668
+ * no local (`S.plain()`), a path (`E::Unit.m()`, `m::S.m()`), a struct
3669
+ * expression (`P { x }.m()`), or a once-bound immutable local holding one of
3670
+ * those (`let w = S; w.m()`). Returns { path, kind } or null.
3671
+ */
3672
+ function rustReceiverValuePath(valueNode) {
3673
+ if (!valueNode) return null;
3674
+ if (valueNode.type === 'scoped_identifier') {
3675
+ return /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)+$/.test(valueNode.text)
3676
+ ? { path: valueNode.text, kind: 'value' } : null;
3677
+ }
3678
+ if (valueNode.type === 'struct_expression') {
3679
+ let nameNode = valueNode.childForFieldName('name');
3680
+ if (nameNode?.type === 'generic_type' || nameNode?.type === 'generic_type_with_turbofish') {
3681
+ nameNode = nameNode.childForFieldName('type');
3682
+ }
3683
+ const text = nameNode?.text?.replace(/\s+/g, '');
3684
+ return text && /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*$/.test(text)
3685
+ ? { path: text, kind: 'struct' } : null;
3686
+ }
3687
+ if (valueNode.type !== 'identifier') return null;
3688
+ if (rustIdentifierIsItemPath(valueNode)) return { path: valueNode.text, kind: 'value' };
3689
+ // `let w = S;` (bound once, never assigned, not `mut`): w holds S.
3690
+ let fnNode = null;
3691
+ for (let parent = valueNode.parent; parent; parent = parent.parent) {
3692
+ if (parent.type === 'function_item') { fnNode = parent; break; }
3693
+ }
3694
+ if (!fnNode) return null;
3695
+ const name = valueNode.text;
3696
+ const facts = rustBindingFacts(fnNode);
3697
+ if (facts.declared.get(name) !== 1 || facts.assigned.has(name)) return null;
3698
+ for (let block = valueNode.parent; block && block.id !== fnNode.id; block = block.parent) {
3699
+ if (block.type !== 'block') continue;
3700
+ for (const stmt of block.namedChildren) {
3701
+ if (stmt.startIndex >= valueNode.startIndex) break;
3702
+ if (stmt.type !== 'let_declaration') continue;
3703
+ const pattern = stmt.childForFieldName('pattern');
3704
+ if (pattern?.type !== 'identifier' || pattern.text !== name) continue;
3705
+ const value = stmt.childForFieldName('value');
3706
+ if (!value || value.type === 'identifier' && value.text === name) return null;
3707
+ if (value.type === 'identifier' || value.type === 'scoped_identifier' ||
3708
+ value.type === 'struct_expression') {
3709
+ return rustReceiverValuePath(value);
3710
+ }
3711
+ return null;
3712
+ }
3713
+ }
3714
+ return null;
3715
+ }
3716
+
3717
+ /**
3718
+ * The field path an immutable one-hop `let` alias stands for (fix #381):
3719
+ * `let c = &self.config; c.load()` receives like `self.config.load()`. The
3720
+ * name is bound once in the function (no shadowing `let`), not `mut`, and
3721
+ * the path's root is `self` or a once-bound local.
3722
+ */
3723
+ function rustFieldAliasOf(identNode) {
3724
+ let fnNode = null;
3725
+ for (let parent = identNode.parent; parent; parent = parent.parent) {
3726
+ if (parent.type === 'function_item') { fnNode = parent; break; }
3727
+ }
3728
+ if (!fnNode) return null;
3729
+ const name = identNode.text;
3730
+ const facts = rustBindingFacts(fnNode);
3731
+ if (facts.declared.get(name) !== 1 || facts.assigned.has(name)) return null;
3732
+ for (let block = identNode.parent; block && block.id !== fnNode.id; block = block.parent) {
3733
+ if (block.type !== 'block') continue;
3734
+ for (const stmt of block.namedChildren) {
3735
+ if (stmt.startIndex >= identNode.startIndex) break;
3736
+ if (stmt.type !== 'let_declaration') continue;
3737
+ const pattern = stmt.childForFieldName('pattern');
3738
+ if (pattern?.type !== 'identifier' || pattern.text !== name) continue;
3739
+ let value = stmt.childForFieldName('value');
3740
+ if (value?.type === 'reference_expression') value = value.childForFieldName('value');
3741
+ if (value?.type !== 'field_expression') return null;
3742
+ let root = value;
3743
+ while (root?.type === 'field_expression') {
3744
+ if (root.childForFieldName('field')?.type !== 'field_identifier') return null;
3745
+ root = root.childForFieldName('value');
3746
+ }
3747
+ if (root?.type === 'self') return value;
3748
+ if (root?.type === 'identifier' && root.text !== name &&
3749
+ facts.declared.get(root.text) === 1 && !facts.assigned.has(root.text)) return value;
3750
+ return null;
3751
+ }
3752
+ }
3753
+ return null;
3754
+ }
3755
+
3756
+ /**
3757
+ * Call records of Rust code. `options` (fix #405, macro-expansion calls):
3758
+ * `tree` - the already parsed tree of `code` (an expanded file parsed with
3759
+ * included ranges), whose item-recovery facts are `itemRecovery` (null when
3760
+ * none); `skip(node)` - nodes whose subtrees are not read (other units of an
3761
+ * expanded file: every scope the extractor keeps is a function's own, so a
3762
+ * unit's records do not depend on its sibling units).
3763
+ */
3764
+ function findCallsInCode(code, parser, options = {}) {
3765
+ const tree = options.tree || parseTree(parser, code);
3766
+ const skip = typeof options.skip === 'function' ? options.skip : null;
2136
3767
  const calls = [];
3768
+ // Words of every value-binding pattern seen so far in document order
3769
+ // (fix #369). A binding shadows a bare call only if it precedes the call,
3770
+ // so this superset gates the exact ancestor walk below.
3771
+ const bindingWords = new Set();
3772
+ const BINDING_HOLDERS = new Set(['let_declaration', 'parameter', 'closure_parameters',
3773
+ 'for_expression', 'match_arm', 'let_condition']);
3774
+ const mayBeLocallyBound = name => bindingWords.has(name);
2137
3775
  const functionStack = []; // Stack of { name, startLine, endLine }
2138
3776
  // Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
2139
3777
  const scopeTypes = new Map();
@@ -2191,12 +3829,49 @@ function findCallsInCode(code, parser) {
2191
3829
  return null;
2192
3830
  };
2193
3831
 
3832
+ // Index contracts of std containers (fix #359): `items: &Vec<Conv>` makes
3833
+ // `items[i]` a Conv, `m: HashMap<K, Conv>` makes `m[&k]` a Conv, and
3834
+ // `[Conv; N]` / `&[Conv]` index to Conv. Only the declared slot is used;
3835
+ // generic parameters and unknown containers abstain.
3836
+ const RUST_SEQUENCE_CONTAINERS = new Set(['Vec', 'VecDeque']);
3837
+ const RUST_MAP_CONTAINERS = new Set(['HashMap', 'BTreeMap']);
3838
+ const rustIndexElement = (typeNode) => {
3839
+ let current = typeNode;
3840
+ while (current?.type === 'reference_type') {
3841
+ current = current.childForFieldName('type') ||
3842
+ current.namedChildren.find(c => c.type !== 'lifetime' && c.type !== 'mutable_specifier');
3843
+ }
3844
+ if (!current) return null;
3845
+ let element = null;
3846
+ if (current.type === 'array_type') {
3847
+ element = current.childForFieldName('element');
3848
+ } else if (current.type === 'generic_type') {
3849
+ const base = extractTypeName(current.childForFieldName('type') || current.namedChild(0));
3850
+ const argsNode = current.childForFieldName('type_arguments') ||
3851
+ current.namedChildren.find(c => c.type === 'type_arguments');
3852
+ const args = (argsNode?.namedChildren || []).filter(c =>
3853
+ c.type !== 'lifetime' && !c.type.endsWith('comment'));
3854
+ if (RUST_SEQUENCE_CONTAINERS.has(base) && args.length === 1) element = args[0];
3855
+ else if (RUST_MAP_CONTAINERS.has(base) && args.length === 2) element = args[1];
3856
+ }
3857
+ if (!element) return null;
3858
+ const type = extractTypeName(element);
3859
+ if (!type || /^[A-Z][A-Z0-9]?$/.test(type)) return null;
3860
+ const qualifier = extractTypeQualifier(element);
3861
+ return { type, ...(qualifier && { qualifier }) };
3862
+ };
3863
+ const isElementIndex = (indexNode) => indexNode?.type === 'index_expression' &&
3864
+ indexNode.namedChild(1)?.type !== 'range_expression';
3865
+
2194
3866
  // Build type map from function parameters (including self receiver for impl methods)
2195
3867
  const buildScopeTypeMap = (node) => {
2196
3868
  const typeMap = new ReceiverTypeMap();
2197
3869
  typeMap.qualifiers = new Map();
2198
3870
  typeMap.iteratorItems = new Map();
2199
3871
  typeMap.annotationTexts = new Map();
3872
+ typeMap.indexElements = new Map();
3873
+ typeMap.refKinds = new Map();
3874
+ typeMap.stdTypes = new Set();
2200
3875
  typeMap.boundNames = new Set();
2201
3876
  const retainBoundNames = (pattern) => {
2202
3877
  if (!pattern) return;
@@ -2219,16 +3894,33 @@ function findCallsInCode(code, parser) {
2219
3894
  if (param.type === 'parameter') {
2220
3895
  const patternNode = param.childForFieldName('pattern');
2221
3896
  retainBoundNames(patternNode);
2222
- const typeNode = param.childForFieldName('type');
2223
- const typeName = extractTypeName(typeNode);
3897
+ const declaredType = param.childForFieldName('type');
3898
+ // `p: Arc<Store>` receives as its Deref target (fix
3899
+ // #401); the wrapper chain rides on the evidence.
3900
+ const deref = rustDerefTypedName(declaredType);
3901
+ if (deref && !deref.typeName) continue;
3902
+ const typeNode = deref ? deref.inner : declaredType;
3903
+ const sliceType = rustSliceTypeOf(typeNode) || rustRawPointerTypeOf(typeNode);
3904
+ // `other: &Self` is the enclosing impl's self type (fix
3905
+ // #399); in a trait's own methods it is the implementor,
3906
+ // which no annotation names.
3907
+ const written = deref ? deref.typeName : extractTypeName(typeNode);
3908
+ const typeName = (written === 'Self' ? findEnclosingImplType(param) : written) || sliceType;
2224
3909
  const qualifier = extractTypeQualifier(typeNode);
2225
3910
  const iteratorItem = extractRustIteratorItemTypeFromTypeNode(typeNode);
3911
+ const indexElement = patternNode?.type === 'identifier'
3912
+ ? rustIndexElement(typeNode) : null;
3913
+ if (indexElement) typeMap.indexElements.set(patternNode.text, indexElement);
2226
3914
  if (patternNode && typeName) {
2227
3915
  // Pattern can be identifier or _
2228
3916
  const name = patternNode.type === 'identifier' ? patternNode.text : null;
2229
3917
  if (name) {
2230
- typeMap.set(name, typeName, 'annotation', param);
3918
+ typeMap.set(name, typeName, deref
3919
+ ? { ...typeOrigin('annotation', param), derefVia: deref.via, derefOuterRef: deref.outerRef }
3920
+ : 'annotation', param);
2231
3921
  typeMap.annotationTexts.set(name, typeNode.text);
3922
+ typeMap.refKinds.set(name, rustTypeRefKind(typeNode));
3923
+ if ((sliceType && typeName === sliceType) || deref?.std) typeMap.stdTypes.add(name);
2232
3924
  if (qualifier) typeMap.qualifiers.set(name, qualifier);
2233
3925
  if (iteratorItem) typeMap.iteratorItems.set(name, iteratorItem);
2234
3926
  }
@@ -2330,22 +4022,42 @@ function findCallsInCode(code, parser) {
2330
4022
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2331
4023
  const flow = flowEventAt(atNode, varName);
2332
4024
  if (flow?.type) {
2333
- const { until, ...origin } = flow;
4025
+ const { until, letStart, ...origin } = flow;
2334
4026
  return evidence ? { receiverTypeSource: 'flow',
2335
4027
  receiverTypeEvidence: { ...typeOrigin('flow', atNode), ...origin,
2336
4028
  ...(Number.isFinite(until) && { until }) },
2337
4029
  ...(flow.qualifier && { receiverTypeQualifier: flow.qualifier }) } : flow.type;
2338
4030
  }
4031
+ // An untyped `let` in scope hides the outer binding's type.
4032
+ const shadowedBy = flow && flow.letStart != null ? flow : null;
2339
4033
  for (let i = functionStack.length - 1; i >= 0; i--) {
2340
4034
  const typeMap = scopeTypes.get(functionStack[i].startLine);
4035
+ if (typeMap?.has(varName) && shadowedBy) {
4036
+ const start = typeMap.origins.get(varName)?.start;
4037
+ if (!(start >= shadowedBy.letStart && start <= shadowedBy.at)) return undefined;
4038
+ }
2341
4039
  if (typeMap?.has(varName)) return evidence ? { ...typeMap.fields(varName),
2342
- ...(typeMap.qualifiers?.has(varName) && { receiverTypeQualifier: typeMap.qualifiers.get(varName) }) }
4040
+ ...(typeMap.qualifiers?.has(varName) && { receiverTypeQualifier: typeMap.qualifiers.get(varName) }),
4041
+ ...(typeMap.refKinds?.has(varName) && { receiverTypeRef: typeMap.refKinds.get(varName) }),
4042
+ ...(typeMap.stdTypes?.has(varName) && { receiverTypeStd: true }) }
2343
4043
  : typeMap.get(varName);
2344
4044
  if (typeMap?.boundNames?.has(varName)) return undefined;
2345
4045
  }
2346
4046
  return undefined;
2347
4047
  };
2348
4048
 
4049
+ // Argument type descriptors for trait-impl selection (fix #384). Only
4050
+ // names some enclosing scope types can have one (flow-typed locals are
4051
+ // not argument evidence), so untyped names skip the positional lookup.
4052
+ const scopeTypesName = (name) => {
4053
+ for (let i = functionStack.length - 1; i >= 0; i--) {
4054
+ if (scopeTypes.get(functionStack[i].startLine)?.has(name)) return true;
4055
+ }
4056
+ return false;
4057
+ };
4058
+ const astArgTypeOfName = (name, atNode) => scopeTypesName(name)
4059
+ ? rustKnownArgNameType(name, atNode, getReceiverType, patternShadowsAt, null) : null;
4060
+
2349
4061
  const getReceiverTypeQualifier = (varName, atNode) => {
2350
4062
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2351
4063
  const flow = flowEventAt(atNode, varName);
@@ -2360,6 +4072,97 @@ function findCallsInCode(code, parser) {
2360
4072
  return undefined;
2361
4073
  };
2362
4074
 
4075
+ // A std deref wrapper constructed around a typed value (fix #401):
4076
+ // `ManuallyDrop::new(self)`, `Box::new(Store::new())`, `Arc::new(x)`,
4077
+ // `Rc::clone(&rc)`, `Box::pin(f)`, `Pin::new(&mut x)`,
4078
+ // `AssertUnwindSafe(x)`. Returns { via, typeName, qualifier, source }
4079
+ // (typeName null when the wrapped value's type is unknown), or null
4080
+ // when the value is no such construction.
4081
+ const rustStdDerefConstruction = (valueNode, atNode, depth = 0) => {
4082
+ if (depth > 4 || valueNode?.type !== 'call_expression') return null;
4083
+ let fn = valueNode.childForFieldName('function');
4084
+ if (fn?.type === 'generic_function') fn = fn.childForFieldName('function');
4085
+ let wrapper;
4086
+ let method = null;
4087
+ if (fn?.type === 'identifier' && fn.text === 'AssertUnwindSafe') {
4088
+ wrapper = 'AssertUnwindSafe';
4089
+ } else if (fn?.type === 'scoped_identifier') {
4090
+ const pathNode = fn.childForFieldName('path');
4091
+ method = fn.childForFieldName('name')?.text;
4092
+ const head = pathNode?.type === 'generic_type'
4093
+ ? pathNode.childForFieldName('type') || pathNode.namedChild(0) : pathNode;
4094
+ const headName = head?.type === 'identifier' || head?.type === 'type_identifier' ? head.text
4095
+ : head?.type === 'scoped_identifier' || head?.type === 'scoped_type_identifier'
4096
+ ? head.childForFieldName('name')?.text : null;
4097
+ if (!headName || !RUST_STD_DEREF_WRAPPERS.has(headName)) return null;
4098
+ const constructs = method === 'new' && ['Box', 'Rc', 'Arc', 'ManuallyDrop', 'Pin'].includes(headName);
4099
+ const pins = method === 'pin' && ['Box', 'Rc', 'Arc'].includes(headName);
4100
+ const clones = method === 'clone' && ['Rc', 'Arc'].includes(headName);
4101
+ if (!constructs && !pins && !clones) return null;
4102
+ wrapper = head.text.replace(/\s+/g, '');
4103
+ } else {
4104
+ return null;
4105
+ }
4106
+ const argsNode = valueNode.childForFieldName('arguments');
4107
+ const args = (argsNode?.namedChildren || []).filter(child => !child.type.endsWith('comment'));
4108
+ if (args.length !== 1) return { via: [wrapper], typeName: null };
4109
+ // `Box::pin(x)` makes a `Pin<Box<_>>`: Pin is implied, not written.
4110
+ const outer = method === 'pin' ? ['=Pin', wrapper] : [wrapper];
4111
+ let arg = args[0];
4112
+ let derefArg = false;
4113
+ while (arg?.type === 'reference_expression' || arg?.type === 'parenthesized_expression') {
4114
+ if (arg.type === 'reference_expression') derefArg = true;
4115
+ arg = arg.childForFieldName('value') || arg.namedChild(arg.namedChildCount - 1);
4116
+ }
4117
+ // `Pin::new(&mut x)` and `Rc::clone(&rc)` take a reference: the
4118
+ // result derefs to what the referent derefs to.
4119
+ if ((wrapper.endsWith('Pin') || method === 'clone') && !derefArg) return { via: outer, typeName: null };
4120
+ if (arg?.type === 'self') {
4121
+ const typeName = findEnclosingImplType(arg);
4122
+ return { via: outer, typeName: typeName || null, source: 'constructor' };
4123
+ }
4124
+ if (arg?.type === 'identifier') {
4125
+ const typed = getReceiverType(arg.text, atNode, true);
4126
+ const typeName = getReceiverType(arg.text, atNode);
4127
+ if (!typeName) return { via: outer, typeName: null };
4128
+ const inner = typed?.receiverTypeEvidence?.derefVia || [];
4129
+ // `Rc::clone(&rc)` is the same Rc: its own chain, not one more layer.
4130
+ const via = method === 'clone' ? inner : [...outer, ...inner];
4131
+ if (via.length === 0) return { via: outer, typeName: null };
4132
+ const source = typed?.receiverTypeSource === 'guess' ? 'guess' : 'constructor';
4133
+ const qualifier = getReceiverTypeQualifier(arg.text, atNode);
4134
+ return { via, typeName, ...(qualifier && { qualifier }), source };
4135
+ }
4136
+ if (method === 'clone') return { via: outer, typeName: null };
4137
+ if (arg?.type === 'struct_expression') {
4138
+ const pathText = arg.childForFieldName('name')?.text || '';
4139
+ const segments = pathText.split('::');
4140
+ const typeName = segments.pop();
4141
+ return { via: outer, typeName: /^[A-Z]/.test(typeName || '') ? typeName : null,
4142
+ ...(segments.length > 0 && { qualifier: segments.join('::') }), source: 'constructor' };
4143
+ }
4144
+ if (arg?.type === 'call_expression') {
4145
+ const nested = rustStdDerefConstruction(arg, atNode, depth + 1);
4146
+ if (nested) return { ...nested, via: [...outer, ...nested.via] };
4147
+ const callee = arg.childForFieldName('function');
4148
+ if (callee?.type === 'scoped_identifier') {
4149
+ const segments = callee.text.split('::');
4150
+ const ctor = segments[segments.length - 1];
4151
+ const typeName = segments[segments.length - 2];
4152
+ if (segments.length >= 2 && /^(new|from|default|with_|create|build|open|connect|init)/.test(ctor) &&
4153
+ /^[A-Z]/.test(typeName || '') && !RUST_STD_DEREF_WRAPPERS.has(typeName)) {
4154
+ // The prelude's `Vec`/`String` constructors make that
4155
+ // std type (a project namesake is checked at query time).
4156
+ const prelude = segments.length === 2 && (typeName === 'Vec' || typeName === 'String');
4157
+ return { via: outer, typeName,
4158
+ ...(segments.length > 2 && { qualifier: segments.slice(0, -2).join('::') }),
4159
+ source: prelude ? 'literal' : 'guess', ...(prelude && { std: true }) };
4160
+ }
4161
+ }
4162
+ }
4163
+ return { via: outer, typeName: null };
4164
+ };
4165
+
2363
4166
  const getReceiverIteratorItemType = (varName, atNode) => {
2364
4167
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2365
4168
  for (let i = functionStack.length - 1; i >= 0; i--) {
@@ -2372,6 +4175,29 @@ function findCallsInCode(code, parser) {
2372
4175
  return undefined;
2373
4176
  };
2374
4177
 
4178
+ const getReceiverIndexElement = (varName, atNode) => {
4179
+ if (atNode && patternShadowsAt(atNode, varName)) return undefined;
4180
+ if (flowEventAt(atNode, varName)) return undefined;
4181
+ for (let i = functionStack.length - 1; i >= 0; i--) {
4182
+ const typeMap = scopeTypes.get(functionStack[i].startLine);
4183
+ if (typeMap?.indexElements?.has(varName)) return typeMap.indexElements.get(varName);
4184
+ if (typeMap?.has(varName) || typeMap?.boundNames?.has(varName)) return undefined;
4185
+ }
4186
+ return undefined;
4187
+ };
4188
+
4189
+ // Reference layer of a variable's declared type (fix #368): 'owned',
4190
+ // '&' or '&mut'. Unknown after a flow rebinding or for untyped names.
4191
+ const getReceiverRefKind = (varName, atNode) => {
4192
+ if (atNode && patternShadowsAt(atNode, varName)) return undefined;
4193
+ for (let i = functionStack.length - 1; i >= 0; i--) {
4194
+ const typeMap = scopeTypes.get(functionStack[i].startLine);
4195
+ if (typeMap?.has(varName)) return typeMap.refKinds?.get(varName);
4196
+ if (typeMap?.boundNames?.has(varName)) return undefined;
4197
+ }
4198
+ return undefined;
4199
+ };
4200
+
2375
4201
  const getReceiverAnnotationText = (varName, atNode) => {
2376
4202
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2377
4203
  for (let i = functionStack.length - 1; i >= 0; i--) {
@@ -2509,12 +4335,33 @@ function findCallsInCode(code, parser) {
2509
4335
  const parameterNames = [];
2510
4336
  let parametersComplete = true;
2511
4337
  if (parameters) {
2512
- for (let i = 0; i < parameters.namedChildCount; i++) {
2513
- const parameter = parameters.namedChild(i);
4338
+ // Positions are the callback's argument slots (fix #368): the
4339
+ // wildcard `_` is an anonymous token but still occupies a slot,
4340
+ // so `|_, ctx|` binds ctx to slot 1, not slot 0. Unbound slots
4341
+ // are recorded as null.
4342
+ for (let i = 0; i < parameters.childCount; i++) {
4343
+ const parameter = parameters.child(i);
4344
+ if (parameter.type === '|' || parameter.type === ',' ||
4345
+ parameter.type.endsWith('comment')) continue;
4346
+ if (!parameter.isNamed) {
4347
+ if (parameter.type === '_') {
4348
+ parameterNames.push(null);
4349
+ continue;
4350
+ }
4351
+ parametersComplete = false;
4352
+ break;
4353
+ }
2514
4354
  if (parameter.type === 'identifier') {
2515
4355
  parameterNames.push(parameter.text);
2516
4356
  continue;
2517
4357
  }
4358
+ if (parameter.type === 'parameter') {
4359
+ const pattern = parameter.childForFieldName('pattern');
4360
+ if (pattern?.type === 'identifier') {
4361
+ parameterNames.push(pattern.text);
4362
+ continue;
4363
+ }
4364
+ }
2518
4365
  // `|ref a, ref b|` and `|mut value|` bind the same callback
2519
4366
  // parameter as their identifier child. Destructuring patterns
2520
4367
  // intentionally abstain: a tuple member is not the callback's
@@ -2542,7 +4389,7 @@ function findCallsInCode(code, parser) {
2542
4389
  break;
2543
4390
  }
2544
4391
  }
2545
- if (!parametersComplete || parameterNames.length === 0) return null;
4392
+ if (!parametersComplete || !parameterNames.some(Boolean)) return null;
2546
4393
  return {
2547
4394
  closureSourceCall: callName,
2548
4395
  closureSourceCallStart: outerCall.startIndex,
@@ -2553,7 +4400,21 @@ function findCallsInCode(code, parser) {
2553
4400
  };
2554
4401
  };
2555
4402
 
2556
- traverseTree(tree.rootNode, (node) => {
4403
+ // Item bodies of item-position macros (`quickcheck! { fn p(..) { .. } }`,
4404
+ // fix #401) are parsed as items: their calls are read from that parse,
4405
+ // with their functions' scopes and local types, instead of as tokens.
4406
+ const itemRecovery = options.tree ? (options.itemRecovery || null) : rustMacroItemCallRecovery(code, parser);
4407
+ const recoveredTokenTrees = new Set((itemRecovery?.ranges || []).map(([start]) => start - 1));
4408
+
4409
+ const visitNode = (node) => {
4410
+ if (BINDING_HOLDERS.has(node.type)) {
4411
+ const pattern = node.type === 'closure_parameters' ? node : node.childForFieldName('pattern');
4412
+ if (pattern) {
4413
+ for (const word of pattern.text.split(/[^A-Za-z0-9_]+/)) {
4414
+ if (word) bindingWords.add(word);
4415
+ }
4416
+ }
4417
+ }
2557
4418
  // Track function entry
2558
4419
  if (isFunctionNode(node)) {
2559
4420
  const entry = {
@@ -2591,6 +4452,9 @@ function findCallsInCode(code, parser) {
2591
4452
  byName.get(pattern.text).push({
2592
4453
  at: node.endIndex,
2593
4454
  until,
4455
+ // A `let` shadows every earlier binding of the name
4456
+ // (fix #401); only a type this statement sets survives.
4457
+ ...(node.type === 'let_declaration' && { letStart: node.startIndex }),
2594
4458
  ...(() => {
2595
4459
  // Rust assignment preserves a variable's static
2596
4460
  // type; a fresh `let` may shadow it with another.
@@ -2618,6 +4482,9 @@ function findCallsInCode(code, parser) {
2618
4482
  }
2619
4483
 
2620
4484
  const enclosingFunction = getCurrentEnclosingFunction();
4485
+ // Where the value goes (fix #371/#372): audit-async reads only
4486
+ // records whose value can be lost or used as a resolved value.
4487
+ const { valueConsumed: consumedValue, consumingMethod } = rustValueFacts(node);
2621
4488
 
2622
4489
  // Assignment target for return-type flow (fix #207): let args =
2623
4490
  // parse_low_raw(...)? lets findCallers type args from the
@@ -2628,13 +4495,15 @@ function findCallsInCode(code, parser) {
2628
4495
  // UFCS `Type::method(&x, ...)` counts the explicit self — the
2629
4496
  // pruning range accounts for the shift).
2630
4497
  const argsNode = node.childForFieldName('arguments');
2631
- let argCount = 0;
4498
+ const argNodes = [];
2632
4499
  if (argsNode) {
2633
4500
  for (let i = 0; i < argsNode.namedChildCount; i++) {
2634
- if (argsNode.namedChild(i).type.endsWith('comment')) continue;
2635
- argCount++;
4501
+ const arg = argsNode.namedChild(i);
4502
+ if (arg.type.endsWith('comment')) continue;
4503
+ argNodes.push(arg);
2636
4504
  }
2637
4505
  }
4506
+ const argCount = argNodes.length;
2638
4507
 
2639
4508
  if (funcNode.type === 'identifier') {
2640
4509
  // Direct call: foo()
@@ -2644,19 +4513,30 @@ function findCallsInCode(code, parser) {
2644
4513
  line: node.startPosition.row + 1,
2645
4514
  callStart: node.startIndex,
2646
4515
  callEnd: node.endIndex,
4516
+ ...(consumedValue && { valueConsumed: consumedValue }),
4517
+ ...(consumingMethod && { consumingMethod }),
2647
4518
  isMethod: false,
2648
4519
  argCount,
2649
4520
  ...(assigned && { assignedTo: assigned.assignedTo }),
2650
4521
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
2651
4522
  ...(assigned?.tuple && { assignedTuple: true }),
2652
4523
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4524
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
2653
4525
  enclosingFunction,
2654
- ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
4526
+ ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4527
+ ...(mayBeLocallyBound(funcNode.text) &&
4528
+ rustBareNameShadowedByLocal(node, funcNode.text) && { localShadow: true }),
2655
4529
  });
2656
4530
  } else if (funcNode.type === 'field_expression') {
2657
4531
  // Method call: obj.method()
2658
4532
  const fieldNode = funcNode.childForFieldName('field');
2659
- const valueNode = funcNode.childForFieldName('value');
4533
+ let valueNode = funcNode.childForFieldName('value');
4534
+ // fix #381: `c.load()` after `let c = &self.config;`
4535
+ // receives like `self.config.load()`.
4536
+ if (valueNode?.type === 'identifier' && !getReceiverType(valueNode.text, node)) {
4537
+ const aliased = rustFieldAliasOf(valueNode);
4538
+ if (aliased) valueNode = aliased;
4539
+ }
2660
4540
 
2661
4541
  if (fieldNode) {
2662
4542
  let receiver = (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined;
@@ -2698,6 +4578,7 @@ function findCallsInCode(code, parser) {
2698
4578
  // let findCallers hop to the field's declared type cross-file.
2699
4579
  let receiverRoot, receiverField, receiverFields, receiverRootType;
2700
4580
  let receiverFieldCallRoot;
4581
+ let receiverViaClone = false;
2701
4582
  if (!receiver) {
2702
4583
  let obj = valueNode;
2703
4584
  while (obj?.type === 'call_expression') {
@@ -2739,6 +4620,19 @@ function findCallsInCode(code, parser) {
2739
4620
  (obj.type === 'identifier' || obj.type === 'self')) {
2740
4621
  // x.clone().m() — the receiver is effectively x
2741
4622
  receiver = obj.text;
4623
+ receiverViaClone = true;
4624
+ }
4625
+ } else if (valueNode?.type === 'identifier' && !getReceiverType(receiver, node)) {
4626
+ // fix #392: a local destructured from `self`
4627
+ // (`let Self { iter, .. } = self;`) holds that field's
4628
+ // value: the receiver is the field, like `self.iter`.
4629
+ const field = rustSelfFieldBinding(valueNode);
4630
+ if (field) {
4631
+ receiverRoot = 'self';
4632
+ receiverFields = [field];
4633
+ receiverField = field;
4634
+ receiverRootType = findEnclosingImplType(node);
4635
+ receiver = undefined;
2742
4636
  }
2743
4637
  }
2744
4638
  // Chained receiver (fix #220): the receiver IS a call —
@@ -2825,20 +4719,46 @@ function findCallsInCode(code, parser) {
2825
4719
  // ripgrep-measured): "match:fg:magenta".parse() is
2826
4720
  // str::parse, never a project method. Numeric literals
2827
4721
  // stay untyped (i32/u64/f64 ambiguity).
4722
+ // An array literal (fix #401) is the primitive array
4723
+ // type 'array': it unsizes to a slice, never derefs.
4724
+ const rangeReceiverType = (!receiver && valueNode)
4725
+ ? (rustRangeLiteralType(valueNode) ||
4726
+ (valueNode.type === 'tuple_expression' ? 'tuple' : null) ||
4727
+ (valueNode.type === 'array_expression' ? 'array' : null)) : null;
4728
+ // A cast receiver (fix #399): `(p as *const T).m()` has
4729
+ // exactly the cast's type (a primitive or raw pointer).
4730
+ let castNode = !receiver ? valueNode : null;
4731
+ while (castNode?.type === 'parenthesized_expression') castNode = castNode.namedChild(0);
4732
+ const castTypeNode = castNode?.type === 'type_cast_expression'
4733
+ ? castNode.childForFieldName('type') : null;
4734
+ const castReceiverType = castTypeNode
4735
+ ? (rustRawPointerTypeOf(castTypeNode) ||
4736
+ (castTypeNode.type === 'primitive_type' ? castTypeNode.text : null))
4737
+ : null;
2828
4738
  const literalReceiverType = (!receiver && valueNode)
2829
- ? ({ string_literal: 'str', raw_string_literal: 'str',
2830
- char_literal: 'char', boolean_literal: 'bool' })[valueNode.type]
4739
+ ? (({ string_literal: 'str', raw_string_literal: 'str',
4740
+ char_literal: 'char', boolean_literal: 'bool' })[valueNode.type] ||
4741
+ rangeReceiverType || castReceiverType || undefined)
2831
4742
  : undefined;
4743
+ // Element receiver (fix #359): `items[i].m()` on a
4744
+ // declared std container dispatches on its element.
4745
+ const indexRoot = !receiver && isElementIndex(valueNode)
4746
+ ? valueNode.namedChild(0) : null;
4747
+ const indexElement = indexRoot?.type === 'identifier'
4748
+ ? getReceiverIndexElement(indexRoot.text, node) : undefined;
2832
4749
  const receiverType = (receiver && receiver !== 'self' && !receiverIsChainRoot)
2833
4750
  ? getReceiverType(receiver, node)
2834
- : literalReceiverType;
4751
+ : (literalReceiverType || indexElement?.type);
2835
4752
  const receiverTypeQualifier = receiver && receiverType
2836
4753
  ? getReceiverTypeQualifier(receiver, node)
2837
- : undefined;
4754
+ : indexElement?.qualifier;
2838
4755
  const receiverIteratorItemType = receiver
2839
4756
  ? getReceiverIteratorItemType(receiver, node)
2840
4757
  : undefined;
2841
4758
  const receiverPatternShadow = !!(receiver && patternShadowsAt(node, receiver));
4759
+ const receiverValuePath = !receiverType && !receiverIsChainRoot && !receiverField &&
4760
+ (!receiver || receiver === valueNode?.text) && receiver !== 'self'
4761
+ ? rustReceiverValuePath(valueNode) : null;
2842
4762
  const receiverPatternBinding = rustPatternBindingOf(node, receiver);
2843
4763
  if (receiverPatternBinding?.receiverPatternSourceVariable) {
2844
4764
  const sourceType = getReceiverAnnotationText(
@@ -2850,6 +4770,19 @@ function findCallsInCode(code, parser) {
2850
4770
  const receiverFlowInvalidated = !!(receiver && flowInvalidatedAt(node, receiver));
2851
4771
  const iterationSource = rustIterationSourceOf(node, receiver);
2852
4772
  const firstArg = getFirstStringArg(node);
4773
+ // fix #383: axum `.route(path, get(h).post(h2))` - the
4774
+ // methods and handlers its method router serves.
4775
+ const methodRouter = fieldNode.text === 'route' || fieldNode.text === 'route_service'
4776
+ ? rustMethodRouterOf(node, fieldNode.text) : null;
4777
+ // fix #366: actix `.service(handler)` - the registered
4778
+ // handler function's name (route prefixes compose from the
4779
+ // `web::scope("/api")` chain it is registered on).
4780
+ let serviceArg = null;
4781
+ if (fieldNode.text === 'service') {
4782
+ const a = node.childForFieldName('arguments');
4783
+ const first = a && a.namedChildCount === 1 ? a.namedChild(0) : null;
4784
+ if (first && first.type === 'identifier') serviceArg = first.text;
4785
+ }
2853
4786
  // RUST-2: For chained calls like `a().b().parse::<T>().ok()`,
2854
4787
  // each method should report the line where its OWN identifier
2855
4788
  // appears, not the line where the outer expression begins.
@@ -2860,12 +4793,40 @@ function findCallsInCode(code, parser) {
2860
4793
  line: fieldNode.startPosition.row + 1,
2861
4794
  callStart: node.startIndex,
2862
4795
  callEnd: node.endIndex,
4796
+ ...(consumedValue && { valueConsumed: consumedValue }),
4797
+ ...(consumingMethod && { consumingMethod }),
2863
4798
  isMethod: true,
2864
4799
  receiver,
2865
- ...(receiverType && { receiverType, ...(getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
4800
+ ...(receiverType && { receiverType, ...(getReceiverType(receiver, node, true) ||
4801
+ (indexElement ? { receiverTypeSource: 'annotation',
4802
+ receiverTypeEvidence: typeOrigin('annotation', valueNode) } : null) ||
4803
+ (rangeReceiverType && receiverType === rangeReceiverType
4804
+ ? { receiverTypeSource: 'literal',
4805
+ receiverTypeEvidence: typeOrigin('literal', valueNode),
4806
+ receiverTypeStd: true } : null) ||
4807
+ (castReceiverType && receiverType === castReceiverType
4808
+ ? { receiverTypeSource: 'annotation',
4809
+ receiverTypeEvidence: typeOrigin('annotation', castTypeNode),
4810
+ receiverTypeStd: true } : null) ||
4811
+ { receiverTypeSource: 'unknown' }) }),
2866
4812
  ...(receiverTypeQualifier && { receiverTypeQualifier }),
4813
+ ...(receiverType && (() => {
4814
+ // `x.clone()` and range literals are owned values.
4815
+ const kind = receiverViaClone || (rangeReceiverType && receiverType === rangeReceiverType) ||
4816
+ (castReceiverType && receiverType === castReceiverType)
4817
+ ? 'owned'
4818
+ : (receiver && receiver !== 'self' ? getReceiverRefKind(receiver, node) : undefined);
4819
+ return kind ? { receiverTypeRef: kind } : {};
4820
+ })()),
2867
4821
  ...(receiverIteratorItemType && { receiverIteratorItemType }),
2868
4822
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
4823
+ // A path value receiver: `HAlign::Center.get_offset(..)`
4824
+ // (unit variant or associated const, fix #369), a unit
4825
+ // struct `S.plain()` or struct expression, directly or
4826
+ // through a once-bound local (fix #389); query time
4827
+ // decides what the path names.
4828
+ ...(receiverValuePath && { receiverValuePath: receiverValuePath.path,
4829
+ ...(receiverValuePath.kind !== 'value' && { receiverValueKind: receiverValuePath.kind }) }),
2869
4830
  ...(receiverPatternBinding || {}),
2870
4831
  ...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
2871
4832
  ...(iterationSource || {}),
@@ -2882,16 +4843,23 @@ function findCallsInCode(code, parser) {
2882
4843
  ...(receiverCallStart != null && { receiverCallStart }),
2883
4844
  ...(receiverCallEnd != null && { receiverCallEnd }),
2884
4845
  argCount,
4846
+ ...(() => {
4847
+ const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
4848
+ return argTypes ? { argTypes } : {};
4849
+ })(),
2885
4850
  ...(assigned && { assignedTo: assigned.assignedTo }),
2886
4851
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
2887
4852
  ...(assigned?.tuple && { assignedTuple: true }),
2888
4853
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4854
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
2889
4855
  ...(collectResult && {
2890
4856
  explicitResultType: collectResult.type,
2891
4857
  explicitResultItemType: collectResult.itemType,
2892
4858
  }),
2893
4859
  enclosingFunction,
2894
- ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
4860
+ ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4861
+ ...(serviceArg && { serviceArg }),
4862
+ ...(methodRouter && { methodRouter })
2895
4863
  });
2896
4864
  }
2897
4865
  } else if (funcNode.type === 'scoped_identifier') {
@@ -2911,16 +4879,24 @@ function findCallsInCode(code, parser) {
2911
4879
  line: node.startPosition.row + 1,
2912
4880
  callStart: node.startIndex,
2913
4881
  callEnd: node.endIndex,
4882
+ ...(consumedValue && { valueConsumed: consumedValue }),
4883
+ ...(consumingMethod && { consumingMethod }),
2914
4884
  isMethod: segments.length > 1,
2915
4885
  isPathCall: true, // Distinguishes Type::func()/module::func() from obj.method()
2916
4886
  receiver: recvSegments.length > 0 ? recvSegments.join('::') : undefined,
2917
4887
  argCount,
4888
+ ...(segments.length > 1 && (() => {
4889
+ const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
4890
+ return argTypes ? { argTypes } : {};
4891
+ })()),
2918
4892
  ...(assigned && { assignedTo: assigned.assignedTo }),
2919
4893
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
2920
4894
  ...(assigned?.tuple && { assignedTuple: true }),
2921
4895
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4896
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
2922
4897
  enclosingFunction,
2923
- ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
4898
+ ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4899
+ ...(rustFunctionReturnPosition(node) && { returnPosition: true }),
2924
4900
  });
2925
4901
  }
2926
4902
  return true;
@@ -3004,6 +4980,11 @@ function findCallsInCode(code, parser) {
3004
4980
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
3005
4981
  ...(assigned?.tuple && { assignedTuple: true }),
3006
4982
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4983
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
4984
+ // Expression position (fix #375): lets the macro_rules!
4985
+ // expansion pass skip files whose value-forwarding
4986
+ // invocations all sit where no value is taken.
4987
+ ...(rustMacroInvocationContext(node) === 'expr' && { macroExpr: true }),
3007
4988
  enclosingFunction
3008
4989
  });
3009
4990
  }
@@ -3013,12 +4994,13 @@ function findCallsInCode(code, parser) {
3013
4994
  // call lines on ripgrep — test assertions live in macros).
3014
4995
  for (let i = 0; i < node.childCount; i++) {
3015
4996
  const child = node.child(i);
3016
- if (child.type === 'token_tree') {
4997
+ if (child.type === 'token_tree' && !recoveredTokenTrees.has(child.startIndex)) {
3017
4998
  extractCallsFromTokenTree(
3018
4999
  child, enclosingFunction, calls, getReceiverType,
3019
5000
  patternShadowsAt, flowInvalidatedAt, {
3020
5001
  kind: 'invocation',
3021
5002
  containerMacro: macro?.name,
5003
+ scopeTypesName,
3022
5004
  });
3023
5005
  }
3024
5006
  }
@@ -3043,6 +5025,7 @@ function findCallsInCode(code, parser) {
3043
5025
  patternShadowsAt, flowInvalidatedAt, {
3044
5026
  kind: 'attribute',
3045
5027
  containerMacro: attributeName,
5028
+ scopeTypesName,
3046
5029
  });
3047
5030
  }
3048
5031
  return true;
@@ -3107,6 +5090,19 @@ function findCallsInCode(code, parser) {
3107
5090
  const patternNode = node.childForFieldName('pattern');
3108
5091
  const valueNode = node.childForFieldName('value');
3109
5092
  const typeAnnotation = node.childForFieldName('type');
5093
+ if (patternNode && patternNode.type !== 'identifier') {
5094
+ // A destructuring `let` shadows every name it binds; their
5095
+ // earlier declared reference layers no longer apply.
5096
+ const refKinds = scopeTypes.get(functionStack[functionStack.length - 1].startLine)?.refKinds;
5097
+ if (refKinds) {
5098
+ const pending = [patternNode];
5099
+ while (pending.length > 0) {
5100
+ const current = pending.pop();
5101
+ if (current.type === 'identifier') refKinds.delete(current.text);
5102
+ for (let i = 0; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
5103
+ }
5104
+ }
5105
+ }
3110
5106
  if (patternNode && patternNode.type === 'identifier') {
3111
5107
  const varName = patternNode.text;
3112
5108
  const scopeKey = functionStack[functionStack.length - 1].startLine;
@@ -3114,12 +5110,58 @@ function findCallsInCode(code, parser) {
3114
5110
  if (typeMap) {
3115
5111
  let typeName = null;
3116
5112
  let typeQualifier = null;
5113
+ let stdLiteral = false;
5114
+ // Std deref wrappers (fix #401): `let a: Arc<Store>`,
5115
+ // `let m = ManuallyDrop::new(self)` receive as the target.
5116
+ let derefVia = null;
5117
+ let derefSource = null;
5118
+ let derefInnerRef = 'owned';
5119
+ let derefOuterRef = 'owned';
5120
+ let derefStd = false;
3117
5121
  // Pattern 3: explicit type annotation — let s: Server = ...
3118
5122
  if (typeAnnotation) {
3119
- typeName = extractTypeName(typeAnnotation);
3120
- typeQualifier = extractTypeQualifier(typeAnnotation);
5123
+ const deref = rustDerefTypedName(typeAnnotation);
5124
+ const declared = deref ? deref.inner : typeAnnotation;
5125
+ if (deref) {
5126
+ derefVia = deref.via;
5127
+ derefInnerRef = rustTypeRefKind(declared);
5128
+ derefOuterRef = deref.outerRef;
5129
+ derefStd = !!deref.std;
5130
+ }
5131
+ typeName = deref ? deref.typeName : extractTypeName(declared);
5132
+ if (typeName === 'Self') typeName = findEnclosingImplType(typeAnnotation) || null;
5133
+ if (!typeName && !deref && (rustSliceTypeOf(declared) || rustRawPointerTypeOf(declared))) {
5134
+ typeName = rustSliceTypeOf(declared) || rustRawPointerTypeOf(declared);
5135
+ stdLiteral = true;
5136
+ }
5137
+ typeQualifier = extractTypeQualifier(declared);
5138
+ const indexElement = rustIndexElement(declared);
5139
+ if (indexElement) typeMap.indexElements.set(varName, indexElement);
5140
+ else typeMap.indexElements.delete(varName);
5141
+ } else {
5142
+ typeMap.indexElements.delete(varName);
5143
+ }
5144
+ let indexedValue = valueNode;
5145
+ while (indexedValue && (indexedValue.type === 'reference_expression' ||
5146
+ indexedValue.type === 'parenthesized_expression')) {
5147
+ indexedValue = indexedValue.childForFieldName('value') || indexedValue.namedChild(0);
3121
5148
  }
3122
- if (!typeName && valueNode) {
5149
+ if (!typeName && isElementIndex(indexedValue) &&
5150
+ indexedValue.namedChild(0)?.type === 'identifier') {
5151
+ const element = getReceiverIndexElement(indexedValue.namedChild(0).text, node);
5152
+ if (element) {
5153
+ typeName = element.type;
5154
+ typeQualifier = element.qualifier || null;
5155
+ }
5156
+ }
5157
+ if (!typeName && valueNode && !derefVia) {
5158
+ const rangeType = rustRangeLiteralType(valueNode, true);
5159
+ if (rangeType) {
5160
+ typeName = rangeType;
5161
+ stdLiteral = true;
5162
+ }
5163
+ }
5164
+ if (!typeName && valueNode && !(typeAnnotation && derefVia)) {
3123
5165
  // Pattern 1: struct expression — let s = Server { ... }
3124
5166
  if (valueNode.type === 'struct_expression') {
3125
5167
  const nameNode = valueNode.childForFieldName('name');
@@ -3147,7 +5189,16 @@ function findCallsInCode(code, parser) {
3147
5189
  // Pattern 2: constructor call — let s = Server::new()
3148
5190
  else if (valueNode.type === 'call_expression') {
3149
5191
  const funcNode = valueNode.childForFieldName('function');
3150
- if (funcNode?.type === 'scoped_identifier') {
5192
+ const wrapped = rustStdDerefConstruction(valueNode, node);
5193
+ if (wrapped) {
5194
+ if (wrapped.typeName) {
5195
+ typeName = wrapped.typeName;
5196
+ typeQualifier = wrapped.qualifier || null;
5197
+ derefVia = wrapped.via;
5198
+ derefSource = wrapped.source;
5199
+ derefStd = !!wrapped.std;
5200
+ }
5201
+ } else if (funcNode?.type === 'scoped_identifier') {
3151
5202
  const pathText = funcNode.text;
3152
5203
  const segments = pathText.split('::');
3153
5204
  if (segments.length >= 2) {
@@ -3156,32 +5207,66 @@ function findCallsInCode(code, parser) {
3156
5207
  typeName = segments[segments.length - 2];
3157
5208
  typeQualifier = segments.slice(0, -2).join('::') || null;
3158
5209
  if (!typeName || !/^[A-Z]/.test(typeName)) typeName = null;
5210
+ // `Self::with_capacity(..)` names the impl's
5211
+ // self type (fix #401), never a type `Self`.
5212
+ if (typeName === 'Self' && segments.length === 2) {
5213
+ typeName = findEnclosingImplType(valueNode) || null;
5214
+ }
3159
5215
  }
3160
5216
  }
3161
5217
  }
3162
5218
  }
3163
5219
  }
5220
+ typeMap.refKinds.delete(varName);
5221
+ typeMap.stdTypes.delete(varName);
3164
5222
  if (typeName) {
3165
- typeMap.set(varName, typeName, typeAnnotation ? 'annotation' : valueNode?.type === 'call_expression' ? 'guess' : 'constructor', typeAnnotation || valueNode);
5223
+ const source = stdLiteral && !typeAnnotation ? 'literal'
5224
+ : typeAnnotation || isElementIndex(indexedValue) ? 'annotation'
5225
+ : derefSource || (valueNode?.type === 'call_expression' ? 'guess' : 'constructor');
5226
+ typeMap.set(varName, typeName, derefVia
5227
+ ? { ...typeOrigin(source, typeAnnotation || valueNode), derefVia, derefOuterRef }
5228
+ : source, typeAnnotation || valueNode);
3166
5229
  if (typeQualifier) typeMap.qualifiers.set(varName, typeQualifier);
5230
+ if (derefVia) {
5231
+ // The wrapper's target is a place of the inner type.
5232
+ typeMap.refKinds.set(varName, derefInnerRef);
5233
+ } else if (typeAnnotation) {
5234
+ typeMap.refKinds.set(varName, rustTypeRefKind(typeAnnotation));
5235
+ } else if (valueNode?.type === 'struct_expression' || stdLiteral) {
5236
+ typeMap.refKinds.set(varName, 'owned');
5237
+ }
5238
+ if (stdLiteral || derefStd) typeMap.stdTypes.add(varName);
3167
5239
  }
3168
5240
  }
3169
5241
  }
3170
5242
  }
3171
5243
 
3172
5244
  return true;
3173
- }, {
3174
- onLeave: (node) => {
3175
- if (isFunctionNode(node)) {
3176
- const leaving = functionStack.pop();
3177
- if (leaving) {
3178
- scopeTypes.delete(leaving.startLine);
3179
- scopeFlowEvents.delete(leaving.startLine);
3180
- }
5245
+ };
5246
+ const leaveNode = (node) => {
5247
+ if (isFunctionNode(node)) {
5248
+ const leaving = functionStack.pop();
5249
+ if (leaving) {
5250
+ scopeTypes.delete(leaving.startLine);
5251
+ scopeFlowEvents.delete(leaving.startLine);
3181
5252
  }
3182
5253
  }
3183
- });
5254
+ };
5255
+ traverseTree(tree.rootNode, skip ? node => (skip(node) ? false : visitNode(node)) : visitNode,
5256
+ { onLeave: leaveNode });
5257
+ if (itemRecovery) {
5258
+ const ranges = itemRecovery.ranges;
5259
+ const inside = node => ranges.some(([start, end]) => node.startIndex >= start && node.endIndex <= end);
5260
+ const overlaps = node => ranges.some(([start, end]) => node.startIndex < end && node.endIndex > start);
5261
+ traverseTree(itemRecovery.tree.rootNode, node => {
5262
+ if (skip && skip(node)) return false;
5263
+ if (inside(node)) return visitNode(node);
5264
+ return overlaps(node);
5265
+ }, { onLeave: node => { if (inside(node)) leaveNode(node); } });
5266
+ }
3184
5267
 
5268
+ // A tree handed in is an expanded file: no unexpanded item bodies.
5269
+ if (options.tree) return calls;
3185
5270
  const declaration = declarationTrees(code, parser);
3186
5271
  if (!declaration.macroItemRecovery) return calls;
3187
5272
  const functions = findFunctions(code, parser);
@@ -3307,6 +5392,22 @@ function findImportsInCode(code, parser) {
3307
5392
  return true;
3308
5393
  }
3309
5394
 
5395
+ // `extern crate itertools as it;` binds the crate under a local
5396
+ // name exactly like `use itertools as it;` (fix #369). A plain
5397
+ // `extern crate x;` adds no name beyond the extern prelude.
5398
+ if (node.type === 'extern_crate_declaration') {
5399
+ const nameNode = node.childForFieldName('name');
5400
+ const aliasNode = node.childForFieldName('alias');
5401
+ if (nameNode && aliasNode && aliasNode.text !== nameNode.text) {
5402
+ const local = aliasNode.text;
5403
+ addLeaf(nameNode.text, local, 'use', false, node.startPosition.row + 1,
5404
+ { original: nameNode.text, local });
5405
+ if (!imports.aliases) imports.aliases = [];
5406
+ imports.aliases.push({ original: nameNode.text, local });
5407
+ }
5408
+ return true;
5409
+ }
5410
+
3310
5411
  // mod declarations (external module imports)
3311
5412
  if (node.type === 'mod_item') {
3312
5413
  const line = node.startPosition.row + 1;
@@ -3435,13 +5536,13 @@ function findExportsInCode(code, parser) {
3435
5536
  return true;
3436
5537
  }
3437
5538
 
3438
- // Public structs
3439
- if (node.type === 'struct_item' && hasVisibility(node)) {
5539
+ // Public structs and unions
5540
+ if ((node.type === 'struct_item' || node.type === 'union_item') && hasVisibility(node)) {
3440
5541
  const nameNode = node.childForFieldName('name');
3441
5542
  if (nameNode) {
3442
5543
  append({
3443
5544
  name: nameNode.text,
3444
- type: 'struct',
5545
+ type: node.type === 'union_item' ? 'union' : 'struct',
3445
5546
  line: node.startPosition.row + 1
3446
5547
  });
3447
5548
  }
@@ -3548,20 +5649,22 @@ function _indexInParent(node, parent) {
3548
5649
  return -1;
3549
5650
  }
3550
5651
 
3551
- function findUsagesInCode(code, name, parser, tree) {
5652
+ function findUsagesInCode(code, name, parser, tree, options = {}) {
3552
5653
  tree = tree || parseTree(parser, code);
3553
5654
  const usages = [];
5655
+ // Lexical scope verdicts (fix #392) only for refactoring internals.
5656
+ const scopeMemo = options.lexicalScopes ? new Map() : null;
3554
5657
  // Lazy same-file enum→variants map: built only when a paren-less
3555
5658
  // `Type::name` reference needs the enum-variant check.
3556
5659
  let _enumVariants = null;
3557
5660
  const sameFileEnumVariant = (enumName, variantName) => {
3558
5661
  if (_enumVariants === null) {
3559
5662
  _enumVariants = new Map();
3560
- traverseTreeCached(tree.rootNode, (n) => {
3561
- if (n.type !== 'enum_item') return;
5663
+ // Native type walk: no node list of the whole file (fix #403).
5664
+ for (const n of tree.rootNode.descendantsOfType('enum_item')) {
3562
5665
  const enName = n.childForFieldName('name')?.text;
3563
5666
  const body = n.childForFieldName('body');
3564
- if (!enName || !body) return;
5667
+ if (!enName || !body) continue;
3565
5668
  let set = _enumVariants.get(enName);
3566
5669
  if (!set) { set = new Set(); _enumVariants.set(enName, set); }
3567
5670
  for (let i = 0; i < body.namedChildCount; i++) {
@@ -3571,7 +5674,7 @@ function findUsagesInCode(code, name, parser, tree) {
3571
5674
  if (vn) set.add(vn);
3572
5675
  }
3573
5676
  }
3574
- });
5677
+ }
3575
5678
  }
3576
5679
  return _enumVariants.get(enumName)?.has(variantName) || false;
3577
5680
  };
@@ -3691,8 +5794,8 @@ function findUsagesInCode(code, name, parser, tree) {
3691
5794
  sameNode(parent.childForFieldName('name'), node)) {
3692
5795
  usageType = 'definition';
3693
5796
  }
3694
- // Definition: struct name
3695
- else if (parent.type === 'struct_item' &&
5797
+ // Definition: struct / union name
5798
+ else if ((parent.type === 'struct_item' || parent.type === 'union_item') &&
3696
5799
  sameNode(parent.childForFieldName('name'), node)) {
3697
5800
  usageType = 'definition';
3698
5801
  }
@@ -3771,7 +5874,11 @@ function findUsagesInCode(code, name, parser, tree) {
3771
5874
  if (idx >= 0) {
3772
5875
  // Check no preceding dot (would be method call handled above)
3773
5876
  const prev = idx > 0 ? parent.child(idx - 1) : null;
3774
- if ((!prev || prev.text !== '.') && callArgs) {
5877
+ if (prev?.type === 'fn') {
5878
+ // `fn name(...)` in a token tree is a definition
5879
+ // template, not a call (fix #360).
5880
+ usageType = 'definition';
5881
+ } else if ((!prev || prev.text !== '.') && callArgs) {
3775
5882
  usageType = 'call';
3776
5883
  }
3777
5884
  }
@@ -3802,21 +5909,62 @@ function findUsagesInCode(code, name, parser, tree) {
3802
5909
  }
3803
5910
 
3804
5911
  let inAttribute = false;
5912
+ let attributeNode = null;
3805
5913
  for (let a = parent; a; a = a.parent) {
3806
5914
  if (a.type === 'attribute' || a.type === 'attribute_item') {
3807
5915
  inAttribute = true;
5916
+ attributeNode = a.type === 'attribute' ? a
5917
+ : a.namedChildren.find(child => child.type === 'attribute') || null;
3808
5918
  break;
3809
5919
  }
3810
5920
  if (a.type === 'function_item' || a.type === 'impl_item' ||
3811
5921
  a.type === 'struct_item') break;
3812
5922
  }
3813
- usages.push({ line, column, usageType, ...(inAttribute && { inAttribute: true }) });
5923
+ // Macro namespace (fix #377): a macro invocation's name, an
5924
+ // attribute's path (`#[must_use]`, `#[tokio::main]`) and a derive
5925
+ // list entry (`#[derive(Debug)]`) name macros or built-in
5926
+ // attributes, never a same-named fn, const or local.
5927
+ const macroNamespace = rustMacroNamespaceName(node, parent, attributeNode);
5928
+ // Where a bare reference resolves (fix #392).
5929
+ const scope = scopeMemo && usageType === 'reference' && !macroNamespace
5930
+ ? scopeFields(inAttribute ? 'unknown' : referenceScope(node, 'rust', scopeMemo)) : null;
5931
+ // `S { name }` names the field too: a rename keeps the key (fix #397).
5932
+ const shorthandProperty = scopeMemo && parent?.type === 'shorthand_field_initializer';
5933
+ usages.push({ line, column, usageType, ...(inAttribute && { inAttribute: true }),
5934
+ ...(macroNamespace && { namespace: 'macro' }), ...scope,
5935
+ ...(shorthandProperty && { shorthandProperty: true }) });
3814
5936
  return true;
3815
5937
  });
3816
5938
 
3817
5939
  return usages;
3818
5940
  }
3819
5941
 
5942
+ /**
5943
+ * Is this identifier a name in the macro namespace (fix #377)? The name of a
5944
+ * macro invocation, the path of an attribute, or an entry of a derive list
5945
+ * (also inside `cfg_attr(..., derive(...))`).
5946
+ */
5947
+ function rustMacroNamespaceName(node, parent, attributeNode) {
5948
+ if (!parent) return false;
5949
+ if (parent.type === 'macro_invocation') return sameNode(parent.childForFieldName('macro'), node);
5950
+ if (parent.type === 'scoped_identifier' && sameNode(parent.childForFieldName('name'), node)) {
5951
+ const up = parent.parent;
5952
+ if (up?.type === 'macro_invocation') return sameNode(up.childForFieldName('macro'), parent);
5953
+ if (up?.type === 'attribute') return sameNode(up.namedChild(0), parent);
5954
+ }
5955
+ if (!attributeNode) return false;
5956
+ if (parent.type === 'attribute') return sameNode(parent.namedChild(0), node);
5957
+ if (parent.type !== 'token_tree') return false;
5958
+ // derive(A, path::B): the token tree follows the `derive` path (the
5959
+ // attribute's own path, or an identifier token inside cfg_attr).
5960
+ const opener = parent.parent?.type === 'attribute' && sameNode(parent.parent.childForFieldName('arguments'), parent)
5961
+ ? parent.parent.namedChild(0)
5962
+ : parent.previousSibling;
5963
+ if (!opener || opener.text !== 'derive') return false;
5964
+ const next = node.nextSibling;
5965
+ return !next || next.type !== '::';
5966
+ }
5967
+
3820
5968
  /**
3821
5969
  * Classify a Rust symbol as a runtime entry point of a specific kind.
3822
5970
  * Returns 'test' | 'main' | 'framework' | null.
@@ -3854,6 +6002,11 @@ function isEntryPoint(symbol) {
3854
6002
  }
3855
6003
 
3856
6004
  module.exports = {
6005
+ primeDeclarationTrees,
6006
+ DECLARATION_NODE_TYPES,
6007
+ rustExpandedItemRecovery,
6008
+ parseDeclarationsIn,
6009
+ rustMacroInvocationContext,
3857
6010
  findFunctions,
3858
6011
  findClasses,
3859
6012
  findStateObjects,