ucn 5.4.2 → 5.5.0

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 +18 -12
  6. package/cli/index.js +40 -14
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +30 -0
  9. package/core/analysis.js +508 -264
  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 +522 -31
  16. package/core/callers.js +12053 -1047
  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 +457 -60
  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 +2113 -0
  62. package/core/rust-modules.js +185 -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 +1161 -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 +2237 -115
  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,23 @@ 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 };
100
127
  }
101
128
 
102
129
  /**
@@ -116,6 +143,7 @@ function declarationTrees(code, parser) {
116
143
  macroItemRecovery: !!macro,
117
144
  macroItemCount: macro?.itemCount || 0,
118
145
  macroDeclarationNameStarts: macro?.declarationNameStarts || new Set(),
146
+ macroCallRecovery: macro?.callRecovery || null,
119
147
  };
120
148
  lastDeclarationParser = parser;
121
149
  lastDeclarationCode = code;
@@ -123,6 +151,189 @@ function declarationTrees(code, parser) {
123
151
  return result;
124
152
  }
125
153
 
154
+ /** Names of the outer attributes preceding an item (`#[cfg(..)]` -> cfg). */
155
+ function rustAttributeNames(node) {
156
+ const names = [];
157
+ for (let sibling = node.previousNamedSibling; sibling; sibling = sibling.previousNamedSibling) {
158
+ if (sibling.type === 'line_comment' || sibling.type === 'block_comment') continue;
159
+ if (sibling.type !== 'attribute_item') break;
160
+ const head = sibling.namedChildren.find(child => child.type === 'attribute')
161
+ ?.namedChildren.find(child => child.type === 'identifier' || child.type === 'scoped_identifier');
162
+ if (head) names.unshift(head.text);
163
+ }
164
+ return names;
165
+ }
166
+
167
+ /**
168
+ * Declaration trees for a source the caller already parsed and knows holds
169
+ * no macro item bodies to recover (fix #374: an expanded file, whose project
170
+ * invocations are already replaced by their expansions). Primes the memo
171
+ * the extractors below read, so they share that tree.
172
+ */
173
+ function primeDeclarationTrees(code, parser, tree) {
174
+ lastDeclarationParser = parser;
175
+ lastDeclarationCode = code;
176
+ lastDeclarationTrees = {
177
+ primary: tree,
178
+ trees: [tree],
179
+ macroItemRecovery: false,
180
+ macroItemCount: 0,
181
+ macroDeclarationNameStarts: new Set(),
182
+ // An expansion may hand items to an outside macro (`quickcheck! {
183
+ // fn .. }`, fix #401): their calls are read from the item parse,
184
+ // computed when calls are extracted.
185
+ macroCallRecovery: undefined,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Function qualifiers from the AST `function_modifiers` node (fix #370): a
191
+ * first-line text probe misread `fn f() -> impl Future { async { .. } }` as
192
+ * an async fn and missed qualifiers written on a later line.
193
+ */
194
+ function rustFunctionQualifiers(node) {
195
+ const out = { async: false, unsafe: false, const: false, extern: false };
196
+ const mods = node?.namedChildren?.find(child => child.type === 'function_modifiers');
197
+ if (!mods) return out;
198
+ for (let i = 0; i < mods.childCount; i++) {
199
+ const type = mods.child(i).type;
200
+ if (type === 'async') out.async = true;
201
+ else if (type === 'unsafe') out.unsafe = true;
202
+ else if (type === 'const') out.const = true;
203
+ else if (type === 'extern_modifier' || type === 'extern') out.extern = true;
204
+ }
205
+ return out;
206
+ }
207
+
208
+ // Future vocabulary (fix #370): the std/core Future trait, the smart-pointer
209
+ // wrappers that keep a future a future, and the futures-crate boxed aliases.
210
+ const RUST_FUTURE_TRAITS = new Set(['Future']);
211
+ const RUST_FUTURE_WRAPPERS = new Set(['Pin', 'Box']);
212
+ const RUST_FUTURE_ALIASES = new Set(['BoxFuture', 'LocalBoxFuture']);
213
+
214
+ function rustTypeLastName(typeNode) {
215
+ if (!typeNode) return null;
216
+ if (typeNode.type === 'type_identifier') return typeNode.text;
217
+ if (typeNode.type === 'scoped_type_identifier') return typeNode.childForFieldName('name')?.text || null;
218
+ if (typeNode.type === 'generic_type') return rustTypeLastName(typeNode.childForFieldName('type'));
219
+ return null;
220
+ }
221
+
222
+ function rustTypeArguments(typeNode) {
223
+ const args = typeNode?.type === 'generic_type' ? typeNode.childForFieldName('type_arguments') : null;
224
+ return (args?.namedChildren || []).filter(arg => arg.type !== 'lifetime');
225
+ }
226
+
227
+ /**
228
+ * Whether a type node denotes a future, and its Output type text:
229
+ * `impl Future<Output = T> (+ Send)`, `dyn Future<..>` behind `Pin`/`Box`/
230
+ * references, `BoxFuture<'a, T>`. Returns { output } or null.
231
+ */
232
+ function rustFutureShape(typeNode, depth = 0) {
233
+ if (!typeNode || depth > 8) return null;
234
+ switch (typeNode.type) {
235
+ case 'bounded_type':
236
+ for (const child of typeNode.namedChildren) {
237
+ const shape = rustFutureShape(child, depth + 1);
238
+ if (shape) return shape;
239
+ }
240
+ return null;
241
+ case 'abstract_type':
242
+ case 'dynamic_type': {
243
+ const bound = typeNode.childForFieldName('trait') || typeNode.namedChild(0);
244
+ if (bound?.type === 'bounded_type') return rustFutureShape(bound, depth + 1);
245
+ if (!RUST_FUTURE_TRAITS.has(rustTypeLastName(bound))) return null;
246
+ const args = bound.type === 'generic_type' ? bound.childForFieldName('type_arguments') : null;
247
+ const output = (args?.namedChildren || []).find(arg =>
248
+ arg.type === 'type_binding' && arg.childForFieldName('name')?.text === 'Output');
249
+ return { output: output?.childForFieldName('type')?.text || null };
250
+ }
251
+ case 'reference_type':
252
+ return rustFutureShape(typeNode.childForFieldName('type'), depth + 1);
253
+ case 'generic_type': {
254
+ const name = rustTypeLastName(typeNode);
255
+ const args = rustTypeArguments(typeNode);
256
+ if (RUST_FUTURE_WRAPPERS.has(name) && args.length === 1) return rustFutureShape(args[0], depth + 1);
257
+ if (RUST_FUTURE_ALIASES.has(name)) return { output: args.length > 0 ? args[args.length - 1].text : null };
258
+ return null;
259
+ }
260
+ case 'type_identifier':
261
+ case 'scoped_type_identifier':
262
+ return RUST_FUTURE_ALIASES.has(rustTypeLastName(typeNode)) ? { output: null } : null;
263
+ default:
264
+ return null;
265
+ }
266
+ }
267
+
268
+ // Attributes that never replace a function's body or signature: the
269
+ // language's built-in lint/doc/codegen attributes and `tracing::instrument`
270
+ // (which preserves the async signature). Any other attribute may be a
271
+ // proc macro that rewrites the function (`#[tokio::main]` turns an async fn
272
+ // into a blocking sync fn), so its call result is not known.
273
+ const RUST_TRANSPARENT_FN_ATTRIBUTES = new Set([
274
+ 'inline', 'cold', 'must_use', 'allow', 'warn', 'deny', 'forbid', 'expect',
275
+ 'deprecated', 'doc', 'cfg', 'track_caller', 'target_feature', 'rustfmt::skip',
276
+ 'instrument', 'tracing::instrument',
277
+ ]);
278
+
279
+ function rustAttributePath(node) {
280
+ return node && ['identifier', 'scoped_identifier'].includes(node.type) ? node.text.replace(/\s+/g, '') : null;
281
+ }
282
+
283
+ /** Outer attribute paths of an item; `cfg_attr(pred, a, b)` yields a, b. */
284
+ function rustOuterAttributePaths(itemNode) {
285
+ const paths = [];
286
+ for (let sibling = itemNode.previousNamedSibling; sibling; sibling = sibling.previousNamedSibling) {
287
+ if (sibling.type === 'line_comment' || sibling.type === 'block_comment') continue;
288
+ if (sibling.type !== 'attribute_item') break;
289
+ const attribute = sibling.namedChildren.find(child => child.type === 'attribute');
290
+ const path = rustAttributePath(attribute?.namedChild(0));
291
+ if (!path) { paths.push('?'); continue; }
292
+ if (path !== 'cfg_attr') { paths.push(path); continue; }
293
+ const args = attribute.childForFieldName('arguments');
294
+ let group = 0;
295
+ let expectPath = false;
296
+ let current = '';
297
+ for (let i = 0; args && i < args.childCount; i++) {
298
+ const token = args.child(i);
299
+ if (token.type === ',') {
300
+ if (current) paths.push(current);
301
+ current = '';
302
+ group++;
303
+ expectPath = true;
304
+ continue;
305
+ }
306
+ if (group === 0 || !expectPath) continue;
307
+ if (token.type === 'identifier' || token.type === '::') current += token.text;
308
+ else { if (current) paths.push(current); current = ''; expectPath = false; }
309
+ }
310
+ if (current) paths.push(current);
311
+ }
312
+ return paths;
313
+ }
314
+
315
+ /**
316
+ * What calling a function returns when that is a future (fix #370):
317
+ * `async fn` -> { kind: 'async', output }, a future-shaped return type ->
318
+ * { kind: 'future', output }. Null otherwise (named return types are
319
+ * resolved against project `impl Future` types and aliases at query time).
320
+ */
321
+ function rustFutureReturn(fnNode, qualifiers) {
322
+ const returnNode = fnNode.childForFieldName('return_type');
323
+ let result = null;
324
+ if (qualifiers.async) {
325
+ result = { kind: 'async', output: returnNode ? nodeTextWithoutComments(returnNode).trim() : '()' };
326
+ } else {
327
+ const shape = rustFutureShape(returnNode);
328
+ if (shape) result = { kind: 'future', output: shape.output };
329
+ }
330
+ if (result) {
331
+ const wrapping = rustOuterAttributePaths(fnNode).find(path => !RUST_TRANSPARENT_FN_ATTRIBUTES.has(path));
332
+ if (wrapping) result = { kind: 'wrapped', output: result.output, attribute: wrapping };
333
+ }
334
+ return result;
335
+ }
336
+
126
337
  /**
127
338
  * Extract return type from Rust function
128
339
  */
@@ -203,16 +414,82 @@ function extractRustParams(paramsNode) {
203
414
  return text.replace(/^\(|\)$/g, '').trim();
204
415
  }
205
416
 
206
- function extractRustCallbackParamTypes(paramsNode) {
417
+ /**
418
+ * Generic parameters bounded by a closure trait (fix #368):
419
+ * `fn join<A: FnOnce(FnContext) -> R>(a: A)` and `where A: FnOnce(...)`.
420
+ * Returns name -> function_type node, plus every declared type-parameter
421
+ * name (a closure argument typed by a generic parameter has no concrete
422
+ * identity, so such slots stay unknown).
423
+ */
424
+ function rustGenericClosureBounds(ownerNode) {
425
+ const bounds = new Map();
426
+ const declared = new Set();
427
+ if (!ownerNode) return { bounds, declared };
428
+ const functionTypeIn = (boundsNode) => {
429
+ const pending = boundsNode ? [boundsNode] : [];
430
+ while (pending.length > 0) {
431
+ const current = pending.pop();
432
+ if (current.type === 'function_type' &&
433
+ ['Fn', 'FnMut', 'FnOnce'].includes(current.childForFieldName('trait')?.text)) {
434
+ return current;
435
+ }
436
+ for (let j = 0; j < current.namedChildCount; j++) pending.push(current.namedChild(j));
437
+ }
438
+ return null;
439
+ };
440
+ const record = (nameNode, boundsNode) => {
441
+ if (nameNode?.type !== 'type_identifier') return;
442
+ const functionType = functionTypeIn(boundsNode);
443
+ if (!functionType) return;
444
+ if (bounds.has(nameNode.text) && bounds.get(nameNode.text) !== functionType) {
445
+ bounds.set(nameNode.text, null); // two closure bounds: ambiguous
446
+ } else {
447
+ bounds.set(nameNode.text, functionType);
448
+ }
449
+ };
450
+ const owners = [ownerNode];
451
+ for (let a = ownerNode.parent; a; a = a.parent) {
452
+ if (a.type === 'impl_item' || a.type === 'trait_item') {
453
+ owners.push(a);
454
+ break;
455
+ }
456
+ if (a.type === 'function_item') break;
457
+ }
458
+ for (const owner of owners) {
459
+ const typeParameters = owner.childForFieldName('type_parameters');
460
+ for (const child of typeParameters?.namedChildren || []) {
461
+ if (child.type === 'type_identifier') declared.add(child.text);
462
+ else if (child.type === 'constrained_type_parameter') {
463
+ const left = child.childForFieldName('left');
464
+ if (left?.type === 'type_identifier') declared.add(left.text);
465
+ if (owner === ownerNode) record(left, child.childForFieldName('bounds'));
466
+ } else if (child.type === 'optional_type_parameter') {
467
+ const name = child.childForFieldName('name');
468
+ if (name?.type === 'type_identifier') declared.add(name.text);
469
+ }
470
+ }
471
+ if (owner !== ownerNode) continue;
472
+ const whereClause = owner.namedChildren.find(child => child.type === 'where_clause');
473
+ for (const predicate of whereClause?.namedChildren || []) {
474
+ if (predicate.type !== 'where_predicate') continue;
475
+ record(predicate.childForFieldName('left'), predicate.childForFieldName('bounds'));
476
+ }
477
+ }
478
+ return { bounds, declared };
479
+ }
480
+
481
+ function extractRustCallbackParamTypes(paramsNode, ownerNode = null) {
207
482
  if (!paramsNode) return undefined;
208
483
  const callbacks = {};
209
484
  let callArgumentIndex = 0;
485
+ let generic = null;
210
486
  for (let i = 0; i < paramsNode.namedChildCount; i++) {
211
487
  const parameter = paramsNode.namedChild(i);
212
488
  if (parameter.type === 'self_parameter') continue;
213
489
  if (parameter.type !== 'parameter') continue;
214
490
  const typeNode = parameter.childForFieldName('type');
215
491
  let functionType = null;
492
+ let viaBound = false;
216
493
  const pending = typeNode ? [typeNode] : [];
217
494
  while (pending.length > 0 && !functionType) {
218
495
  const current = pending.pop();
@@ -224,19 +501,52 @@ function extractRustCallbackParamTypes(paramsNode) {
224
501
  pending.push(current.namedChild(j));
225
502
  }
226
503
  }
504
+ if (!functionType && ownerNode) {
505
+ // `op: OP` / `op: &OP` / `op: &mut OP` with OP bounded by a
506
+ // closure trait on this function (fix #368).
507
+ let head = typeNode;
508
+ while (head?.type === 'reference_type') {
509
+ head = head.childForFieldName('type');
510
+ }
511
+ if (head?.type === 'type_identifier') {
512
+ generic = generic || rustGenericClosureBounds(ownerNode);
513
+ const bound = generic.bounds.get(head.text);
514
+ if (bound) {
515
+ functionType = bound;
516
+ viaBound = true;
517
+ }
518
+ }
519
+ }
227
520
  const callbackParams = functionType?.childForFieldName('parameters');
228
521
  if (callbackParams) {
522
+ if (viaBound || ownerNode) {
523
+ generic = generic || rustGenericClosureBounds(ownerNode);
524
+ }
229
525
  const types = [];
230
526
  let complete = true;
527
+ let known = 0;
231
528
  for (let j = 0; j < callbackParams.namedChildCount; j++) {
232
- const name = aliasBaseTypeName(callbackParams.namedChild(j));
529
+ const slot = callbackParams.namedChild(j);
530
+ if (slot.type.endsWith('comment')) continue;
531
+ const name = aliasBaseTypeName(slot);
233
532
  if (!name) {
234
- complete = false;
235
- break;
533
+ if (!ownerNode) {
534
+ complete = false;
535
+ break;
536
+ }
537
+ types.push(null);
538
+ continue;
539
+ }
540
+ // A slot naming a declared type parameter is not a concrete
541
+ // type; keep its position, never its spelling.
542
+ if (generic && generic.declared.has(name)) {
543
+ types.push(null);
544
+ continue;
236
545
  }
237
546
  types.push(name);
547
+ known++;
238
548
  }
239
- if (complete && types.length > 0) callbacks[callArgumentIndex] = types;
549
+ if (complete && known > 0) callbacks[callArgumentIndex] = types;
240
550
  }
241
551
  callArgumentIndex++;
242
552
  }
@@ -337,7 +647,9 @@ function extractAttributesWithArgs(node, codeOrLines) {
337
647
  if (line.startsWith('#[')) {
338
648
  // Match #[name(...args...)] or #[name]
339
649
  // Need to handle nested parens; use a simple bracket-matching approach.
340
- const m = line.match(/^#\[(.+)\]\s*$/);
650
+ // (The line is trimmed: only a line ending in ']' can match; a
651
+ // macro expansion's generated line can be very long.)
652
+ const m = line.charCodeAt(line.length - 1) === 93 ? line.match(/^#\[(.+)\]\s*$/) : null;
341
653
  if (m) {
342
654
  const attrContent = m[1];
343
655
  const parenIdx = attrContent.indexOf('(');
@@ -437,12 +749,13 @@ function _processFunction(node, functions, processedRanges, lines, code) {
437
749
  if (nameNode) {
438
750
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
439
751
  const text = node.text;
440
- const firstLine = text.split('\n')[0];
441
752
 
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 ');
753
+ const qualifiers = rustFunctionQualifiers(node);
754
+ const isAsync = qualifiers.async;
755
+ const isUnsafe = qualifiers.unsafe;
756
+ const isConst = qualifiers.const;
757
+ const isExtern = qualifiers.extern;
758
+ const futureReturn = rustFutureReturn(node, qualifiers);
446
759
  const visibility = extractVisibility(text);
447
760
  const returnType = extractReturnType(node);
448
761
  const iteratorItemType = extractRustIteratorItemType(node);
@@ -452,7 +765,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
452
765
  const attributes = extractAttributes(node, lines);
453
766
  const attributesWithArgs = extractAttributesWithArgs(node, lines);
454
767
  const inCfgTest = _isInsideCfgTestModule(node, lines);
455
- const callbackParamTypes = extractRustCallbackParamTypes(paramsNode);
768
+ const callbackParamTypes = extractRustCallbackParamTypes(paramsNode, node);
456
769
 
457
770
  const modifiers = [];
458
771
  if (visibility) modifiers.push(visibility);
@@ -478,6 +791,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
478
791
  indent,
479
792
  modifiers,
480
793
  ...(returnType && { returnType }),
794
+ ...(futureReturn && { futureReturn }),
481
795
  ...(iteratorItemType && { iteratorItemType }),
482
796
  ...(docstring && { docstring }),
483
797
  ...(generics && { generics }),
@@ -506,7 +820,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
506
820
  const visibility = extractVisibility(child.text);
507
821
  const returnType = extractReturnType(child);
508
822
  const docstring = extractRustDocstring(lines, startLine);
509
- const callbackParamTypes = extractRustCallbackParamTypes(fParams);
823
+ const callbackParamTypes = extractRustCallbackParamTypes(fParams, child);
510
824
  const iteratorItemType = extractRustIteratorItemType(child);
511
825
  const modifiers = ['extern'];
512
826
  if (visibility) modifiers.push(visibility);
@@ -715,9 +1029,39 @@ function _inferMacroReturn(node, macroName) {
715
1029
  * Returns true if node was matched, false otherwise
716
1030
  * Note: for impl_item, caller should NOT skip subtrees (parse() always returns true)
717
1031
  */
1032
+ /**
1033
+ * Lexical scope of an item declared in a function body (fix #381): Rust
1034
+ * items in a block are visible throughout that block and nowhere else.
1035
+ */
1036
+ function rustBlockItemScope(node) {
1037
+ for (let parent = node.parent; parent; parent = parent.parent) {
1038
+ if (parent.type === 'declaration_list' || parent.type === 'source_file') return {};
1039
+ if (parent.type === 'block') {
1040
+ return {
1041
+ lexicalScopeStartLine: parent.startPosition.row + 1,
1042
+ lexicalScopeEndLine: parent.endPosition.row + 1,
1043
+ };
1044
+ }
1045
+ }
1046
+ return {};
1047
+ }
1048
+
1049
+ /**
1050
+ * How a struct's name works as a VALUE (fix #389): a unit struct (`struct
1051
+ * S;`) names its only value, a tuple struct (`struct T(u8);`) names its
1052
+ * constructor function; a braced struct's name is a type only.
1053
+ */
1054
+ function rustStructValueShape(node) {
1055
+ const body = node.childForFieldName('body');
1056
+ if (!body) return 'unit';
1057
+ if (body.type === 'ordered_field_declaration_list') return 'tuple';
1058
+ return null;
1059
+ }
1060
+
718
1061
  function _processClass(node, types, processedRanges, lines, code) {
719
- // Struct items
720
- if (node.type === 'struct_item') {
1062
+ // Struct and union items (a union is declared like a braced struct;
1063
+ // its fields are its members, fix #389)
1064
+ if (node.type === 'struct_item' || node.type === 'union_item') {
721
1065
  const rangeKey = `${node.startIndex}-${node.endIndex}`;
722
1066
  if (processedRanges.has(rangeKey)) return true;
723
1067
  processedRanges.add(rangeKey);
@@ -732,14 +1076,17 @@ function _processClass(node, types, processedRanges, lines, code) {
732
1076
  const attributes = extractAttributes(node, lines);
733
1077
  const modifiers = visibility ? [visibility] : [];
734
1078
  for (const attr of attributes) modifiers.push(attr);
1079
+ const valueShape = node.type === 'struct_item' ? rustStructValueShape(node) : null;
735
1080
 
736
1081
  types.push({
737
1082
  name: nameNode.text,
738
1083
  startLine,
739
1084
  endLine,
740
- type: 'struct',
1085
+ type: node.type === 'union_item' ? 'union' : 'struct',
741
1086
  members,
742
1087
  modifiers,
1088
+ ...(valueShape && { valueShape }),
1089
+ ...rustBlockItemScope(node),
743
1090
  ...(docstring && { docstring }),
744
1091
  ...(generics && { generics })
745
1092
  });
@@ -770,6 +1117,7 @@ function _processClass(node, types, processedRanges, lines, code) {
770
1117
  type: 'enum',
771
1118
  members: extractEnumVariants(node, lines),
772
1119
  modifiers,
1120
+ ...rustBlockItemScope(node),
773
1121
  ...(docstring && { docstring }),
774
1122
  ...(generics && { generics })
775
1123
  });
@@ -789,6 +1137,7 @@ function _processClass(node, types, processedRanges, lines, code) {
789
1137
  const docstring = extractRustDocstring(lines, startLine);
790
1138
  const visibility = extractVisibility(node.text);
791
1139
  const generics = extractGenerics(node);
1140
+ const supertraits = extractRustSupertraits(node);
792
1141
 
793
1142
  types.push({
794
1143
  name: nameNode.text,
@@ -797,8 +1146,10 @@ function _processClass(node, types, processedRanges, lines, code) {
797
1146
  type: 'trait',
798
1147
  members: extractTraitMembers(node, lines),
799
1148
  modifiers: visibility ? [visibility] : [],
1149
+ ...rustBlockItemScope(node),
800
1150
  ...(docstring && { docstring }),
801
- ...(generics && { generics })
1151
+ ...(generics && { generics }),
1152
+ ...(supertraits && { supertraits }),
802
1153
  });
803
1154
  }
804
1155
  return true;
@@ -814,6 +1165,13 @@ function _processClass(node, types, processedRanges, lines, code) {
814
1165
  const implInfo = extractImplInfo(node);
815
1166
  const docstring = extractRustDocstring(lines, startLine);
816
1167
  const derefTarget = extractDerefTarget(node, implInfo.traitName);
1168
+ const implSelfTypeNode = node.childForFieldName('type');
1169
+ const implSelfRef = implSelfTypeNode?.type === 'reference_type'
1170
+ ? (implSelfTypeNode.namedChildren.some(child => child.type === 'mutable_specifier')
1171
+ ? '&mut' : '&')
1172
+ : null;
1173
+ const blanketSelfBounds = implInfo.traitName
1174
+ ? extractRustBlanketSelfBounds(node, implSelfTypeNode) : null;
817
1175
 
818
1176
  types.push({
819
1177
  name: implInfo.name,
@@ -821,10 +1179,13 @@ function _processClass(node, types, processedRanges, lines, code) {
821
1179
  endLine,
822
1180
  type: 'impl',
823
1181
  traitName: implInfo.traitName,
1182
+ ...rustBlockItemScope(node),
824
1183
  typeName: implInfo.typeName,
825
1184
  members: extractImplMembers(node, lines, implInfo.typeName),
826
1185
  modifiers: [],
827
1186
  ...(implInfo.generics && { generics: implInfo.generics }),
1187
+ ...(implSelfRef && { implSelfRef }),
1188
+ ...(blanketSelfBounds && { blanketSelfBounds }),
828
1189
  ...(derefTarget && { derefTarget }),
829
1190
  ...(docstring && { docstring })
830
1191
  });
@@ -871,13 +1232,33 @@ function _processClass(node, types, processedRanges, lines, code) {
871
1232
  const docstring = extractRustDocstring(lines, startLine);
872
1233
  const inferred = _inferMacroReturn(node, nameNode.text);
873
1234
 
1235
+ // Attributes and the textual scope a macro_rules! definition is
1236
+ // visible in (fix #374: macro expansion resolves invocations by
1237
+ // Rust's textual scoping without re-parsing the defining file).
1238
+ const modifiers = rustAttributeNames(node).filter(name => name === 'macro_export' || name === 'cfg');
1239
+ let macroScope = null;
1240
+ for (let parent = node.parent; parent && parent.type !== 'source_file'; parent = parent.parent) {
1241
+ if (parent.type === 'block' ||
1242
+ (parent.type === 'declaration_list' && parent.parent?.type === 'mod_item')) {
1243
+ const mod = parent.type === 'declaration_list';
1244
+ const macroUse = mod && rustAttributeNames(parent.parent).includes('macro_use');
1245
+ macroScope = {
1246
+ kind: mod ? 'mod' : 'block',
1247
+ startLine: parent.startPosition.row + 1, endLine: parent.endPosition.row + 1,
1248
+ ...(macroUse && { macroUse: true }),
1249
+ };
1250
+ break;
1251
+ }
1252
+ }
1253
+
874
1254
  types.push({
875
1255
  name: nameNode.text,
876
1256
  startLine,
877
1257
  endLine,
878
1258
  type: 'macro',
879
1259
  members: [],
880
- modifiers: [],
1260
+ modifiers,
1261
+ ...(macroScope && { macroScope }),
881
1262
  ...inferred,
882
1263
  ...(docstring && { docstring })
883
1264
  });
@@ -911,6 +1292,9 @@ function _processClass(node, types, processedRanges, lines, code) {
911
1292
  // callers can treat alias-qualified receivers as the base type
912
1293
  // (fix #208 — cursive StyledString::plain).
913
1294
  const aliasOf = aliasBaseTypeName(node.childForFieldName('type'));
1295
+ // An alias of a future type (`type Fut = Pin<Box<dyn Future..>>`)
1296
+ // makes functions returning it future producers (fix #370).
1297
+ const futureShape = rustFutureShape(node.childForFieldName('type'));
914
1298
 
915
1299
  types.push({
916
1300
  name: nameNode.text,
@@ -920,6 +1304,7 @@ function _processClass(node, types, processedRanges, lines, code) {
920
1304
  members: [],
921
1305
  modifiers: visibility ? [visibility] : [],
922
1306
  ...(aliasOf && { aliasOf }),
1307
+ ...(futureShape && { futureReturn: { kind: 'future', output: futureShape.output } }),
923
1308
  aliasTypeText: node.childForFieldName('type')?.text,
924
1309
  aliasTypeParameters: (node.childForFieldName('type_parameters')?.namedChildren || [])
925
1310
  .map(parameter => parameter.type === 'type_identifier' ? parameter.text
@@ -982,29 +1367,31 @@ function extractDerefTarget(implNode, traitName) {
982
1367
  * Returns true if node was matched, false otherwise
983
1368
  */
984
1369
  function _processState(node, objects, lines) {
985
- // Handle const items (only top-level)
986
- if (node.type === 'const_item') {
1370
+ // Handle const and static items (only top-level). The declared type
1371
+ // (fix #392) types the item when it is a method receiver (`FLAGS.iter()`).
1372
+ if (node.type === 'const_item' || node.type === 'static_item') {
987
1373
  if (!node.parent || node.parent.type !== 'source_file') return false;
988
1374
  const nameNode = node.childForFieldName('name');
989
1375
  if (nameNode) {
990
1376
  const name = nameNode.text;
991
1377
  if (_STATE_PATTERN.test(name)) {
992
1378
  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 });
1379
+ // Only a plain type path (behind any references) is recorded:
1380
+ // a generic wrapper (`Lazy<T>`, `Mutex<T>`) owns the methods
1381
+ // or derefs to its argument. A `static mut` is never typed.
1382
+ let typeNode = node.childForFieldName('type');
1383
+ // A std deref wrapper (`LazyLock<Store>`, fix #401) receives
1384
+ // as its target; the chain is recorded for the query.
1385
+ const deref = rustStdDerefChain(typeNode);
1386
+ if (deref) typeNode = deref.inner;
1387
+ while (typeNode?.type === 'reference_type') typeNode = typeNode.childForFieldName('type');
1388
+ const mutable = node.type === 'static_item' &&
1389
+ node.children.some(child => child.type === 'mutable_specifier');
1390
+ const plain = typeNode && (typeNode.type === 'type_identifier' ||
1391
+ typeNode.type === 'scoped_type_identifier');
1392
+ objects.push({ name, startLine, endLine,
1393
+ ...(plain && !mutable && { valueType: typeNode.text,
1394
+ ...(deref && { valueDerefVia: deref.via }) }) });
1008
1395
  }
1009
1396
  }
1010
1397
  return true;
@@ -1054,7 +1441,10 @@ function extractGenericBounds(node) {
1054
1441
  const record = declaration => {
1055
1442
  if (!declaration) return;
1056
1443
  const children = declaration.namedChildren || [];
1057
- const parameter = children.find(child => child.type === 'type_identifier');
1444
+ // `where for<'a> C: Trait<'a>` bounds C itself (fix #368).
1445
+ const higherRanked = children.find(child => child.type === 'higher_ranked_trait_bound');
1446
+ const parameter = children.find(child => child.type === 'type_identifier') ||
1447
+ (higherRanked?.namedChildren || []).find(child => child.type === 'type_identifier');
1058
1448
  const bounds = children.find(child => child.type === 'trait_bounds');
1059
1449
  if (!parameter || !bounds) return;
1060
1450
  const names = bounds.namedChildren
@@ -1077,6 +1467,170 @@ function extractGenericBounds(node) {
1077
1467
  [name, [...bounds].sort()]));
1078
1468
  }
1079
1469
 
1470
+ /**
1471
+ * Declared supertraits of a trait (fix #368): `trait C<I>: Send + Sized`
1472
+ * and `where Self: P`. Nominal trait heads only (paths keep their terminal
1473
+ * name, `?Sized` and lifetimes add nothing).
1474
+ */
1475
+ function extractRustSupertraits(traitNode) {
1476
+ const names = new Set();
1477
+ const addBounds = (boundsNode) => {
1478
+ for (const bound of boundsNode?.namedChildren || []) {
1479
+ if (bound.type === 'removed_trait_bound' || bound.type === 'lifetime') continue;
1480
+ const name = aliasBaseTypeName(bound.type === 'higher_ranked_trait_bound'
1481
+ ? bound.childForFieldName('type') || bound.namedChildren.at(-1) : bound);
1482
+ if (name) names.add(name);
1483
+ }
1484
+ };
1485
+ addBounds(traitNode.childForFieldName('bounds'));
1486
+ const whereClause = traitNode.namedChildren.find(child => child.type === 'where_clause');
1487
+ for (const predicate of whereClause?.namedChildren || []) {
1488
+ if (predicate.type !== 'where_predicate') continue;
1489
+ if (predicate.childForFieldName('left')?.text !== 'Self') continue;
1490
+ addBounds(predicate.childForFieldName('bounds'));
1491
+ }
1492
+ return names.size > 0 ? [...names].sort() : null;
1493
+ }
1494
+
1495
+ /**
1496
+ * `[T]` behind any reference layers (`&[T]`, `&mut [T]`) is the primitive
1497
+ * slice type (fix #368). Its methods are the slice's inherent methods and
1498
+ * trait impls written `for [T]`; the canonical receiver name is 'slice'.
1499
+ * Arrays (`[T; N]`) unsize to slices during method probing and abstain.
1500
+ */
1501
+ function rustSliceTypeOf(typeNode) {
1502
+ let current = typeNode;
1503
+ while (current?.type === 'reference_type') current = current.childForFieldName('type');
1504
+ return current?.type === 'array_type' && !current.childForFieldName('length') ? 'slice' : null;
1505
+ }
1506
+
1507
+ /**
1508
+ * A raw pointer type behind any reference layers (`*const T`, `*mut T`) is a
1509
+ * primitive type (fix #399): its methods are the pointer's inherent methods
1510
+ * and trait impls written `for *const T` / `for *mut T`. Canonical receiver
1511
+ * names '*const' and '*mut'; raw pointers never auto-deref.
1512
+ */
1513
+ function rustRawPointerTypeOf(typeNode) {
1514
+ let current = typeNode;
1515
+ while (current?.type === 'reference_type') current = current.childForFieldName('type');
1516
+ if (current?.type !== 'pointer_type') return null;
1517
+ return current.children.some(child => child.type === 'mutable_specifier') ? '*mut' : '*const';
1518
+ }
1519
+
1520
+ /**
1521
+ * Standard-library wrappers whose `Deref::Target` is their (first) type
1522
+ * argument (fix #401): smart pointers, `ManuallyDrop`, lazy cells, `RefCell`
1523
+ * borrow guards, lock guards, `AssertUnwindSafe`, `Cow` (Target = its
1524
+ * borrowed form) and `Pin<P>` (Target = P's own target). Method probing
1525
+ * visits the wrapper first, then its target. Only the written name is
1526
+ * recorded here; the query checks that it denotes the std type.
1527
+ */
1528
+ const RUST_STD_DEREF_WRAPPERS = new Set(['Box', 'Rc', 'Arc', 'ManuallyDrop', 'LazyLock',
1529
+ 'LazyCell', 'Ref', 'RefMut', 'MutexGuard', 'RwLockReadGuard', 'RwLockWriteGuard',
1530
+ 'ReentrantLockGuard', 'AssertUnwindSafe', 'Cow', 'Pin']);
1531
+
1532
+ /**
1533
+ * The std deref wrappers a declared type is written with, outermost first,
1534
+ * and the type they deref to: `&Arc<Store>` -> { via: ['Arc'], inner:
1535
+ * Store }, `Pin<Box<T>>` -> via ['Pin', 'Box']. null when the outermost
1536
+ * named type is not such a wrapper.
1537
+ */
1538
+ function rustStdDerefChain(typeNode) {
1539
+ let current = typeNode;
1540
+ const via = [];
1541
+ for (let hop = 0; hop < 6; hop++) {
1542
+ while (current?.type === 'reference_type') current = current.childForFieldName('type');
1543
+ if (current?.type !== 'generic_type') break;
1544
+ const head = current.childForFieldName('type') || current.namedChild(0);
1545
+ const name = head?.type === 'type_identifier' ? head.text
1546
+ : head?.type === 'scoped_type_identifier' ? head.childForFieldName('name')?.text : null;
1547
+ if (!name || !RUST_STD_DEREF_WRAPPERS.has(name)) break;
1548
+ const argsNode = current.childForFieldName('type_arguments') ||
1549
+ current.namedChildren.find(child => child.type === 'type_arguments');
1550
+ const args = (argsNode?.namedChildren || []).filter(child =>
1551
+ child.type !== 'lifetime' && !child.type.endsWith('comment'));
1552
+ if (args.length === 0) break;
1553
+ via.push(head.text.replace(/\s+/g, ''));
1554
+ current = args[0];
1555
+ }
1556
+ return via.length > 0 ? { via, inner: current } : null;
1557
+ }
1558
+
1559
+ /** Is `name` a type parameter declared by an item enclosing `node`? */
1560
+ function rustTypeParamInScope(node, name) {
1561
+ for (let parent = node?.parent; parent; parent = parent.parent) {
1562
+ if (!['function_item', 'impl_item', 'trait_item', 'struct_item', 'enum_item',
1563
+ 'function_signature_item'].includes(parent.type)) continue;
1564
+ const params = parent.childForFieldName('type_parameters');
1565
+ for (const param of params?.namedChildren || []) {
1566
+ const nameNode = param.type === 'type_identifier' ? param
1567
+ : param.childForFieldName('name') || param.namedChildren.find(child => child.type === 'type_identifier');
1568
+ if (nameNode?.text === name) return true;
1569
+ }
1570
+ }
1571
+ return false;
1572
+ }
1573
+
1574
+ /**
1575
+ * A declared type behind std deref wrappers (fix #401): the target's type
1576
+ * name and the wrapper chain, or null. A target that is a type parameter
1577
+ * of an enclosing item (`Box<T>`) has no name: { via, typeName: null }.
1578
+ */
1579
+ function rustDerefTypedName(typeNode) {
1580
+ const chain = rustStdDerefChain(typeNode);
1581
+ if (!chain) return null;
1582
+ const written = extractTypeName(chain.inner);
1583
+ // A slice or raw pointer target (`Box<[u8]>`) has its canonical name.
1584
+ const primitive = !written ? rustSliceTypeOf(chain.inner) || rustRawPointerTypeOf(chain.inner) : null;
1585
+ const typeName = primitive || (written === 'Self' ? findEnclosingImplType(chain.inner) || null
1586
+ : written && !rustTypeParamInScope(chain.inner, written) ? written : null);
1587
+ return { via: chain.via, typeName, inner: chain.inner, ...(primitive && { std: true }),
1588
+ outerRef: rustTypeRefKind(typeNode) };
1589
+ }
1590
+
1591
+ /** Reference layer of a declared type node: 'owned', '&' or '&mut'. */
1592
+ function rustTypeRefKind(typeNode) {
1593
+ if (typeNode?.type !== 'reference_type') return 'owned';
1594
+ return typeNode.namedChildren.some(child => child.type === 'mutable_specifier') ? '&mut' : '&';
1595
+ }
1596
+
1597
+ /**
1598
+ * How a method takes `self`: 'value' (`self`, `mut self`), '&' (`&self`),
1599
+ * '&mut' (`&mut self`); null for associated functions or typed self
1600
+ * (`self: Box<Self>`), which method probing here does not model.
1601
+ */
1602
+ function rustSelfParamKind(paramsNode) {
1603
+ const selfParameter = paramsNode?.namedChildren?.find(child => child.type === 'self_parameter');
1604
+ if (!selfParameter) return null;
1605
+ let reference = false;
1606
+ let mutable = false;
1607
+ for (let i = 0; i < selfParameter.childCount; i++) {
1608
+ const child = selfParameter.child(i);
1609
+ if (child.type === '&') reference = true;
1610
+ else if (child.type === 'mutable_specifier' && reference) mutable = true;
1611
+ }
1612
+ if (!reference) return 'value';
1613
+ return mutable ? '&mut' : '&';
1614
+ }
1615
+
1616
+ /**
1617
+ * Generic arguments of an impl's self type, in declaration order, lifetimes
1618
+ * excluded: `impl<'f, T, C> Consumer<T> for MapWith<'f, C, U>` -> ['C', 'U'].
1619
+ * Maps a struct's positional type parameters to the impl's own names.
1620
+ */
1621
+ function extractRustImplSelfArgs(implNode) {
1622
+ let typeNode = implNode.childForFieldName('type');
1623
+ while (typeNode?.type === 'reference_type') typeNode = typeNode.childForFieldName('type');
1624
+ if (typeNode?.type !== 'generic_type') return null;
1625
+ const argsNode = typeNode.childForFieldName('type_arguments');
1626
+ const args = [];
1627
+ for (const arg of argsNode?.namedChildren || []) {
1628
+ if (arg.type === 'lifetime' || arg.type.endsWith('comment')) continue;
1629
+ args.push(arg.text);
1630
+ }
1631
+ return args.length > 0 ? args : null;
1632
+ }
1633
+
1080
1634
  /**
1081
1635
  * Find all types (structs, enums, traits, impls) in Rust code
1082
1636
  */
@@ -1160,6 +1714,58 @@ function extractStructFields(structNode, codeOrLines) {
1160
1714
  /**
1161
1715
  * Extract impl block info
1162
1716
  */
1717
+ /**
1718
+ * Bounds of a blanket impl's self parameter (fix #369): for `impl<I: A>
1719
+ * Trait for I where &'a mut I: B, I: C`, one entry per subject reference
1720
+ * layer: [{ ref: 'owned', traits: ['A', 'C'] }, { ref: '&mut', traits:
1721
+ * ['B'] }]. `?Sized` and lifetimes add nothing. null when the impl's self
1722
+ * type is not one of its own type parameters.
1723
+ */
1724
+ function extractRustBlanketSelfBounds(implNode, selfTypeNode) {
1725
+ if (!selfTypeNode || selfTypeNode.type !== 'type_identifier') return null;
1726
+ const param = selfTypeNode.text;
1727
+ const typeParameters = implNode.childForFieldName('type_parameters');
1728
+ const declared = (typeParameters?.namedChildren || []).some(child =>
1729
+ (child.type === 'constrained_type_parameter' &&
1730
+ child.namedChildren.some(n => n.type === 'type_identifier' && n.text === param)) ||
1731
+ (child.type === 'type_identifier' && child.text === param) ||
1732
+ (child.type === 'type_parameter' && child.text.split(/[\s:=]/)[0] === param));
1733
+ if (!declared) return null;
1734
+ const layers = new Map();
1735
+ const add = (layer, boundsNode) => {
1736
+ for (const bound of boundsNode?.namedChildren || []) {
1737
+ if (bound.type === 'removed_trait_bound' || bound.type === 'lifetime') continue;
1738
+ const name = aliasBaseTypeName(bound.type === 'higher_ranked_trait_bound'
1739
+ ? bound.childForFieldName('type') || bound.namedChildren.at(-1) : bound);
1740
+ if (!name) continue;
1741
+ if (!layers.has(layer)) layers.set(layer, new Set());
1742
+ layers.get(layer).add(name);
1743
+ }
1744
+ };
1745
+ for (const child of typeParameters?.namedChildren || []) {
1746
+ if (child.type !== 'constrained_type_parameter') continue;
1747
+ if (!child.namedChildren.some(n => n.type === 'type_identifier' && n.text === param)) continue;
1748
+ add('owned', child.namedChildren.find(n => n.type === 'trait_bounds'));
1749
+ }
1750
+ const whereClause = implNode.namedChildren.find(child => child.type === 'where_clause');
1751
+ for (const predicate of whereClause?.namedChildren || []) {
1752
+ if (predicate.type !== 'where_predicate') continue;
1753
+ const left = predicate.childForFieldName('left') || predicate.namedChild(0);
1754
+ const bounds = predicate.childForFieldName('bounds') ||
1755
+ predicate.namedChildren.find(n => n.type === 'trait_bounds');
1756
+ if (!left) continue;
1757
+ if (left.type === 'type_identifier' && left.text === param) {
1758
+ add('owned', bounds);
1759
+ } else if (left.type === 'reference_type') {
1760
+ const inner = left.namedChildren.find(n => n.type === 'type_identifier');
1761
+ if (inner?.text !== param) continue;
1762
+ add(left.namedChildren.some(n => n.type === 'mutable_specifier') ? '&mut' : '&', bounds);
1763
+ }
1764
+ }
1765
+ return [...layers].map(([ref, traits]) => ({ ref, traits: [...traits].sort() }))
1766
+ .sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
1767
+ }
1768
+
1163
1769
  function extractImplInfo(implNode) {
1164
1770
  let traitName = null;
1165
1771
  let typeName = null;
@@ -1250,10 +1856,14 @@ function extractEnumVariants(enumNode, codeOrLines) {
1250
1856
  const { startLine, endLine } = nodeToLocation(child, code);
1251
1857
  // Check for tuple/struct variant data
1252
1858
  let params = undefined;
1859
+ // A unit variant names its value, a tuple variant its
1860
+ // constructor (fix #389); a struct variant only a path.
1861
+ let valueShape = 'unit';
1253
1862
  for (let j = 0; j < child.namedChildCount; j++) {
1254
1863
  const variantChild = child.namedChild(j);
1255
1864
  if (variantChild.type === 'field_declaration_list' || variantChild.type === 'ordered_field_declaration_list') {
1256
1865
  params = variantChild.text.slice(1, -1);
1866
+ valueShape = variantChild.type === 'ordered_field_declaration_list' ? 'tuple' : null;
1257
1867
  }
1258
1868
  }
1259
1869
  variants.push({
@@ -1261,7 +1871,8 @@ function extractEnumVariants(enumNode, codeOrLines) {
1261
1871
  startLine,
1262
1872
  endLine,
1263
1873
  memberType: 'variant',
1264
- ...(params !== undefined && { params })
1874
+ ...(params !== undefined && { params }),
1875
+ ...(valueShape && { valueShape }),
1265
1876
  });
1266
1877
  }
1267
1878
  }
@@ -1288,7 +1899,9 @@ function extractTraitMembers(traitNode, codeOrLines) {
1288
1899
  const returnType = extractReturnType(child);
1289
1900
  const iteratorItemType = extractRustIteratorItemType(child);
1290
1901
  const hasSelf = paramsNode && paramsNode.text.includes('self');
1291
- const callbackParamTypes = extractRustCallbackParamTypes(paramsNode);
1902
+ const callbackParamTypes = extractRustCallbackParamTypes(paramsNode, child);
1903
+ const qualifiers = rustFunctionQualifiers(child);
1904
+ const futureReturn = rustFutureReturn(child, qualifiers);
1292
1905
 
1293
1906
  // Rust vocabulary (fix #248): trait members carry the trait's
1294
1907
  // OWN visibility — a method of a private trait is not `pub`,
@@ -1300,7 +1913,9 @@ function extractTraitMembers(traitNode, codeOrLines) {
1300
1913
  endLine,
1301
1914
  memberType: 'method',
1302
1915
  isMethod: true,
1303
- modifiers: traitVisibility ? [traitVisibility] : [],
1916
+ ...(qualifiers.async && { isAsync: true }),
1917
+ modifiers: [...(traitVisibility ? [traitVisibility] : []), ...(qualifiers.async ? ['async'] : [])],
1918
+ ...(futureReturn && { futureReturn }),
1304
1919
  ...(paramsNode && { params: extractRustParams(paramsNode) }),
1305
1920
  ...(paramsNode && { paramsStructured: parseStructuredParams(paramsNode, 'rust') }),
1306
1921
  ...(callbackParamTypes && { callbackParamTypes }),
@@ -1320,12 +1935,112 @@ function extractTraitMembers(traitNode, codeOrLines) {
1320
1935
  * @param {string} code - Source code
1321
1936
  * @param {string} [typeName] - The type this impl is for (e.g., "MyStruct")
1322
1937
  */
1938
+ /**
1939
+ * `Self::Assoc` projections in an impl member's return type, resolved
1940
+ * through the SAME impl block's `type Assoc = T;` items (fix #368):
1941
+ * `type Folder = CollectResult<'c, T>; fn into_folder(self) -> Self::Folder`
1942
+ * declares the concrete `CollectResult<'c, T>`. Returns the substituted
1943
+ * return-type text, or null when nothing (or not everything) resolves.
1944
+ */
1945
+ function rustResolveSelfProjections(functionNode, associatedTypes) {
1946
+ const returnTypeNode = functionNode.childForFieldName('return_type');
1947
+ if (!returnTypeNode || associatedTypes.size === 0) return null;
1948
+ const replacements = [];
1949
+ let unresolved = false;
1950
+ const pending = [returnTypeNode];
1951
+ while (pending.length > 0) {
1952
+ const current = pending.pop();
1953
+ if (current.type.endsWith('comment')) return null;
1954
+ if (current.type === 'scoped_type_identifier' &&
1955
+ current.childForFieldName('path')?.text === 'Self') {
1956
+ const assoc = associatedTypes.get(current.childForFieldName('name')?.text);
1957
+ if (!assoc) unresolved = true;
1958
+ else replacements.push({ start: current.startIndex, end: current.endIndex, text: assoc });
1959
+ continue;
1960
+ }
1961
+ for (let i = 0; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
1962
+ }
1963
+ if (unresolved || replacements.length === 0) return null;
1964
+ replacements.sort((a, b) => b.start - a.start);
1965
+ const base = returnTypeNode.startIndex;
1966
+ let text = returnTypeNode.text;
1967
+ for (const replacement of replacements) {
1968
+ text = text.slice(0, replacement.start - base) + replacement.text +
1969
+ text.slice(replacement.end - base);
1970
+ }
1971
+ return text.trim() || null;
1972
+ }
1973
+
1974
+ /**
1975
+ * Associated types visible as `Self::X` inside an impl: its own items plus
1976
+ * those of sibling impl blocks for the same self type in the same module
1977
+ * scope (supertrait associated types). A name defined with different values
1978
+ * by two sibling impls is ambiguous and dropped.
1979
+ */
1980
+ function rustSiblingAssociatedTypes(implNode, own) {
1981
+ const selfText = implNode.childForFieldName('type')?.text;
1982
+ const merged = new Map(own);
1983
+ const scope = implNode.parent;
1984
+ if (!selfText || !scope) return merged;
1985
+ const conflicting = new Set();
1986
+ for (let i = 0; i < scope.namedChildCount; i++) {
1987
+ const sibling = scope.namedChild(i);
1988
+ if (sibling.type !== 'impl_item' || sibling.id === implNode.id ||
1989
+ sibling.childForFieldName('type')?.text !== selfText) continue;
1990
+ const body = sibling.childForFieldName('body');
1991
+ for (let j = 0; j < (body?.namedChildCount || 0); j++) {
1992
+ const item = body.namedChild(j);
1993
+ if (item.type !== 'type_item') continue;
1994
+ const name = item.childForFieldName('name')?.text;
1995
+ const value = item.childForFieldName('type')?.text;
1996
+ if (!name || !value || own.has(name) || /\/[/*]/.test(value)) continue;
1997
+ if (merged.has(name) && merged.get(name) !== value) conflicting.add(name);
1998
+ else merged.set(name, value);
1999
+ }
2000
+ }
2001
+ for (const name of conflicting) merged.delete(name);
2002
+ return merged;
2003
+ }
2004
+
2005
+ /**
2006
+ * The module path an impl's self type is written under (fix #371):
2007
+ * `impl Ext for std::fs::File` -> 'std::fs', through `&`/`&mut` and generic
2008
+ * arguments. null for an unqualified self type.
2009
+ */
2010
+ function rustImplSelfQualifier(implSelfTypeNode) {
2011
+ let node = implSelfTypeNode;
2012
+ while (node?.type === 'reference_type') node = node.childForFieldName('type');
2013
+ if (node?.type === 'generic_type') node = node.childForFieldName('type');
2014
+ if (node?.type !== 'scoped_type_identifier') return null;
2015
+ return node.childForFieldName('path')?.text || null;
2016
+ }
2017
+
1323
2018
  function extractImplMembers(implNode, codeOrLines, typeName) {
1324
2019
  const code = codeOrLines;
1325
2020
  const members = [];
1326
2021
  const bodyNode = implNode.childForFieldName('body');
1327
2022
  if (!bodyNode) return members;
1328
2023
  const implAttributes = extractAttributes(implNode, codeOrLines);
2024
+ const ownerGenericBounds = extractGenericBounds(implNode);
2025
+ const ownerSelfArgs = extractRustImplSelfArgs(implNode);
2026
+ // Reference impls (`impl Trait for &'a Vec<T>`) share the owner name with
2027
+ // the owned impl; the reference layer decides method probing (fix #368).
2028
+ const implSelfTypeNode = implNode.childForFieldName('type');
2029
+ const implSelfRef = implSelfTypeNode?.type === 'reference_type'
2030
+ ? (implSelfTypeNode.namedChildren.some(child => child.type === 'mutable_specifier')
2031
+ ? '&mut' : '&')
2032
+ : null;
2033
+ const implSelfQualifier = rustImplSelfQualifier(implSelfTypeNode);
2034
+ const associatedTypes = new Map();
2035
+ for (let i = 0; i < bodyNode.namedChildCount; i++) {
2036
+ const item = bodyNode.namedChild(i);
2037
+ if (item.type !== 'type_item') continue;
2038
+ const nameNode = item.childForFieldName('name');
2039
+ const valueNode = item.childForFieldName('type');
2040
+ if (nameNode && valueNode && !/\/[/*]/.test(valueNode.text)) {
2041
+ associatedTypes.set(nameNode.text, valueNode.text);
2042
+ }
2043
+ }
1329
2044
 
1330
2045
  for (let i = 0; i < bodyNode.namedChildCount; i++) {
1331
2046
  const child = bodyNode.namedChild(i);
@@ -1337,7 +2052,6 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1337
2052
  if (nameNode) {
1338
2053
  const { startLine, endLine } = nodeToLocation(child, code);
1339
2054
  const text = child.text;
1340
- const firstLine = text.split('\n')[0];
1341
2055
  const returnType = extractReturnType(child);
1342
2056
  const iteratorItemType = extractRustIteratorItemType(child);
1343
2057
  const docstring = extractRustDocstring(code, startLine);
@@ -1354,10 +2068,12 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1354
2068
  // Function qualifiers, same vocabulary as free functions
1355
2069
  // (fix #248: `pub async fn get` rendered as `pub get(...)`;
1356
2070
  // 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');
2071
+ const qualifiers = rustFunctionQualifiers(child);
2072
+ const futureReturn = rustFutureReturn(child, qualifiers);
2073
+ if (qualifiers.async) modifiers.push('async');
2074
+ if (qualifiers.unsafe) modifiers.push('unsafe');
2075
+ if (qualifiers.const) modifiers.push('const');
2076
+ if (qualifiers.extern) modifiers.push('extern');
1361
2077
  for (const attr of attributes) modifiers.push(attr);
1362
2078
  for (const attr of implAttributes) {
1363
2079
  if (!modifiers.includes(attr)) modifiers.push(attr);
@@ -1366,7 +2082,17 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1366
2082
 
1367
2083
  const memberGenerics = extractGenerics(child);
1368
2084
  const genericBounds = extractGenericBounds(child);
1369
- const callbackParamTypes = extractRustCallbackParamTypes(paramsNode);
2085
+ const callbackParamTypes = extractRustCallbackParamTypes(paramsNode, child);
2086
+ let returnTypeResolved = returnType
2087
+ ? rustResolveSelfProjections(child, associatedTypes) : null;
2088
+ if (returnType && !returnTypeResolved && /\bSelf\s*::/.test(returnType)) {
2089
+ // A supertrait's associated type is defined by a sibling
2090
+ // impl block for the same self type (`impl Consumer for C
2091
+ // { type Reducer = R; }` + `impl UnindexedConsumer for C
2092
+ // { fn to_reducer(&self) -> Self::Reducer }`).
2093
+ returnTypeResolved = rustResolveSelfProjections(child,
2094
+ rustSiblingAssociatedTypes(implNode, associatedTypes));
2095
+ }
1370
2096
  members.push({
1371
2097
  name: nameNode.text,
1372
2098
  params: extractRustParams(paramsNode),
@@ -1375,11 +2101,18 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
1375
2101
  startLine,
1376
2102
  endLine,
1377
2103
  memberType: 'method',
1378
- isAsync: firstLine.includes('async '),
2104
+ isAsync: qualifiers.async,
1379
2105
  isMethod: hasSelf, // Only true methods (with self) — associated functions are false
1380
2106
  modifiers,
1381
2107
  ...(typeName && { receiver: typeName }), // All impl members get receiver for findMethodsForType
1382
2108
  ...(returnType && { returnType }),
2109
+ ...(returnTypeResolved && { returnTypeResolved }),
2110
+ ...(futureReturn && { futureReturn }),
2111
+ ...(ownerGenericBounds && { ownerGenericBounds }),
2112
+ ...(ownerSelfArgs && { ownerSelfArgs }),
2113
+ ...(implSelfRef && { implSelfRef }),
2114
+ ...(implSelfQualifier && { implSelfQualifier }),
2115
+ ...(rustSelfParamKind(paramsNode) && { selfParamKind: rustSelfParamKind(paramsNode) }),
1383
2116
  ...(iteratorItemType && { iteratorItemType }),
1384
2117
  ...(docstring && { docstring }),
1385
2118
  // Method-level type params (fix #229): generic-param receiver
@@ -1441,10 +2174,74 @@ function parse(code, parser) {
1441
2174
  ...((tree.rootNode.hasError || declaration.macroItemRecovery) && {
1442
2175
  parseRecovery: true,
1443
2176
  }),
2177
+ ...(tree.rootNode.hasError && { parseErrorRegions: parseErrorRegions(tree.rootNode) }),
2178
+ ...(() => {
2179
+ const names = rustAsyncClosureNames(tree);
2180
+ return names.length > 0 ? { asyncClosureNames: names } : {};
2181
+ })(),
1444
2182
  imports: [], exports: [],
1445
2183
  };
1446
2184
  }
1447
2185
 
2186
+ const DECLARATION_NODE_TYPES = [
2187
+ 'function_item', 'foreign_mod_item', 'struct_item', 'union_item', 'enum_item', 'trait_item', 'impl_item',
2188
+ 'mod_item', 'macro_definition', 'type_item', 'const_item', 'static_item',
2189
+ ];
2190
+
2191
+ /**
2192
+ * Declarations of the given subtrees only, extracted exactly as parse()
2193
+ * extracts them (fix #374: the declarations a macro expansion generates,
2194
+ * without walking the rest of the expanded file).
2195
+ */
2196
+ function parseDeclarationsIn(code, roots) {
2197
+ const lines = code.split('\n');
2198
+ const functions = [], classes = [], stateObjects = [];
2199
+ const processedFn = new Set(), processedCls = new Set();
2200
+ for (const root of roots) {
2201
+ const nodes = DECLARATION_NODE_TYPES.includes(root.type) ? [root] : [];
2202
+ for (const node of nodes.concat(root.descendantsOfType(DECLARATION_NODE_TYPES))) {
2203
+ _processFunction(node, functions, processedFn, lines, code);
2204
+ _processClass(node, classes, processedCls, lines, code);
2205
+ _processState(node, stateObjects, lines);
2206
+ }
2207
+ }
2208
+ _postProcessTraitImpls(classes);
2209
+ functions.sort((a, b) => a.startLine - b.startLine);
2210
+ classes.sort((a, b) => a.startLine - b.startLine);
2211
+ stateObjects.sort((a, b) => a.startLine - b.startLine);
2212
+ return { language: 'rust', totalLines: lines.length, functions, classes, stateObjects, imports: [], exports: [] };
2213
+ }
2214
+
2215
+ /**
2216
+ * Local names bound to closures whose body is an async block
2217
+ * (`let f = || async { .. }`, fix #370): calling one creates a future.
2218
+ */
2219
+ function rustAsyncClosureNames(tree) {
2220
+ const names = new Set();
2221
+ // The flat node list the declaration pass built for this tree holds the
2222
+ // same nodes in document order (fix #388: no second native walk).
2223
+ const range = cachedNodeRange(tree.rootNode);
2224
+ let blocks;
2225
+ if (range) {
2226
+ blocks = [];
2227
+ const { nodes, subtreeEnds, index } = range;
2228
+ for (let i = index, end = subtreeEnds[index]; i < end; i++) {
2229
+ if (nodes[i].type === 'async_block') blocks.push(nodes[i]);
2230
+ }
2231
+ } else {
2232
+ blocks = tree.rootNode.descendantsOfType('async_block');
2233
+ }
2234
+ for (const block of blocks) {
2235
+ const closure = block.parent;
2236
+ if (closure?.type !== 'closure_expression' || !sameNode(closure.childForFieldName('body'), block)) continue;
2237
+ const binding = closure.parent;
2238
+ if (binding?.type !== 'let_declaration') continue;
2239
+ const pattern = binding.childForFieldName('pattern');
2240
+ if (pattern?.type === 'identifier') names.add(pattern.text);
2241
+ }
2242
+ return [...names].sort();
2243
+ }
2244
+
1448
2245
  /**
1449
2246
  * Walk a Rust call chain to find its root constructor type.
1450
2247
  *
@@ -1544,32 +2341,267 @@ function extractTypeName(typeNode) {
1544
2341
  typeNode.type === 'primitive_type') {
1545
2342
  return typeNode.text;
1546
2343
  }
1547
- if (typeNode.type === 'reference_type') {
1548
- // &Filter or &mut Filter -> Filter
1549
- for (let i = 0; i < typeNode.namedChildCount; i++) {
1550
- const r = extractTypeName(typeNode.namedChild(i));
1551
- if (r) return r;
2344
+ if (typeNode.type === 'reference_type') {
2345
+ // &Filter or &mut Filter -> Filter
2346
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
2347
+ const r = extractTypeName(typeNode.namedChild(i));
2348
+ if (r) return r;
2349
+ }
2350
+ }
2351
+ if (typeNode.type === 'abstract_type' || typeNode.type === 'dynamic_type') {
2352
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
2353
+ const r = extractTypeName(typeNode.namedChild(i));
2354
+ if (r) return r;
2355
+ }
2356
+ }
2357
+ if (typeNode.type === 'generic_type') {
2358
+ // Box<Filter> -> Filter (or get the outer type)
2359
+ return extractTypeName(typeNode.namedChild(0));
2360
+ }
2361
+ if (typeNode.type === 'scoped_type_identifier') {
2362
+ // module::Type -> Type
2363
+ const nameNode = typeNode.childForFieldName('name');
2364
+ return nameNode?.text || null;
2365
+ }
2366
+ return null;
2367
+ }
2368
+
2369
+
2370
+ // Argument type descriptors (fix #384): impls of one generic trait for one
2371
+ // self type (`impl From<Bytes> for Vec<u8>`, `impl From<BytesMut> for
2372
+ // Vec<u8>`) are selected by argument type, so method and path calls record
2373
+ // what each argument's type is known to be: '<refs><base>' where refs is a
2374
+ // run of '&' / '&mut ' and base a type name the site proves (a declared
2375
+ // parameter or local, a struct literal), '?<start>' for a local bound from
2376
+ // the path call starting at byte <start> (its declared return type decides
2377
+ // at query time), 'str'/'char'/'bool'/a suffixed numeric type for literals,
2378
+ // '#int'/'#float' for unsuffixed numeric literals, '#array' for a byte
2379
+ // string's array, '#slice' for a range-indexed slice; null when unknown.
2380
+ const RUST_NUMERIC_TYPES = ['i8', 'i16', 'i32', 'i64', 'i128', 'isize',
2381
+ 'u8', 'u16', 'u32', 'u64', 'u128', 'usize', 'f32', 'f64'];
2382
+
2383
+ function rustLiteralArgType(node) {
2384
+ switch (node.type) {
2385
+ case 'string_literal':
2386
+ case 'raw_string_literal':
2387
+ return node.text.startsWith('b') ? '&#array:u8' : node.text.startsWith('c') ? null : '&str';
2388
+ case 'char_literal':
2389
+ return node.text.startsWith('b') ? 'u8' : 'char';
2390
+ case 'boolean_literal':
2391
+ return 'bool';
2392
+ case 'integer_literal':
2393
+ case 'float_literal': {
2394
+ const text = node.text.replace(/_/g, '');
2395
+ if (!/^[0-9]/.test(text) || /^0[xob]/i.test(text)) {
2396
+ return node.type === 'integer_literal' ? '#int' : '#float';
2397
+ }
2398
+ const suffix = RUST_NUMERIC_TYPES.find(type => text.endsWith(type) &&
2399
+ /[0-9.]$/.test(text.slice(0, -type.length)));
2400
+ return suffix || (node.type === 'integer_literal' ? '#int' : '#float');
2401
+ }
2402
+ default:
2403
+ return null;
2404
+ }
2405
+ }
2406
+
2407
+ /**
2408
+ * Base of a range index (`&s[..]`, `&v[1..]`): 'str', '#slice:u8' for a
2409
+ * byte string, '#slice' when the element type is unknown.
2410
+ */
2411
+ function rustRangeIndexBase(container, typeOfName) {
2412
+ if (!container) return null;
2413
+ if (container.type === 'string_literal' || container.type === 'raw_string_literal') {
2414
+ return container.text.startsWith('b') ? '#slice:u8' : 'str';
2415
+ }
2416
+ if (container.type !== 'identifier') return null;
2417
+ const known = typeOfName(container.text, container);
2418
+ const base = known ? known.replace(/^(?:&mut |&)+/, '') : null;
2419
+ if (base === 'String' || base === 'str') return 'str';
2420
+ if (base === 'Vec' || base?.startsWith('#slice') || base?.startsWith('#array')) {
2421
+ return base?.includes(':') ? `#slice:${base.split(':')[1]}` : '#slice';
2422
+ }
2423
+ return null;
2424
+ }
2425
+
2426
+ /**
2427
+ * A local's type as an argument descriptor: declared (parameter, annotated
2428
+ * `let`), constructed (struct literal) or literal types with their
2429
+ * reference layer; a local bound from a path call as '?<start>'.
2430
+ */
2431
+ function rustKnownArgNameType(name, atNode, getReceiverType, isPatternShadow, tokenTypes) {
2432
+ // One evidence lookup (it checks pattern shadows itself); a declared or
2433
+ // constructed type carries its name in the evidence.
2434
+ let fields = getReceiverType?.(name, atNode, true);
2435
+ let type = fields?.receiverTypeEvidence?.type;
2436
+ if (!fields && tokenTypes?.has(name) && !isPatternShadow?.(atNode, name)) {
2437
+ type = tokenTypes.get(name);
2438
+ fields = tokenTypes.fields(name, type);
2439
+ }
2440
+ if (!type || !fields) return null;
2441
+ const source = fields.receiverTypeSource;
2442
+ const evidence = fields.receiverTypeEvidence;
2443
+ // A value behind a std wrapper is passed as the wrapper itself (fix #401).
2444
+ if (Array.isArray(evidence?.derefVia) && evidence.derefVia.length > 0) {
2445
+ const wrapper = String(evidence.derefVia[0]).replace(/^=/, '').split('::').pop();
2446
+ const outer = evidence.derefOuterRef || 'owned';
2447
+ return (outer === '&' ? '&' : outer === '&mut' ? '&mut ' : '') + wrapper;
2448
+ }
2449
+ if (source === 'guess') {
2450
+ return evidence?.nodeType === 'call_expression' && Number.isInteger(evidence.start)
2451
+ ? `?${evidence.start}` : null;
2452
+ }
2453
+ if (source !== 'annotation' && source !== 'constructor' && source !== 'literal') return null;
2454
+ const ref = fields.receiverTypeRef;
2455
+ if (!ref) return null;
2456
+ const base = type === 'slice' ? '#slice' : type;
2457
+ return (ref === '&' ? '&' : ref === '&mut' ? '&mut ' : '') + base;
2458
+ }
2459
+
2460
+ function rustArgTypeOfNode(argNode, typeOfName) {
2461
+ let node = argNode;
2462
+ let refs = '';
2463
+ let type = node?.type;
2464
+ while (node) {
2465
+ if (type === 'parenthesized_expression') {
2466
+ node = node.namedChild(0);
2467
+ } else if (type === 'reference_expression') {
2468
+ refs += node.namedChildren.some(child => child.type === 'mutable_specifier') ? '&mut ' : '&';
2469
+ node = node.childForFieldName('value');
2470
+ } else {
2471
+ break;
2472
+ }
2473
+ type = node?.type;
2474
+ }
2475
+ if (!node) return null;
2476
+ if (type === 'identifier') {
2477
+ const known = typeOfName(node.text, node);
2478
+ return known ? refs + known : null;
2479
+ }
2480
+ const literal = rustLiteralArgType(node);
2481
+ if (literal) return refs + literal;
2482
+ if (type === 'identifier') {
2483
+ const known = typeOfName(node.text, node);
2484
+ return known ? refs + known : null;
2485
+ }
2486
+ if (node.type === 'struct_expression') {
2487
+ const nameNode = node.childForFieldName('name');
2488
+ let name = nameNode?.type === 'scoped_type_identifier'
2489
+ ? nameNode.childForFieldName('name')?.text
2490
+ : nameNode?.type === 'type_identifier' ? nameNode.text : null;
2491
+ if (name === 'Self') name = findEnclosingImplType(node) || null;
2492
+ return name ? refs + name : null;
2493
+ }
2494
+ if (node.type === 'index_expression' && node.namedChild(1)?.type === 'range_expression') {
2495
+ const base = rustRangeIndexBase(node.namedChild(0), typeOfName);
2496
+ return base ? refs + base : null;
2497
+ }
2498
+ // A value produced by a macro or a path call (`vec![..]`,
2499
+ // `Bytes::from(v)`): its record at this byte decides at query time.
2500
+ if (node.type === 'macro_invocation' ||
2501
+ (node.type === 'call_expression' && node.childForFieldName('function')?.type === 'scoped_identifier')) {
2502
+ return `${refs}?${node.startIndex}`;
2503
+ }
2504
+ // `x.clone()` of an owned local has the local's type.
2505
+ if (node.type === 'call_expression' && node.childForFieldName('arguments')?.namedChildCount === 0) {
2506
+ const callee = node.childForFieldName('function');
2507
+ const object = callee?.type === 'field_expression' ? callee.childForFieldName('value') : null;
2508
+ if (callee?.childForFieldName('field')?.text === 'clone' && object?.type === 'identifier') {
2509
+ const known = typeOfName(object.text, object);
2510
+ return known && !known.startsWith('&') ? refs + known : null;
2511
+ }
2512
+ }
2513
+ return null;
2514
+ }
2515
+
2516
+ function rustArgTypesOfNode(argNodes, typeOfName) {
2517
+ if (argNodes.length === 0) return null;
2518
+ let types = null;
2519
+ for (let i = 0; i < argNodes.length; i++) {
2520
+ const descriptor = rustArgTypeOfNode(argNodes[i], typeOfName);
2521
+ if (descriptor) {
2522
+ if (!types) types = new Array(argNodes.length).fill(null);
2523
+ types[i] = descriptor;
2524
+ }
2525
+ }
2526
+ return types;
2527
+ }
2528
+
2529
+ /** The same descriptors read from a macro argument token tree. */
2530
+ function rustArgTypesOfTokens(argsTree, typeOfName) {
2531
+ if (!argsTree) return null;
2532
+ const children = argsTree.children;
2533
+ let types = null;
2534
+ let index = 0;
2535
+ let start = 1; // skip `(`
2536
+ for (let i = 1; i <= children.length - 1; i++) {
2537
+ if (i < children.length - 1 && children[i].type !== ',') continue;
2538
+ if (i > start) {
2539
+ const descriptor = rustTokenSegmentType(children, start, i, typeOfName);
2540
+ if (descriptor) {
2541
+ if (!types) types = [];
2542
+ types[index] = descriptor;
2543
+ }
2544
+ }
2545
+ index++;
2546
+ start = i + 1;
2547
+ }
2548
+ if (!types) return null;
2549
+ for (let i = 0; i < index; i++) if (types[i] === undefined) types[i] = null;
2550
+ types.length = Math.min(types.length, index);
2551
+ return types;
2552
+ }
2553
+
2554
+ /** Descriptor of one macro argument: the tokens children[from..to). */
2555
+ function rustTokenSegmentType(children, from, to, typeOfName) {
2556
+ let refs = '';
2557
+ let at = from;
2558
+ while (at < to && (children[at].type === '&' || children[at].type === '&&')) {
2559
+ const layers = children[at].type === '&&' ? 2 : 1;
2560
+ const mutable = at + 1 < to && children[at + 1].type === 'mutable_specifier';
2561
+ refs += '&'.repeat(layers - 1) + (mutable ? '&mut ' : '&');
2562
+ at += mutable ? 2 : 1;
2563
+ }
2564
+ const count = to - at;
2565
+ const first = children[at];
2566
+ if (count === 1) {
2567
+ if (first.type === 'identifier') {
2568
+ const known = typeOfName(first.text, first);
2569
+ return known ? refs + known : null;
1552
2570
  }
2571
+ const literal = rustLiteralArgType(first);
2572
+ return literal ? refs + literal : null;
1553
2573
  }
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;
2574
+ if (count === 2 && children[at + 1].type === 'token_tree') {
2575
+ const group = children[at + 1];
2576
+ const text = group.text;
2577
+ if (text.startsWith('[')) {
2578
+ let ranged = false;
2579
+ for (const token of group.children) if (token.type === '..') { ranged = true; break; }
2580
+ if (!ranged) return null;
2581
+ const base = rustRangeIndexBase(first, typeOfName);
2582
+ return base ? refs + base : null;
1558
2583
  }
2584
+ if (text.startsWith('{') && first.type === 'identifier') {
2585
+ const name = first.text === 'Self' ? findEnclosingImplType(first) : first.text;
2586
+ return name ? refs + name : null;
2587
+ }
2588
+ return null;
1559
2589
  }
1560
- if (typeNode.type === 'generic_type') {
1561
- // Box<Filter> -> Filter (or get the outer type)
1562
- return extractTypeName(typeNode.namedChild(0));
2590
+ // `vec![..]` / `name!(..)`: the nested macro's own record.
2591
+ if (count === 3 && first.type === 'identifier' && children[at + 1].type === '!' &&
2592
+ children[at + 2].type === 'token_tree') {
2593
+ return `${refs}?${first.startIndex}`;
1563
2594
  }
1564
- if (typeNode.type === 'scoped_type_identifier') {
1565
- // module::Type -> Type
1566
- const nameNode = typeNode.childForFieldName('name');
1567
- return nameNode?.text || null;
2595
+ // `x.clone()` of an owned local.
2596
+ if (count === 4 && first.type === 'identifier' && children[at + 1].type === '.' &&
2597
+ children[at + 2].text === 'clone' && children[at + 3].type === 'token_tree' &&
2598
+ children[at + 3].text.replace(/\s+/g, '') === '()') {
2599
+ const known = typeOfName(first.text, first);
2600
+ return known && !known.startsWith('&') ? refs + known : null;
1568
2601
  }
1569
2602
  return null;
1570
2603
  }
1571
2604
 
1572
-
1573
2605
  // Shared by ordinary AST calls and macro token-tree calls. Both carry the
1574
2606
  // same self-field owner identity; a field spelling is never a local receiver.
1575
2607
  function findEnclosingImplType(node) {
@@ -1629,6 +2661,9 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1629
2661
  i = close;
1630
2662
  }
1631
2663
  let lastProducer = null;
2664
+ const scopedName = typeof context === 'object' ? context.scopeTypesName : null;
2665
+ const argTypeOfName = (name, atNode) => (!scopedName || scopedName(name) || inheritedTokenTypes.has(name))
2666
+ ? rustKnownArgNameType(name, atNode, getReceiverType, isPatternShadow, inheritedTokenTypes) : null;
1632
2667
  const macroFields = {
1633
2668
  inMacro: true,
1634
2669
  ...(contextKind === 'definition' && { inMacroDefinition: true }),
@@ -1643,6 +2678,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1643
2678
  kind: contextKind,
1644
2679
  ...(containerMacro && { containerMacro }),
1645
2680
  tokenTypes,
2681
+ ...(scopedName && { scopeTypesName: scopedName }),
1646
2682
  });
1647
2683
  continue;
1648
2684
  }
@@ -1725,6 +2761,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1725
2761
  startNode = children[k];
1726
2762
  j = k - 1;
1727
2763
  }
2764
+ const tokenArgTypes = segments.length > 0 ? rustArgTypesOfTokens(callArgs, argTypeOfName) : null;
1728
2765
  const record = {
1729
2766
  name: tok.text,
1730
2767
  line: tok.startPosition.row + 1,
@@ -1733,6 +2770,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1733
2770
  isMethod: segments.length > 0,
1734
2771
  isPathCall: true,
1735
2772
  receiver: segments.length > 0 ? segments.join('::') : undefined,
2773
+ ...(tokenArgTypes && { argTypes: tokenArgTypes }),
1736
2774
  ...macroFields,
1737
2775
  enclosingFunction
1738
2776
  };
@@ -1756,9 +2794,14 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1756
2794
  }
1757
2795
  // Literal receivers type as builtins inside macros too (fix #220,
1758
2796
  // ripgrep-measured: assert_eq!(.., vec!["match:fg".parse()...]))
2797
+ const groupedReceiver = recvTok?.type === 'token_tree' &&
2798
+ rustTokenTreeIsGroupedExpression(children, i - 2);
2799
+ const rangeType = groupedReceiver
2800
+ ? (rustTokenTreeRangeType(recvTok) || rustTokenTreeTupleType(recvTok)) : null;
1759
2801
  const litType = recvTok
1760
- ? ({ string_literal: 'str', raw_string_literal: 'str',
1761
- char_literal: 'char', boolean_literal: 'bool' })[recvTok.type]
2802
+ ? (({ string_literal: 'str', raw_string_literal: 'str',
2803
+ char_literal: 'char', boolean_literal: 'bool' })[recvTok.type] ||
2804
+ rangeType || undefined)
1762
2805
  : undefined;
1763
2806
  const receiverRootType = receiverRoot === 'self'
1764
2807
  ? findEnclosingImplType(tok) : getReceiverType?.(receiverRoot, tok);
@@ -1771,16 +2814,21 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1771
2814
  const patternSource = rustPatternBindingOf(tok, receiver);
1772
2815
  const producer = !receiver && lastProducer &&
1773
2816
  lastProducer.callEnd === recvTok?.endIndex ? lastProducer : null;
2817
+ const tokenArgTypes = rustArgTypesOfTokens(callArgs, argTypeOfName);
1774
2818
  const record = {
1775
2819
  name: tok.text,
1776
2820
  line: tok.startPosition.row + 1,
1777
2821
  callStart: producer?.callStart ?? recvTok?.startIndex ?? tok.startIndex,
1778
2822
  callEnd: callArgs.endIndex,
1779
2823
  isMethod: true,
2824
+ ...(tokenArgTypes && { argTypes: tokenArgTypes }),
1780
2825
  receiver: receiverField ? undefined : receiver,
1781
2826
  ...(receiverField && { receiverRoot, receiverField }),
1782
2827
  ...(receiverRootType && { receiverRootType }),
1783
- ...(receiverType && { receiverType, ...(getReceiverType?.(receiver, tok, true) || inheritedTokenTypes.fields(receiver, receiverType)) }),
2828
+ ...(receiverType && { receiverType, ...((rangeType && receiverType === rangeType && !receiver)
2829
+ ? { receiverTypeSource: 'literal', receiverTypeEvidence: typeOrigin('literal', recvTok),
2830
+ receiverTypeStd: true, receiverTypeRef: 'owned' }
2831
+ : (getReceiverType?.(receiver, tok, true) || inheritedTokenTypes.fields(receiver, receiverType))) }),
1784
2832
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
1785
2833
  ...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
1786
2834
  ...(iterationSource || {}),
@@ -1799,6 +2847,11 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1799
2847
  calls.push(record);
1800
2848
  lastProducer = record;
1801
2849
  } else {
2850
+ // `fn name(...)` inside a macro token tree is a DEFINITION
2851
+ // template (deref-forwarding / impl-generating macros), not a
2852
+ // call (fix #360): recording it as a call listed the definition
2853
+ // line as a caller of every same-name function.
2854
+ if (prev && prev.type === 'fn') continue;
1802
2855
  // Plain call: func(...) — includes enum-variant constructors
1803
2856
  const record = {
1804
2857
  name: tok.text,
@@ -1903,11 +2956,49 @@ function rustAssignmentTargetOf(callNode) {
1903
2956
  const bindings = pattern.namedChildren
1904
2957
  .filter(child => child.type === 'identifier')
1905
2958
  .map(child => child.text);
2959
+ // Positional targets (fix #368): `let (left, right, _) = c.split_at(i)`
2960
+ // binds element i of the declared tuple return to each name.
2961
+ // `_` and non-identifier sub-patterns keep their position but
2962
+ // bind nothing typed.
2963
+ const tupleTargets = [];
2964
+ let position = 0;
2965
+ let positional = true;
2966
+ for (let i = 0; i < pattern.childCount; i++) {
2967
+ const child = pattern.child(i);
2968
+ if (['(', ')', ','].includes(child.type)) continue;
2969
+ if (child.type.endsWith('comment')) continue;
2970
+ if (child.type === 'remaining_field_pattern' || child.text === '..') {
2971
+ positional = false;
2972
+ break;
2973
+ }
2974
+ let binding = child;
2975
+ // `mut x`: the pattern's named children are the
2976
+ // `mutable_specifier` and then the identifier (fix #369).
2977
+ if (binding.type === 'mut_pattern') {
2978
+ binding = binding.namedChildren.find(c => c.type === 'identifier') || null;
2979
+ }
2980
+ if (binding?.type === 'identifier') {
2981
+ tupleTargets.push({ name: binding.text, index: position });
2982
+ }
2983
+ position++;
2984
+ }
1906
2985
  if (bindings.length === pattern.namedChildCount && bindings.length > 0) {
1907
2986
  return {
1908
2987
  assignedTo: bindings[0],
1909
2988
  tuple: true,
1910
2989
  ...(bindings.length > 1 && { tupleRest: bindings.slice(1) }),
2990
+ ...(positional && tupleTargets.length > 0 && { tupleTargets }),
2991
+ ...(unwrapped && { unwrapped: true }),
2992
+ };
2993
+ }
2994
+ if (positional && tupleTargets.length > 0) {
2995
+ return {
2996
+ assignedTo: tupleTargets[0].name,
2997
+ tuple: true,
2998
+ ...(tupleTargets.length > 1 && {
2999
+ tupleRest: tupleTargets.slice(1).map(target => target.name),
3000
+ }),
3001
+ tupleTargets,
1911
3002
  ...(unwrapped && { unwrapped: true }),
1912
3003
  };
1913
3004
  }
@@ -1924,6 +3015,114 @@ function rustAssignmentTargetOf(callNode) {
1924
3015
  return undefined;
1925
3016
  }
1926
3017
 
3018
+ /**
3019
+ * Range expressions have a language-fixed type (fix #368): `a..b` is
3020
+ * `std::ops::Range`, `a..=b` is `RangeInclusive`, `a..` / `..b` / `..=b` /
3021
+ * `..` are RangeFrom / RangeTo / RangeToInclusive / RangeFull. A method call
3022
+ * on a parenthesized range therefore dispatches on that std type exactly
3023
+ * like a string literal dispatches on `str`.
3024
+ */
3025
+ function rustRangeTypeName(operator, hasStart, hasEnd) {
3026
+ if (operator === '..=' || operator === '...') {
3027
+ if (!hasEnd) return null;
3028
+ return hasStart ? 'RangeInclusive' : 'RangeToInclusive';
3029
+ }
3030
+ if (operator !== '..') return null;
3031
+ if (hasStart && hasEnd) return 'Range';
3032
+ if (hasStart) return 'RangeFrom';
3033
+ if (hasEnd) return 'RangeTo';
3034
+ return 'RangeFull';
3035
+ }
3036
+
3037
+ function rustRangeLiteralType(node, allowBare = false) {
3038
+ let current = node;
3039
+ while (current?.type === 'parenthesized_expression' && current.namedChildCount === 1) {
3040
+ current = current.namedChild(0);
3041
+ }
3042
+ // `0..8.len()` parses as a range whose END is the call; only a
3043
+ // parenthesized range is a method receiver. A `let` value may be bare.
3044
+ if (current?.type !== 'range_expression' || (current === node && !allowBare)) return null;
3045
+ let operator = null;
3046
+ let operatorIndex = -1;
3047
+ for (let i = 0; i < current.childCount; i++) {
3048
+ const child = current.child(i);
3049
+ if (!child.isNamed && ['..', '..=', '...'].includes(child.type)) {
3050
+ operator = child.type;
3051
+ operatorIndex = child.startIndex;
3052
+ break;
3053
+ }
3054
+ }
3055
+ if (!operator) return null;
3056
+ let hasStart = false;
3057
+ let hasEnd = false;
3058
+ for (let i = 0; i < current.namedChildCount; i++) {
3059
+ const operand = current.namedChild(i);
3060
+ if (operand.type.endsWith('comment')) continue;
3061
+ if (operand.startIndex < operatorIndex) hasStart = true;
3062
+ else hasEnd = true;
3063
+ }
3064
+ return rustRangeTypeName(operator, hasStart, hasEnd);
3065
+ }
3066
+
3067
+ /**
3068
+ * Token-tree twin of rustRangeLiteralType for macro arguments
3069
+ * (`assert_eq!(4, (0..8).len())`): a parenthesized token tree holding exactly
3070
+ * one top-level range operator and nothing that binds looser than a range
3071
+ * (comma, assignment, closure bars, statement or control keywords).
3072
+ */
3073
+ /**
3074
+ * A parenthesized token tree is a grouped EXPRESSION only when nothing that
3075
+ * makes it an argument list precedes it (`f(0..8)`, `m!(..)`, `x[..](..)`).
3076
+ */
3077
+ const RUST_TOKEN_EXPRESSION_PRECEDERS = new Set(['(', '[', '{', ',', ';', '=', '=>', '&',
3078
+ '&&', '||', '+', '-', '*', '/', '%', '==', '!=', '<', '<=', '>=', 'return', 'in', 'move', '|']);
3079
+ function rustTokenTreeIsGroupedExpression(children, index) {
3080
+ const previous = children[index - 1];
3081
+ return !previous || RUST_TOKEN_EXPRESSION_PRECEDERS.has(previous.type);
3082
+ }
3083
+
3084
+ /**
3085
+ * Tuple expressions have the language's tuple type (fix #368): `(a, b)` and
3086
+ * `(a,)`. The canonical receiver name is 'tuple'.
3087
+ */
3088
+ function rustTokenTreeTupleType(tokenTree) {
3089
+ if (tokenTree?.type !== 'token_tree' || tokenTree.child(0)?.type !== '(' ||
3090
+ tokenTree.child(tokenTree.childCount - 1)?.type !== ')') return null;
3091
+ let commas = 0;
3092
+ let values = 0;
3093
+ for (let i = 1; i < tokenTree.childCount - 1; i++) {
3094
+ const token = tokenTree.child(i);
3095
+ if (token.type.endsWith('comment')) continue;
3096
+ if (['..', '..=', '...', ';', '=', '|', '||'].includes(token.type)) return null;
3097
+ if (token.type === ',') commas++;
3098
+ else values++;
3099
+ }
3100
+ return commas > 0 && values > 0 ? 'tuple' : null;
3101
+ }
3102
+
3103
+ function rustTokenTreeRangeType(tokenTree) {
3104
+ if (tokenTree?.type !== 'token_tree' || tokenTree.child(0)?.type !== '(') return null;
3105
+ const inner = [];
3106
+ for (let i = 1; i < tokenTree.childCount - 1; i++) inner.push(tokenTree.child(i));
3107
+ if (tokenTree.child(tokenTree.childCount - 1)?.type !== ')') return null;
3108
+ const LOOSER = new Set([',', ';', '=', '|', '||', '+=', '-=', '*=', '/=', '%=',
3109
+ '^=', '&=', '|=', '<<=', '>>=', 'return', 'break', 'continue', 'let', 'move']);
3110
+ let operatorIndex = -1;
3111
+ for (let i = 0; i < inner.length; i++) {
3112
+ const token = inner[i];
3113
+ if (LOOSER.has(token.type)) return null;
3114
+ if (['..', '..=', '...'].includes(token.type)) {
3115
+ if (operatorIndex >= 0) return null;
3116
+ operatorIndex = i;
3117
+ }
3118
+ }
3119
+ if (operatorIndex < 0) return null;
3120
+ const isComment = token => token.type.endsWith('comment');
3121
+ const hasStart = inner.slice(0, operatorIndex).some(token => !isComment(token));
3122
+ const hasEnd = inner.slice(operatorIndex + 1).some(token => !isComment(token));
3123
+ return rustRangeTypeName(inner[operatorIndex].type, hasStart, hasEnd);
3124
+ }
3125
+
1927
3126
  function rustCallIdentity(callNode) {
1928
3127
  if (!callNode || callNode.type !== 'call_expression') return null;
1929
3128
  const fn = callNode.childForFieldName('function');
@@ -2118,6 +3317,38 @@ function patternContainsIdentifier(pattern, name) {
2118
3317
  return false;
2119
3318
  }
2120
3319
 
3320
+ /**
3321
+ * Syntactic context a macro invocation's expansion is parsed in: 'items'
3322
+ * (module or impl/trait body), 'stmts' (statement or block tail), 'expr', or
3323
+ * null where no expansion can stand (patterns, types).
3324
+ */
3325
+ function rustMacroInvocationContext(node) {
3326
+ const parent = node.parent;
3327
+ if (!parent) return null;
3328
+ switch (parent.type) {
3329
+ case 'source_file':
3330
+ return 'items';
3331
+ case 'declaration_list':
3332
+ return 'items';
3333
+ case 'expression_statement':
3334
+ case 'block':
3335
+ return 'stmts';
3336
+ default:
3337
+ break;
3338
+ }
3339
+ if (/pattern/.test(parent.type)) return null;
3340
+ for (const field of ['type', 'return_type', 'trait']) {
3341
+ const typed = parent.childForFieldName(field);
3342
+ if (typed && typed.startIndex === node.startIndex && typed.endIndex === node.endIndex) return null;
3343
+ }
3344
+ if (parent.type === 'type_arguments') return null;
3345
+ if (parent.type === 'match_arm') {
3346
+ const value = parent.childForFieldName('value');
3347
+ return value && value.startIndex === node.startIndex && value.endIndex === node.endIndex ? 'expr' : null;
3348
+ }
3349
+ return 'expr';
3350
+ }
3351
+
2121
3352
  function rustMacroCallIdentity(macroNode) {
2122
3353
  if (!macroNode) return null;
2123
3354
  const parts = macroNode.text.replace(/!$/, '').split('::').filter(Boolean);
@@ -2131,9 +3362,391 @@ function rustMacroCallIdentity(macroNode) {
2131
3362
  };
2132
3363
  }
2133
3364
 
3365
+ /**
3366
+ * Is this expression the value the enclosing fn item returns (fix #369)?
3367
+ * The fn body's tail expression, or the operand of a `return` whose nearest
3368
+ * function-like ancestor is that fn item (a closure boundary stops it). The
3369
+ * value then has the fn's declared return type, which lets a trait path call
3370
+ * (`Trait::assoc(..)`) infer its Self from the declaration.
3371
+ */
3372
+ function rustFunctionReturnPosition(node) {
3373
+ const parent = node.parent;
3374
+ if (!parent) return false;
3375
+ if (parent.type === 'return_expression') {
3376
+ for (let p = parent.parent; p; p = p.parent) {
3377
+ if (p.type === 'closure_expression') return false;
3378
+ if (p.type === 'function_item') return true;
3379
+ }
3380
+ return false;
3381
+ }
3382
+ if (parent.type !== 'block' || parent.parent?.type !== 'function_item') return false;
3383
+ const last = parent.namedChild(parent.namedChildCount - 1);
3384
+ return !!last && sameNode(last, node);
3385
+ }
3386
+
3387
+ /**
3388
+ * Does a local value binding shadow a bare callee name at this call (fix
3389
+ * #369)? `let bridge = bridge_impl; join(|c| bridge(c))` calls the local
3390
+ * function value, never the module item `bridge`. Walks the lexical scopes
3391
+ * outward to the enclosing fn item: `let` patterns of earlier statements in
3392
+ * each block, closure / fn parameters, `for` patterns, `if let` / `while let`
3393
+ * conditions and match-arm patterns that enclose the call. Items (`fn`
3394
+ * declared in a block) are not value bindings and do not count.
3395
+ */
3396
+ function rustPatternBindsName(pattern, name) {
3397
+ if (!pattern) return false;
3398
+ const pending = [pattern];
3399
+ while (pending.length > 0) {
3400
+ const current = pending.pop();
3401
+ if (current.type === 'identifier') {
3402
+ if (current.text === name) return true;
3403
+ continue;
3404
+ }
3405
+ // Type annotations, paths and literals inside a pattern bind nothing.
3406
+ if (/type|scoped_identifier|field_identifier|literal/.test(current.type) &&
3407
+ current.type !== 'tuple_struct_pattern') continue;
3408
+ if (current.type === 'tuple_struct_pattern' || current.type === 'struct_pattern') {
3409
+ // The first child is the constructor path, never a binding.
3410
+ for (let i = 1; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
3411
+ continue;
3412
+ }
3413
+ if (current.type === 'field_pattern') {
3414
+ const shorthand = current.namedChildren.find(c => c.type === 'shorthand_field_identifier');
3415
+ if (shorthand?.text === name) return true;
3416
+ const inner = current.childForFieldName('pattern');
3417
+ if (inner) pending.push(inner);
3418
+ continue;
3419
+ }
3420
+ if (current.type === 'shorthand_field_identifier') {
3421
+ if (current.text === name) return true;
3422
+ continue;
3423
+ }
3424
+ for (let i = 0; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
3425
+ }
3426
+ return false;
3427
+ }
3428
+
3429
+ function rustBareNameShadowedByLocal(callNode, name) {
3430
+ let child = callNode;
3431
+ for (let p = callNode.parent; p; child = p, p = p.parent) {
3432
+ if (p.type === 'block') {
3433
+ for (let i = 0; i < p.namedChildCount; i++) {
3434
+ const statement = p.namedChild(i);
3435
+ if (statement.startIndex >= child.startIndex) break;
3436
+ if (statement.type === 'let_declaration' &&
3437
+ rustPatternBindsName(statement.childForFieldName('pattern'), name)) return true;
3438
+ }
3439
+ } else if (p.type === 'closure_expression') {
3440
+ if (rustPatternBindsName(p.childForFieldName('parameters'), name)) return true;
3441
+ } else if (p.type === 'function_item') {
3442
+ const params = p.childForFieldName('parameters');
3443
+ for (const param of params?.namedChildren || []) {
3444
+ if (param.type === 'parameter' &&
3445
+ rustPatternBindsName(param.childForFieldName('pattern'), name)) return true;
3446
+ }
3447
+ return false;
3448
+ } else if (p.type === 'for_expression') {
3449
+ const body = p.childForFieldName('body');
3450
+ if (body && sameNode(body, child) &&
3451
+ rustPatternBindsName(p.childForFieldName('pattern'), name)) return true;
3452
+ } else if (p.type === 'match_arm') {
3453
+ const value = p.childForFieldName('value');
3454
+ if (value && sameNode(value, child) &&
3455
+ rustPatternBindsName(p.childForFieldName('pattern'), name)) return true;
3456
+ } else if (p.type === 'if_expression' || p.type === 'while_expression') {
3457
+ const condition = p.childForFieldName('condition');
3458
+ const consequence = p.childForFieldName('consequence') || p.childForFieldName('body');
3459
+ if (consequence && sameNode(consequence, child) && condition) {
3460
+ const lets = [condition];
3461
+ while (lets.length > 0) {
3462
+ const current = lets.pop();
3463
+ if (current.type === 'let_condition' &&
3464
+ rustPatternBindsName(current.childForFieldName('pattern'), name)) return true;
3465
+ if (current.type === 'let_chain') {
3466
+ for (let i = 0; i < current.namedChildCount; i++) lets.push(current.namedChild(i));
3467
+ }
3468
+ }
3469
+ }
3470
+ }
3471
+ }
3472
+ return false;
3473
+ }
3474
+
3475
+ const rustBindingFactsByTree = new WeakMap();
3476
+ const RUST_BINDING_HOLDERS = new Set(['let_declaration', 'parameter', 'closure_parameters',
3477
+ 'for_expression', 'let_condition', 'match_pattern', 'self_parameter']);
3478
+
3479
+ /**
3480
+ * Is `child` the binding of its holder rather than an expression inside it
3481
+ * (fix #389): the value of `let x = v;`, a parameter's type, a loop's
3482
+ * iterator or a match guard are not bindings.
3483
+ */
3484
+ function rustBindingPosition(parent, child) {
3485
+ const field = {
3486
+ let_declaration: 'pattern', parameter: 'pattern', for_expression: 'pattern',
3487
+ let_condition: 'pattern',
3488
+ }[parent.type];
3489
+ if (field) return sameNode(parent.childForFieldName(field), child);
3490
+ if (parent.type === 'match_pattern') return !sameNode(parent.childForFieldName('condition'), child);
3491
+ return true;
3492
+ }
3493
+
3494
+ /**
3495
+ * How many bindings of each name one function introduces (let patterns,
3496
+ * parameters, closure parameters, loop/match/if-let patterns; shadowing
3497
+ * `let` counts again) and which names are assigned (fix #381).
3498
+ */
3499
+ function rustBindingFacts(fnNode) {
3500
+ let byId = rustBindingFactsByTree.get(fnNode.tree);
3501
+ if (!byId) { byId = new Map(); rustBindingFactsByTree.set(fnNode.tree, byId); }
3502
+ let facts = byId.get(fnNode.id);
3503
+ if (facts) return facts;
3504
+ facts = { declared: new Map(), assigned: new Set() };
3505
+ const record = (child) => {
3506
+ if (child.type === 'identifier' || child.type === 'shorthand_field_identifier') {
3507
+ const parent = child.parent;
3508
+ if (parent && (RUST_BINDING_HOLDERS.has(parent.type) || parent.type.endsWith('pattern')) &&
3509
+ rustBindingPosition(parent, child)) {
3510
+ facts.declared.set(child.text, (facts.declared.get(child.text) || 0) + 1);
3511
+ }
3512
+ } else if (child.type === 'assignment_expression' || child.type === 'compound_assignment_expr') {
3513
+ const left = child.childForFieldName('left');
3514
+ if (left?.type === 'identifier') facts.assigned.add(left.text);
3515
+ }
3516
+ };
3517
+ // Every named descendant, from the flat node list when one is cached
3518
+ // (fix #388); the facts are counts and sets, so visit order is free.
3519
+ const range = cachedNodeRange(fnNode);
3520
+ if (range) {
3521
+ const { nodes, subtreeEnds, index } = range;
3522
+ for (let i = index + 1, end = subtreeEnds[index]; i < end; i++) record(nodes[i]);
3523
+ } else {
3524
+ const walk = (node) => {
3525
+ for (const child of node.namedChildren) {
3526
+ record(child);
3527
+ walk(child);
3528
+ }
3529
+ };
3530
+ walk(fnNode);
3531
+ }
3532
+ byId.set(fnNode.id, facts);
3533
+ return facts;
3534
+ }
3535
+
3536
+ // axum method routers (fix #383): `get(h)`, `post_service(svc)`, `any(h)`,
3537
+ // `on(MethodFilter::GET.or(MethodFilter::POST), h)`, chained with
3538
+ // `.post(h2)` and wrapped by combinators that return the same router.
3539
+ const RUST_METHOD_ROUTER_VERBS = {
3540
+ get: 'GET', post: 'POST', put: 'PUT', delete: 'DELETE', patch: 'PATCH', head: 'HEAD',
3541
+ options: 'OPTIONS', trace: 'TRACE', connect: 'CONNECT', any: 'ALL',
3542
+ };
3543
+ const RUST_METHOD_ROUTER_PASSTHROUGH = new Set(['layer', 'route_layer', 'with_state', 'handle_error']);
3544
+
3545
+ function rustRouteArgs(callNode) {
3546
+ const argsNode = callNode.childForFieldName('arguments');
3547
+ const args = [];
3548
+ for (let i = 0; argsNode && i < argsNode.namedChildCount; i++) {
3549
+ const arg = argsNode.namedChild(i);
3550
+ if (!arg.type.includes('comment') && arg.type !== 'attribute_item') args.push(arg);
3551
+ }
3552
+ return args;
3553
+ }
3554
+
3555
+ function rustHandlerName(node) {
3556
+ if (!node) return '<anonymous>';
3557
+ if (node.type === 'identifier') return node.text;
3558
+ if (node.type === 'scoped_identifier') return node.childForFieldName('name')?.text || node.text;
3559
+ if (node.type === 'generic_function') return rustHandlerName(node.childForFieldName('function'));
3560
+ if (node.type === 'field_expression') return node.childForFieldName('field')?.text || '<anonymous>';
3561
+ if (node.type === 'call_expression') return rustHandlerName(node.childForFieldName('function'));
3562
+ return '<anonymous>';
3563
+ }
3564
+
3565
+ /** Methods a `MethodFilter` expression selects, or null. */
3566
+ function rustMethodFilter(node) {
3567
+ if (!node) return null;
3568
+ if (node.type === 'scoped_identifier') {
3569
+ const name = node.childForFieldName('name')?.text;
3570
+ return name && /^[A-Z]+$/.test(name) ? [name] : null;
3571
+ }
3572
+ if (node.type === 'call_expression') {
3573
+ const fn = node.childForFieldName('function');
3574
+ if (fn?.type === 'field_expression' && fn.childForFieldName('field')?.text === 'or') {
3575
+ const left = rustMethodFilter(fn.childForFieldName('value'));
3576
+ const right = rustMethodFilter(rustRouteArgs(node)[0]);
3577
+ return left && right ? [...left, ...right] : null;
3578
+ }
3579
+ }
3580
+ return null;
3581
+ }
3582
+
3583
+ /** [{ method, handler }] a method-router expression serves, or null. */
3584
+ function rustMethodRouterEntries(node, depth = 0) {
3585
+ if (!node || depth > 32) return null;
3586
+ if (node.type === 'parenthesized_expression') return rustMethodRouterEntries(node.namedChild(0), depth + 1);
3587
+ if (node.type !== 'call_expression') return null;
3588
+ const fn = node.childForFieldName('function');
3589
+ const args = rustRouteArgs(node);
3590
+ const entriesFor = (name) => {
3591
+ const base = name.replace(/_service$/, '');
3592
+ if (RUST_METHOD_ROUTER_VERBS[base]) {
3593
+ return [{ method: RUST_METHOD_ROUTER_VERBS[base], handler: rustHandlerName(args[0]) }];
3594
+ }
3595
+ if (base === 'on') {
3596
+ const methods = rustMethodFilter(args[0]);
3597
+ return methods ? methods.map(method => ({ method, handler: rustHandlerName(args[1]) })) : null;
3598
+ }
3599
+ return null;
3600
+ };
3601
+ if (fn?.type === 'identifier' || fn?.type === 'scoped_identifier') {
3602
+ const name = fn.type === 'identifier' ? fn.text : fn.childForFieldName('name')?.text;
3603
+ return name ? entriesFor(name) : null;
3604
+ }
3605
+ if (fn?.type === 'field_expression') {
3606
+ const inner = rustMethodRouterEntries(fn.childForFieldName('value'), depth + 1);
3607
+ if (!inner) return null;
3608
+ const name = fn.childForFieldName('field')?.text || '';
3609
+ if (RUST_METHOD_ROUTER_PASSTHROUGH.has(name)) return inner;
3610
+ const added = entriesFor(name);
3611
+ return added ? [...inner, ...added] : null;
3612
+ }
3613
+ return null;
3614
+ }
3615
+
3616
+ /** axum `.route(path, <method router>)` / `.route_service(path, svc)`. */
3617
+ function rustMethodRouterOf(callNode, name) {
3618
+ const args = rustRouteArgs(callNode);
3619
+ if (args.length !== 2) return null;
3620
+ if (name === 'route_service') return [{ method: 'ALL', handler: rustHandlerName(args[1]) }];
3621
+ const entries = rustMethodRouterEntries(args[1]);
3622
+ if (!entries || entries.length === 0) return null;
3623
+ const seen = new Set();
3624
+ return entries.filter(entry => {
3625
+ const key = `${entry.method}\0${entry.handler}`;
3626
+ if (seen.has(key)) return false;
3627
+ seen.add(key);
3628
+ return true;
3629
+ });
3630
+ }
3631
+
3632
+ /**
3633
+ * Is a bare identifier in expression position an item path rather than a
3634
+ * local (fix #389)? A name no binding of the enclosing fn declares (let
3635
+ * patterns, parameters, closure parameters, match/for/if-let patterns) is
3636
+ * resolved in the item namespaces: a unit struct, a const, a variant... The
3637
+ * per-fn binding facts are a superset, so any binding of the name anywhere
3638
+ * in the fn keeps it a possible local.
3639
+ */
3640
+ function rustIdentifierIsItemPath(identNode) {
3641
+ let fnNode = null;
3642
+ for (let parent = identNode.parent; parent; parent = parent.parent) {
3643
+ if (parent.type === 'function_item') { fnNode = parent; break; }
3644
+ if (parent.type === 'token_tree' || parent.type === 'macro_invocation') return false;
3645
+ }
3646
+ if (!fnNode) return !rustBareNameShadowedByLocal(identNode, identNode.text);
3647
+ return !rustBindingFacts(fnNode).declared.has(identNode.text);
3648
+ }
3649
+
3650
+ /**
3651
+ * The item path a value receiver names (fix #389): a bare identifier that is
3652
+ * no local (`S.plain()`), a path (`E::Unit.m()`, `m::S.m()`), a struct
3653
+ * expression (`P { x }.m()`), or a once-bound immutable local holding one of
3654
+ * those (`let w = S; w.m()`). Returns { path, kind } or null.
3655
+ */
3656
+ function rustReceiverValuePath(valueNode) {
3657
+ if (!valueNode) return null;
3658
+ if (valueNode.type === 'scoped_identifier') {
3659
+ return /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)+$/.test(valueNode.text)
3660
+ ? { path: valueNode.text, kind: 'value' } : null;
3661
+ }
3662
+ if (valueNode.type === 'struct_expression') {
3663
+ let nameNode = valueNode.childForFieldName('name');
3664
+ if (nameNode?.type === 'generic_type' || nameNode?.type === 'generic_type_with_turbofish') {
3665
+ nameNode = nameNode.childForFieldName('type');
3666
+ }
3667
+ const text = nameNode?.text?.replace(/\s+/g, '');
3668
+ return text && /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*$/.test(text)
3669
+ ? { path: text, kind: 'struct' } : null;
3670
+ }
3671
+ if (valueNode.type !== 'identifier') return null;
3672
+ if (rustIdentifierIsItemPath(valueNode)) return { path: valueNode.text, kind: 'value' };
3673
+ // `let w = S;` (bound once, never assigned, not `mut`): w holds S.
3674
+ let fnNode = null;
3675
+ for (let parent = valueNode.parent; parent; parent = parent.parent) {
3676
+ if (parent.type === 'function_item') { fnNode = parent; break; }
3677
+ }
3678
+ if (!fnNode) return null;
3679
+ const name = valueNode.text;
3680
+ const facts = rustBindingFacts(fnNode);
3681
+ if (facts.declared.get(name) !== 1 || facts.assigned.has(name)) return null;
3682
+ for (let block = valueNode.parent; block && block.id !== fnNode.id; block = block.parent) {
3683
+ if (block.type !== 'block') continue;
3684
+ for (const stmt of block.namedChildren) {
3685
+ if (stmt.startIndex >= valueNode.startIndex) break;
3686
+ if (stmt.type !== 'let_declaration') continue;
3687
+ const pattern = stmt.childForFieldName('pattern');
3688
+ if (pattern?.type !== 'identifier' || pattern.text !== name) continue;
3689
+ const value = stmt.childForFieldName('value');
3690
+ if (!value || value.type === 'identifier' && value.text === name) return null;
3691
+ if (value.type === 'identifier' || value.type === 'scoped_identifier' ||
3692
+ value.type === 'struct_expression') {
3693
+ return rustReceiverValuePath(value);
3694
+ }
3695
+ return null;
3696
+ }
3697
+ }
3698
+ return null;
3699
+ }
3700
+
3701
+ /**
3702
+ * The field path an immutable one-hop `let` alias stands for (fix #381):
3703
+ * `let c = &self.config; c.load()` receives like `self.config.load()`. The
3704
+ * name is bound once in the function (no shadowing `let`), not `mut`, and
3705
+ * the path's root is `self` or a once-bound local.
3706
+ */
3707
+ function rustFieldAliasOf(identNode) {
3708
+ let fnNode = null;
3709
+ for (let parent = identNode.parent; parent; parent = parent.parent) {
3710
+ if (parent.type === 'function_item') { fnNode = parent; break; }
3711
+ }
3712
+ if (!fnNode) return null;
3713
+ const name = identNode.text;
3714
+ const facts = rustBindingFacts(fnNode);
3715
+ if (facts.declared.get(name) !== 1 || facts.assigned.has(name)) return null;
3716
+ for (let block = identNode.parent; block && block.id !== fnNode.id; block = block.parent) {
3717
+ if (block.type !== 'block') continue;
3718
+ for (const stmt of block.namedChildren) {
3719
+ if (stmt.startIndex >= identNode.startIndex) break;
3720
+ if (stmt.type !== 'let_declaration') continue;
3721
+ const pattern = stmt.childForFieldName('pattern');
3722
+ if (pattern?.type !== 'identifier' || pattern.text !== name) continue;
3723
+ let value = stmt.childForFieldName('value');
3724
+ if (value?.type === 'reference_expression') value = value.childForFieldName('value');
3725
+ if (value?.type !== 'field_expression') return null;
3726
+ let root = value;
3727
+ while (root?.type === 'field_expression') {
3728
+ if (root.childForFieldName('field')?.type !== 'field_identifier') return null;
3729
+ root = root.childForFieldName('value');
3730
+ }
3731
+ if (root?.type === 'self') return value;
3732
+ if (root?.type === 'identifier' && root.text !== name &&
3733
+ facts.declared.get(root.text) === 1 && !facts.assigned.has(root.text)) return value;
3734
+ return null;
3735
+ }
3736
+ }
3737
+ return null;
3738
+ }
3739
+
2134
3740
  function findCallsInCode(code, parser) {
2135
3741
  const tree = parseTree(parser, code);
2136
3742
  const calls = [];
3743
+ // Words of every value-binding pattern seen so far in document order
3744
+ // (fix #369). A binding shadows a bare call only if it precedes the call,
3745
+ // so this superset gates the exact ancestor walk below.
3746
+ const bindingWords = new Set();
3747
+ const BINDING_HOLDERS = new Set(['let_declaration', 'parameter', 'closure_parameters',
3748
+ 'for_expression', 'match_arm', 'let_condition']);
3749
+ const mayBeLocallyBound = name => bindingWords.has(name);
2137
3750
  const functionStack = []; // Stack of { name, startLine, endLine }
2138
3751
  // Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
2139
3752
  const scopeTypes = new Map();
@@ -2191,12 +3804,49 @@ function findCallsInCode(code, parser) {
2191
3804
  return null;
2192
3805
  };
2193
3806
 
3807
+ // Index contracts of std containers (fix #359): `items: &Vec<Conv>` makes
3808
+ // `items[i]` a Conv, `m: HashMap<K, Conv>` makes `m[&k]` a Conv, and
3809
+ // `[Conv; N]` / `&[Conv]` index to Conv. Only the declared slot is used;
3810
+ // generic parameters and unknown containers abstain.
3811
+ const RUST_SEQUENCE_CONTAINERS = new Set(['Vec', 'VecDeque']);
3812
+ const RUST_MAP_CONTAINERS = new Set(['HashMap', 'BTreeMap']);
3813
+ const rustIndexElement = (typeNode) => {
3814
+ let current = typeNode;
3815
+ while (current?.type === 'reference_type') {
3816
+ current = current.childForFieldName('type') ||
3817
+ current.namedChildren.find(c => c.type !== 'lifetime' && c.type !== 'mutable_specifier');
3818
+ }
3819
+ if (!current) return null;
3820
+ let element = null;
3821
+ if (current.type === 'array_type') {
3822
+ element = current.childForFieldName('element');
3823
+ } else if (current.type === 'generic_type') {
3824
+ const base = extractTypeName(current.childForFieldName('type') || current.namedChild(0));
3825
+ const argsNode = current.childForFieldName('type_arguments') ||
3826
+ current.namedChildren.find(c => c.type === 'type_arguments');
3827
+ const args = (argsNode?.namedChildren || []).filter(c =>
3828
+ c.type !== 'lifetime' && !c.type.endsWith('comment'));
3829
+ if (RUST_SEQUENCE_CONTAINERS.has(base) && args.length === 1) element = args[0];
3830
+ else if (RUST_MAP_CONTAINERS.has(base) && args.length === 2) element = args[1];
3831
+ }
3832
+ if (!element) return null;
3833
+ const type = extractTypeName(element);
3834
+ if (!type || /^[A-Z][A-Z0-9]?$/.test(type)) return null;
3835
+ const qualifier = extractTypeQualifier(element);
3836
+ return { type, ...(qualifier && { qualifier }) };
3837
+ };
3838
+ const isElementIndex = (indexNode) => indexNode?.type === 'index_expression' &&
3839
+ indexNode.namedChild(1)?.type !== 'range_expression';
3840
+
2194
3841
  // Build type map from function parameters (including self receiver for impl methods)
2195
3842
  const buildScopeTypeMap = (node) => {
2196
3843
  const typeMap = new ReceiverTypeMap();
2197
3844
  typeMap.qualifiers = new Map();
2198
3845
  typeMap.iteratorItems = new Map();
2199
3846
  typeMap.annotationTexts = new Map();
3847
+ typeMap.indexElements = new Map();
3848
+ typeMap.refKinds = new Map();
3849
+ typeMap.stdTypes = new Set();
2200
3850
  typeMap.boundNames = new Set();
2201
3851
  const retainBoundNames = (pattern) => {
2202
3852
  if (!pattern) return;
@@ -2219,16 +3869,33 @@ function findCallsInCode(code, parser) {
2219
3869
  if (param.type === 'parameter') {
2220
3870
  const patternNode = param.childForFieldName('pattern');
2221
3871
  retainBoundNames(patternNode);
2222
- const typeNode = param.childForFieldName('type');
2223
- const typeName = extractTypeName(typeNode);
3872
+ const declaredType = param.childForFieldName('type');
3873
+ // `p: Arc<Store>` receives as its Deref target (fix
3874
+ // #401); the wrapper chain rides on the evidence.
3875
+ const deref = rustDerefTypedName(declaredType);
3876
+ if (deref && !deref.typeName) continue;
3877
+ const typeNode = deref ? deref.inner : declaredType;
3878
+ const sliceType = rustSliceTypeOf(typeNode) || rustRawPointerTypeOf(typeNode);
3879
+ // `other: &Self` is the enclosing impl's self type (fix
3880
+ // #399); in a trait's own methods it is the implementor,
3881
+ // which no annotation names.
3882
+ const written = deref ? deref.typeName : extractTypeName(typeNode);
3883
+ const typeName = (written === 'Self' ? findEnclosingImplType(param) : written) || sliceType;
2224
3884
  const qualifier = extractTypeQualifier(typeNode);
2225
3885
  const iteratorItem = extractRustIteratorItemTypeFromTypeNode(typeNode);
3886
+ const indexElement = patternNode?.type === 'identifier'
3887
+ ? rustIndexElement(typeNode) : null;
3888
+ if (indexElement) typeMap.indexElements.set(patternNode.text, indexElement);
2226
3889
  if (patternNode && typeName) {
2227
3890
  // Pattern can be identifier or _
2228
3891
  const name = patternNode.type === 'identifier' ? patternNode.text : null;
2229
3892
  if (name) {
2230
- typeMap.set(name, typeName, 'annotation', param);
3893
+ typeMap.set(name, typeName, deref
3894
+ ? { ...typeOrigin('annotation', param), derefVia: deref.via, derefOuterRef: deref.outerRef }
3895
+ : 'annotation', param);
2231
3896
  typeMap.annotationTexts.set(name, typeNode.text);
3897
+ typeMap.refKinds.set(name, rustTypeRefKind(typeNode));
3898
+ if ((sliceType && typeName === sliceType) || deref?.std) typeMap.stdTypes.add(name);
2232
3899
  if (qualifier) typeMap.qualifiers.set(name, qualifier);
2233
3900
  if (iteratorItem) typeMap.iteratorItems.set(name, iteratorItem);
2234
3901
  }
@@ -2330,22 +3997,42 @@ function findCallsInCode(code, parser) {
2330
3997
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2331
3998
  const flow = flowEventAt(atNode, varName);
2332
3999
  if (flow?.type) {
2333
- const { until, ...origin } = flow;
4000
+ const { until, letStart, ...origin } = flow;
2334
4001
  return evidence ? { receiverTypeSource: 'flow',
2335
4002
  receiverTypeEvidence: { ...typeOrigin('flow', atNode), ...origin,
2336
4003
  ...(Number.isFinite(until) && { until }) },
2337
4004
  ...(flow.qualifier && { receiverTypeQualifier: flow.qualifier }) } : flow.type;
2338
4005
  }
4006
+ // An untyped `let` in scope hides the outer binding's type.
4007
+ const shadowedBy = flow && flow.letStart != null ? flow : null;
2339
4008
  for (let i = functionStack.length - 1; i >= 0; i--) {
2340
4009
  const typeMap = scopeTypes.get(functionStack[i].startLine);
4010
+ if (typeMap?.has(varName) && shadowedBy) {
4011
+ const start = typeMap.origins.get(varName)?.start;
4012
+ if (!(start >= shadowedBy.letStart && start <= shadowedBy.at)) return undefined;
4013
+ }
2341
4014
  if (typeMap?.has(varName)) return evidence ? { ...typeMap.fields(varName),
2342
- ...(typeMap.qualifiers?.has(varName) && { receiverTypeQualifier: typeMap.qualifiers.get(varName) }) }
4015
+ ...(typeMap.qualifiers?.has(varName) && { receiverTypeQualifier: typeMap.qualifiers.get(varName) }),
4016
+ ...(typeMap.refKinds?.has(varName) && { receiverTypeRef: typeMap.refKinds.get(varName) }),
4017
+ ...(typeMap.stdTypes?.has(varName) && { receiverTypeStd: true }) }
2343
4018
  : typeMap.get(varName);
2344
4019
  if (typeMap?.boundNames?.has(varName)) return undefined;
2345
4020
  }
2346
4021
  return undefined;
2347
4022
  };
2348
4023
 
4024
+ // Argument type descriptors for trait-impl selection (fix #384). Only
4025
+ // names some enclosing scope types can have one (flow-typed locals are
4026
+ // not argument evidence), so untyped names skip the positional lookup.
4027
+ const scopeTypesName = (name) => {
4028
+ for (let i = functionStack.length - 1; i >= 0; i--) {
4029
+ if (scopeTypes.get(functionStack[i].startLine)?.has(name)) return true;
4030
+ }
4031
+ return false;
4032
+ };
4033
+ const astArgTypeOfName = (name, atNode) => scopeTypesName(name)
4034
+ ? rustKnownArgNameType(name, atNode, getReceiverType, patternShadowsAt, null) : null;
4035
+
2349
4036
  const getReceiverTypeQualifier = (varName, atNode) => {
2350
4037
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2351
4038
  const flow = flowEventAt(atNode, varName);
@@ -2360,6 +4047,97 @@ function findCallsInCode(code, parser) {
2360
4047
  return undefined;
2361
4048
  };
2362
4049
 
4050
+ // A std deref wrapper constructed around a typed value (fix #401):
4051
+ // `ManuallyDrop::new(self)`, `Box::new(Store::new())`, `Arc::new(x)`,
4052
+ // `Rc::clone(&rc)`, `Box::pin(f)`, `Pin::new(&mut x)`,
4053
+ // `AssertUnwindSafe(x)`. Returns { via, typeName, qualifier, source }
4054
+ // (typeName null when the wrapped value's type is unknown), or null
4055
+ // when the value is no such construction.
4056
+ const rustStdDerefConstruction = (valueNode, atNode, depth = 0) => {
4057
+ if (depth > 4 || valueNode?.type !== 'call_expression') return null;
4058
+ let fn = valueNode.childForFieldName('function');
4059
+ if (fn?.type === 'generic_function') fn = fn.childForFieldName('function');
4060
+ let wrapper;
4061
+ let method = null;
4062
+ if (fn?.type === 'identifier' && fn.text === 'AssertUnwindSafe') {
4063
+ wrapper = 'AssertUnwindSafe';
4064
+ } else if (fn?.type === 'scoped_identifier') {
4065
+ const pathNode = fn.childForFieldName('path');
4066
+ method = fn.childForFieldName('name')?.text;
4067
+ const head = pathNode?.type === 'generic_type'
4068
+ ? pathNode.childForFieldName('type') || pathNode.namedChild(0) : pathNode;
4069
+ const headName = head?.type === 'identifier' || head?.type === 'type_identifier' ? head.text
4070
+ : head?.type === 'scoped_identifier' || head?.type === 'scoped_type_identifier'
4071
+ ? head.childForFieldName('name')?.text : null;
4072
+ if (!headName || !RUST_STD_DEREF_WRAPPERS.has(headName)) return null;
4073
+ const constructs = method === 'new' && ['Box', 'Rc', 'Arc', 'ManuallyDrop', 'Pin'].includes(headName);
4074
+ const pins = method === 'pin' && ['Box', 'Rc', 'Arc'].includes(headName);
4075
+ const clones = method === 'clone' && ['Rc', 'Arc'].includes(headName);
4076
+ if (!constructs && !pins && !clones) return null;
4077
+ wrapper = head.text.replace(/\s+/g, '');
4078
+ } else {
4079
+ return null;
4080
+ }
4081
+ const argsNode = valueNode.childForFieldName('arguments');
4082
+ const args = (argsNode?.namedChildren || []).filter(child => !child.type.endsWith('comment'));
4083
+ if (args.length !== 1) return { via: [wrapper], typeName: null };
4084
+ // `Box::pin(x)` makes a `Pin<Box<_>>`: Pin is implied, not written.
4085
+ const outer = method === 'pin' ? ['=Pin', wrapper] : [wrapper];
4086
+ let arg = args[0];
4087
+ let derefArg = false;
4088
+ while (arg?.type === 'reference_expression' || arg?.type === 'parenthesized_expression') {
4089
+ if (arg.type === 'reference_expression') derefArg = true;
4090
+ arg = arg.childForFieldName('value') || arg.namedChild(arg.namedChildCount - 1);
4091
+ }
4092
+ // `Pin::new(&mut x)` and `Rc::clone(&rc)` take a reference: the
4093
+ // result derefs to what the referent derefs to.
4094
+ if ((wrapper.endsWith('Pin') || method === 'clone') && !derefArg) return { via: outer, typeName: null };
4095
+ if (arg?.type === 'self') {
4096
+ const typeName = findEnclosingImplType(arg);
4097
+ return { via: outer, typeName: typeName || null, source: 'constructor' };
4098
+ }
4099
+ if (arg?.type === 'identifier') {
4100
+ const typed = getReceiverType(arg.text, atNode, true);
4101
+ const typeName = getReceiverType(arg.text, atNode);
4102
+ if (!typeName) return { via: outer, typeName: null };
4103
+ const inner = typed?.receiverTypeEvidence?.derefVia || [];
4104
+ // `Rc::clone(&rc)` is the same Rc: its own chain, not one more layer.
4105
+ const via = method === 'clone' ? inner : [...outer, ...inner];
4106
+ if (via.length === 0) return { via: outer, typeName: null };
4107
+ const source = typed?.receiverTypeSource === 'guess' ? 'guess' : 'constructor';
4108
+ const qualifier = getReceiverTypeQualifier(arg.text, atNode);
4109
+ return { via, typeName, ...(qualifier && { qualifier }), source };
4110
+ }
4111
+ if (method === 'clone') return { via: outer, typeName: null };
4112
+ if (arg?.type === 'struct_expression') {
4113
+ const pathText = arg.childForFieldName('name')?.text || '';
4114
+ const segments = pathText.split('::');
4115
+ const typeName = segments.pop();
4116
+ return { via: outer, typeName: /^[A-Z]/.test(typeName || '') ? typeName : null,
4117
+ ...(segments.length > 0 && { qualifier: segments.join('::') }), source: 'constructor' };
4118
+ }
4119
+ if (arg?.type === 'call_expression') {
4120
+ const nested = rustStdDerefConstruction(arg, atNode, depth + 1);
4121
+ if (nested) return { ...nested, via: [...outer, ...nested.via] };
4122
+ const callee = arg.childForFieldName('function');
4123
+ if (callee?.type === 'scoped_identifier') {
4124
+ const segments = callee.text.split('::');
4125
+ const ctor = segments[segments.length - 1];
4126
+ const typeName = segments[segments.length - 2];
4127
+ if (segments.length >= 2 && /^(new|from|default|with_|create|build|open|connect|init)/.test(ctor) &&
4128
+ /^[A-Z]/.test(typeName || '') && !RUST_STD_DEREF_WRAPPERS.has(typeName)) {
4129
+ // The prelude's `Vec`/`String` constructors make that
4130
+ // std type (a project namesake is checked at query time).
4131
+ const prelude = segments.length === 2 && (typeName === 'Vec' || typeName === 'String');
4132
+ return { via: outer, typeName,
4133
+ ...(segments.length > 2 && { qualifier: segments.slice(0, -2).join('::') }),
4134
+ source: prelude ? 'literal' : 'guess', ...(prelude && { std: true }) };
4135
+ }
4136
+ }
4137
+ }
4138
+ return { via: outer, typeName: null };
4139
+ };
4140
+
2363
4141
  const getReceiverIteratorItemType = (varName, atNode) => {
2364
4142
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2365
4143
  for (let i = functionStack.length - 1; i >= 0; i--) {
@@ -2372,6 +4150,29 @@ function findCallsInCode(code, parser) {
2372
4150
  return undefined;
2373
4151
  };
2374
4152
 
4153
+ const getReceiverIndexElement = (varName, atNode) => {
4154
+ if (atNode && patternShadowsAt(atNode, varName)) return undefined;
4155
+ if (flowEventAt(atNode, varName)) return undefined;
4156
+ for (let i = functionStack.length - 1; i >= 0; i--) {
4157
+ const typeMap = scopeTypes.get(functionStack[i].startLine);
4158
+ if (typeMap?.indexElements?.has(varName)) return typeMap.indexElements.get(varName);
4159
+ if (typeMap?.has(varName) || typeMap?.boundNames?.has(varName)) return undefined;
4160
+ }
4161
+ return undefined;
4162
+ };
4163
+
4164
+ // Reference layer of a variable's declared type (fix #368): 'owned',
4165
+ // '&' or '&mut'. Unknown after a flow rebinding or for untyped names.
4166
+ const getReceiverRefKind = (varName, atNode) => {
4167
+ if (atNode && patternShadowsAt(atNode, varName)) return undefined;
4168
+ for (let i = functionStack.length - 1; i >= 0; i--) {
4169
+ const typeMap = scopeTypes.get(functionStack[i].startLine);
4170
+ if (typeMap?.has(varName)) return typeMap.refKinds?.get(varName);
4171
+ if (typeMap?.boundNames?.has(varName)) return undefined;
4172
+ }
4173
+ return undefined;
4174
+ };
4175
+
2375
4176
  const getReceiverAnnotationText = (varName, atNode) => {
2376
4177
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2377
4178
  for (let i = functionStack.length - 1; i >= 0; i--) {
@@ -2509,12 +4310,33 @@ function findCallsInCode(code, parser) {
2509
4310
  const parameterNames = [];
2510
4311
  let parametersComplete = true;
2511
4312
  if (parameters) {
2512
- for (let i = 0; i < parameters.namedChildCount; i++) {
2513
- const parameter = parameters.namedChild(i);
4313
+ // Positions are the callback's argument slots (fix #368): the
4314
+ // wildcard `_` is an anonymous token but still occupies a slot,
4315
+ // so `|_, ctx|` binds ctx to slot 1, not slot 0. Unbound slots
4316
+ // are recorded as null.
4317
+ for (let i = 0; i < parameters.childCount; i++) {
4318
+ const parameter = parameters.child(i);
4319
+ if (parameter.type === '|' || parameter.type === ',' ||
4320
+ parameter.type.endsWith('comment')) continue;
4321
+ if (!parameter.isNamed) {
4322
+ if (parameter.type === '_') {
4323
+ parameterNames.push(null);
4324
+ continue;
4325
+ }
4326
+ parametersComplete = false;
4327
+ break;
4328
+ }
2514
4329
  if (parameter.type === 'identifier') {
2515
4330
  parameterNames.push(parameter.text);
2516
4331
  continue;
2517
4332
  }
4333
+ if (parameter.type === 'parameter') {
4334
+ const pattern = parameter.childForFieldName('pattern');
4335
+ if (pattern?.type === 'identifier') {
4336
+ parameterNames.push(pattern.text);
4337
+ continue;
4338
+ }
4339
+ }
2518
4340
  // `|ref a, ref b|` and `|mut value|` bind the same callback
2519
4341
  // parameter as their identifier child. Destructuring patterns
2520
4342
  // intentionally abstain: a tuple member is not the callback's
@@ -2542,7 +4364,7 @@ function findCallsInCode(code, parser) {
2542
4364
  break;
2543
4365
  }
2544
4366
  }
2545
- if (!parametersComplete || parameterNames.length === 0) return null;
4367
+ if (!parametersComplete || !parameterNames.some(Boolean)) return null;
2546
4368
  return {
2547
4369
  closureSourceCall: callName,
2548
4370
  closureSourceCallStart: outerCall.startIndex,
@@ -2553,7 +4375,21 @@ function findCallsInCode(code, parser) {
2553
4375
  };
2554
4376
  };
2555
4377
 
2556
- traverseTree(tree.rootNode, (node) => {
4378
+ // Item bodies of item-position macros (`quickcheck! { fn p(..) { .. } }`,
4379
+ // fix #401) are parsed as items: their calls are read from that parse,
4380
+ // with their functions' scopes and local types, instead of as tokens.
4381
+ const itemRecovery = rustMacroItemCallRecovery(code, parser);
4382
+ const recoveredTokenTrees = new Set((itemRecovery?.ranges || []).map(([start]) => start - 1));
4383
+
4384
+ const visitNode = (node) => {
4385
+ if (BINDING_HOLDERS.has(node.type)) {
4386
+ const pattern = node.type === 'closure_parameters' ? node : node.childForFieldName('pattern');
4387
+ if (pattern) {
4388
+ for (const word of pattern.text.split(/[^A-Za-z0-9_]+/)) {
4389
+ if (word) bindingWords.add(word);
4390
+ }
4391
+ }
4392
+ }
2557
4393
  // Track function entry
2558
4394
  if (isFunctionNode(node)) {
2559
4395
  const entry = {
@@ -2591,6 +4427,9 @@ function findCallsInCode(code, parser) {
2591
4427
  byName.get(pattern.text).push({
2592
4428
  at: node.endIndex,
2593
4429
  until,
4430
+ // A `let` shadows every earlier binding of the name
4431
+ // (fix #401); only a type this statement sets survives.
4432
+ ...(node.type === 'let_declaration' && { letStart: node.startIndex }),
2594
4433
  ...(() => {
2595
4434
  // Rust assignment preserves a variable's static
2596
4435
  // type; a fresh `let` may shadow it with another.
@@ -2618,6 +4457,9 @@ function findCallsInCode(code, parser) {
2618
4457
  }
2619
4458
 
2620
4459
  const enclosingFunction = getCurrentEnclosingFunction();
4460
+ // Where the value goes (fix #371/#372): audit-async reads only
4461
+ // records whose value can be lost or used as a resolved value.
4462
+ const { valueConsumed: consumedValue, consumingMethod } = rustValueFacts(node);
2621
4463
 
2622
4464
  // Assignment target for return-type flow (fix #207): let args =
2623
4465
  // parse_low_raw(...)? lets findCallers type args from the
@@ -2628,13 +4470,15 @@ function findCallsInCode(code, parser) {
2628
4470
  // UFCS `Type::method(&x, ...)` counts the explicit self — the
2629
4471
  // pruning range accounts for the shift).
2630
4472
  const argsNode = node.childForFieldName('arguments');
2631
- let argCount = 0;
4473
+ const argNodes = [];
2632
4474
  if (argsNode) {
2633
4475
  for (let i = 0; i < argsNode.namedChildCount; i++) {
2634
- if (argsNode.namedChild(i).type.endsWith('comment')) continue;
2635
- argCount++;
4476
+ const arg = argsNode.namedChild(i);
4477
+ if (arg.type.endsWith('comment')) continue;
4478
+ argNodes.push(arg);
2636
4479
  }
2637
4480
  }
4481
+ const argCount = argNodes.length;
2638
4482
 
2639
4483
  if (funcNode.type === 'identifier') {
2640
4484
  // Direct call: foo()
@@ -2644,19 +4488,30 @@ function findCallsInCode(code, parser) {
2644
4488
  line: node.startPosition.row + 1,
2645
4489
  callStart: node.startIndex,
2646
4490
  callEnd: node.endIndex,
4491
+ ...(consumedValue && { valueConsumed: consumedValue }),
4492
+ ...(consumingMethod && { consumingMethod }),
2647
4493
  isMethod: false,
2648
4494
  argCount,
2649
4495
  ...(assigned && { assignedTo: assigned.assignedTo }),
2650
4496
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
2651
4497
  ...(assigned?.tuple && { assignedTuple: true }),
2652
4498
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4499
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
2653
4500
  enclosingFunction,
2654
- ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
4501
+ ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4502
+ ...(mayBeLocallyBound(funcNode.text) &&
4503
+ rustBareNameShadowedByLocal(node, funcNode.text) && { localShadow: true }),
2655
4504
  });
2656
4505
  } else if (funcNode.type === 'field_expression') {
2657
4506
  // Method call: obj.method()
2658
4507
  const fieldNode = funcNode.childForFieldName('field');
2659
- const valueNode = funcNode.childForFieldName('value');
4508
+ let valueNode = funcNode.childForFieldName('value');
4509
+ // fix #381: `c.load()` after `let c = &self.config;`
4510
+ // receives like `self.config.load()`.
4511
+ if (valueNode?.type === 'identifier' && !getReceiverType(valueNode.text, node)) {
4512
+ const aliased = rustFieldAliasOf(valueNode);
4513
+ if (aliased) valueNode = aliased;
4514
+ }
2660
4515
 
2661
4516
  if (fieldNode) {
2662
4517
  let receiver = (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined;
@@ -2698,6 +4553,7 @@ function findCallsInCode(code, parser) {
2698
4553
  // let findCallers hop to the field's declared type cross-file.
2699
4554
  let receiverRoot, receiverField, receiverFields, receiverRootType;
2700
4555
  let receiverFieldCallRoot;
4556
+ let receiverViaClone = false;
2701
4557
  if (!receiver) {
2702
4558
  let obj = valueNode;
2703
4559
  while (obj?.type === 'call_expression') {
@@ -2739,6 +4595,19 @@ function findCallsInCode(code, parser) {
2739
4595
  (obj.type === 'identifier' || obj.type === 'self')) {
2740
4596
  // x.clone().m() — the receiver is effectively x
2741
4597
  receiver = obj.text;
4598
+ receiverViaClone = true;
4599
+ }
4600
+ } else if (valueNode?.type === 'identifier' && !getReceiverType(receiver, node)) {
4601
+ // fix #392: a local destructured from `self`
4602
+ // (`let Self { iter, .. } = self;`) holds that field's
4603
+ // value: the receiver is the field, like `self.iter`.
4604
+ const field = rustSelfFieldBinding(valueNode);
4605
+ if (field) {
4606
+ receiverRoot = 'self';
4607
+ receiverFields = [field];
4608
+ receiverField = field;
4609
+ receiverRootType = findEnclosingImplType(node);
4610
+ receiver = undefined;
2742
4611
  }
2743
4612
  }
2744
4613
  // Chained receiver (fix #220): the receiver IS a call —
@@ -2825,20 +4694,46 @@ function findCallsInCode(code, parser) {
2825
4694
  // ripgrep-measured): "match:fg:magenta".parse() is
2826
4695
  // str::parse, never a project method. Numeric literals
2827
4696
  // stay untyped (i32/u64/f64 ambiguity).
4697
+ // An array literal (fix #401) is the primitive array
4698
+ // type 'array': it unsizes to a slice, never derefs.
4699
+ const rangeReceiverType = (!receiver && valueNode)
4700
+ ? (rustRangeLiteralType(valueNode) ||
4701
+ (valueNode.type === 'tuple_expression' ? 'tuple' : null) ||
4702
+ (valueNode.type === 'array_expression' ? 'array' : null)) : null;
4703
+ // A cast receiver (fix #399): `(p as *const T).m()` has
4704
+ // exactly the cast's type (a primitive or raw pointer).
4705
+ let castNode = !receiver ? valueNode : null;
4706
+ while (castNode?.type === 'parenthesized_expression') castNode = castNode.namedChild(0);
4707
+ const castTypeNode = castNode?.type === 'type_cast_expression'
4708
+ ? castNode.childForFieldName('type') : null;
4709
+ const castReceiverType = castTypeNode
4710
+ ? (rustRawPointerTypeOf(castTypeNode) ||
4711
+ (castTypeNode.type === 'primitive_type' ? castTypeNode.text : null))
4712
+ : null;
2828
4713
  const literalReceiverType = (!receiver && valueNode)
2829
- ? ({ string_literal: 'str', raw_string_literal: 'str',
2830
- char_literal: 'char', boolean_literal: 'bool' })[valueNode.type]
4714
+ ? (({ string_literal: 'str', raw_string_literal: 'str',
4715
+ char_literal: 'char', boolean_literal: 'bool' })[valueNode.type] ||
4716
+ rangeReceiverType || castReceiverType || undefined)
2831
4717
  : undefined;
4718
+ // Element receiver (fix #359): `items[i].m()` on a
4719
+ // declared std container dispatches on its element.
4720
+ const indexRoot = !receiver && isElementIndex(valueNode)
4721
+ ? valueNode.namedChild(0) : null;
4722
+ const indexElement = indexRoot?.type === 'identifier'
4723
+ ? getReceiverIndexElement(indexRoot.text, node) : undefined;
2832
4724
  const receiverType = (receiver && receiver !== 'self' && !receiverIsChainRoot)
2833
4725
  ? getReceiverType(receiver, node)
2834
- : literalReceiverType;
4726
+ : (literalReceiverType || indexElement?.type);
2835
4727
  const receiverTypeQualifier = receiver && receiverType
2836
4728
  ? getReceiverTypeQualifier(receiver, node)
2837
- : undefined;
4729
+ : indexElement?.qualifier;
2838
4730
  const receiverIteratorItemType = receiver
2839
4731
  ? getReceiverIteratorItemType(receiver, node)
2840
4732
  : undefined;
2841
4733
  const receiverPatternShadow = !!(receiver && patternShadowsAt(node, receiver));
4734
+ const receiverValuePath = !receiverType && !receiverIsChainRoot && !receiverField &&
4735
+ (!receiver || receiver === valueNode?.text) && receiver !== 'self'
4736
+ ? rustReceiverValuePath(valueNode) : null;
2842
4737
  const receiverPatternBinding = rustPatternBindingOf(node, receiver);
2843
4738
  if (receiverPatternBinding?.receiverPatternSourceVariable) {
2844
4739
  const sourceType = getReceiverAnnotationText(
@@ -2850,6 +4745,19 @@ function findCallsInCode(code, parser) {
2850
4745
  const receiverFlowInvalidated = !!(receiver && flowInvalidatedAt(node, receiver));
2851
4746
  const iterationSource = rustIterationSourceOf(node, receiver);
2852
4747
  const firstArg = getFirstStringArg(node);
4748
+ // fix #383: axum `.route(path, get(h).post(h2))` - the
4749
+ // methods and handlers its method router serves.
4750
+ const methodRouter = fieldNode.text === 'route' || fieldNode.text === 'route_service'
4751
+ ? rustMethodRouterOf(node, fieldNode.text) : null;
4752
+ // fix #366: actix `.service(handler)` - the registered
4753
+ // handler function's name (route prefixes compose from the
4754
+ // `web::scope("/api")` chain it is registered on).
4755
+ let serviceArg = null;
4756
+ if (fieldNode.text === 'service') {
4757
+ const a = node.childForFieldName('arguments');
4758
+ const first = a && a.namedChildCount === 1 ? a.namedChild(0) : null;
4759
+ if (first && first.type === 'identifier') serviceArg = first.text;
4760
+ }
2853
4761
  // RUST-2: For chained calls like `a().b().parse::<T>().ok()`,
2854
4762
  // each method should report the line where its OWN identifier
2855
4763
  // appears, not the line where the outer expression begins.
@@ -2860,12 +4768,40 @@ function findCallsInCode(code, parser) {
2860
4768
  line: fieldNode.startPosition.row + 1,
2861
4769
  callStart: node.startIndex,
2862
4770
  callEnd: node.endIndex,
4771
+ ...(consumedValue && { valueConsumed: consumedValue }),
4772
+ ...(consumingMethod && { consumingMethod }),
2863
4773
  isMethod: true,
2864
4774
  receiver,
2865
- ...(receiverType && { receiverType, ...(getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
4775
+ ...(receiverType && { receiverType, ...(getReceiverType(receiver, node, true) ||
4776
+ (indexElement ? { receiverTypeSource: 'annotation',
4777
+ receiverTypeEvidence: typeOrigin('annotation', valueNode) } : null) ||
4778
+ (rangeReceiverType && receiverType === rangeReceiverType
4779
+ ? { receiverTypeSource: 'literal',
4780
+ receiverTypeEvidence: typeOrigin('literal', valueNode),
4781
+ receiverTypeStd: true } : null) ||
4782
+ (castReceiverType && receiverType === castReceiverType
4783
+ ? { receiverTypeSource: 'annotation',
4784
+ receiverTypeEvidence: typeOrigin('annotation', castTypeNode),
4785
+ receiverTypeStd: true } : null) ||
4786
+ { receiverTypeSource: 'unknown' }) }),
2866
4787
  ...(receiverTypeQualifier && { receiverTypeQualifier }),
4788
+ ...(receiverType && (() => {
4789
+ // `x.clone()` and range literals are owned values.
4790
+ const kind = receiverViaClone || (rangeReceiverType && receiverType === rangeReceiverType) ||
4791
+ (castReceiverType && receiverType === castReceiverType)
4792
+ ? 'owned'
4793
+ : (receiver && receiver !== 'self' ? getReceiverRefKind(receiver, node) : undefined);
4794
+ return kind ? { receiverTypeRef: kind } : {};
4795
+ })()),
2867
4796
  ...(receiverIteratorItemType && { receiverIteratorItemType }),
2868
4797
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
4798
+ // A path value receiver: `HAlign::Center.get_offset(..)`
4799
+ // (unit variant or associated const, fix #369), a unit
4800
+ // struct `S.plain()` or struct expression, directly or
4801
+ // through a once-bound local (fix #389); query time
4802
+ // decides what the path names.
4803
+ ...(receiverValuePath && { receiverValuePath: receiverValuePath.path,
4804
+ ...(receiverValuePath.kind !== 'value' && { receiverValueKind: receiverValuePath.kind }) }),
2869
4805
  ...(receiverPatternBinding || {}),
2870
4806
  ...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
2871
4807
  ...(iterationSource || {}),
@@ -2882,16 +4818,23 @@ function findCallsInCode(code, parser) {
2882
4818
  ...(receiverCallStart != null && { receiverCallStart }),
2883
4819
  ...(receiverCallEnd != null && { receiverCallEnd }),
2884
4820
  argCount,
4821
+ ...(() => {
4822
+ const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
4823
+ return argTypes ? { argTypes } : {};
4824
+ })(),
2885
4825
  ...(assigned && { assignedTo: assigned.assignedTo }),
2886
4826
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
2887
4827
  ...(assigned?.tuple && { assignedTuple: true }),
2888
4828
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4829
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
2889
4830
  ...(collectResult && {
2890
4831
  explicitResultType: collectResult.type,
2891
4832
  explicitResultItemType: collectResult.itemType,
2892
4833
  }),
2893
4834
  enclosingFunction,
2894
- ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
4835
+ ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4836
+ ...(serviceArg && { serviceArg }),
4837
+ ...(methodRouter && { methodRouter })
2895
4838
  });
2896
4839
  }
2897
4840
  } else if (funcNode.type === 'scoped_identifier') {
@@ -2911,16 +4854,24 @@ function findCallsInCode(code, parser) {
2911
4854
  line: node.startPosition.row + 1,
2912
4855
  callStart: node.startIndex,
2913
4856
  callEnd: node.endIndex,
4857
+ ...(consumedValue && { valueConsumed: consumedValue }),
4858
+ ...(consumingMethod && { consumingMethod }),
2914
4859
  isMethod: segments.length > 1,
2915
4860
  isPathCall: true, // Distinguishes Type::func()/module::func() from obj.method()
2916
4861
  receiver: recvSegments.length > 0 ? recvSegments.join('::') : undefined,
2917
4862
  argCount,
4863
+ ...(segments.length > 1 && (() => {
4864
+ const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
4865
+ return argTypes ? { argTypes } : {};
4866
+ })()),
2918
4867
  ...(assigned && { assignedTo: assigned.assignedTo }),
2919
4868
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
2920
4869
  ...(assigned?.tuple && { assignedTuple: true }),
2921
4870
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4871
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
2922
4872
  enclosingFunction,
2923
- ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
4873
+ ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
4874
+ ...(rustFunctionReturnPosition(node) && { returnPosition: true }),
2924
4875
  });
2925
4876
  }
2926
4877
  return true;
@@ -3004,6 +4955,11 @@ function findCallsInCode(code, parser) {
3004
4955
  ...(assigned?.unwrapped && { assignedUnwrap: true }),
3005
4956
  ...(assigned?.tuple && { assignedTuple: true }),
3006
4957
  ...(assigned?.tupleRest && { assignedTupleRest: assigned.tupleRest }),
4958
+ ...(assigned?.tupleTargets && { assignedTupleTargets: assigned.tupleTargets }),
4959
+ // Expression position (fix #375): lets the macro_rules!
4960
+ // expansion pass skip files whose value-forwarding
4961
+ // invocations all sit where no value is taken.
4962
+ ...(rustMacroInvocationContext(node) === 'expr' && { macroExpr: true }),
3007
4963
  enclosingFunction
3008
4964
  });
3009
4965
  }
@@ -3013,12 +4969,13 @@ function findCallsInCode(code, parser) {
3013
4969
  // call lines on ripgrep — test assertions live in macros).
3014
4970
  for (let i = 0; i < node.childCount; i++) {
3015
4971
  const child = node.child(i);
3016
- if (child.type === 'token_tree') {
4972
+ if (child.type === 'token_tree' && !recoveredTokenTrees.has(child.startIndex)) {
3017
4973
  extractCallsFromTokenTree(
3018
4974
  child, enclosingFunction, calls, getReceiverType,
3019
4975
  patternShadowsAt, flowInvalidatedAt, {
3020
4976
  kind: 'invocation',
3021
4977
  containerMacro: macro?.name,
4978
+ scopeTypesName,
3022
4979
  });
3023
4980
  }
3024
4981
  }
@@ -3043,6 +5000,7 @@ function findCallsInCode(code, parser) {
3043
5000
  patternShadowsAt, flowInvalidatedAt, {
3044
5001
  kind: 'attribute',
3045
5002
  containerMacro: attributeName,
5003
+ scopeTypesName,
3046
5004
  });
3047
5005
  }
3048
5006
  return true;
@@ -3107,6 +5065,19 @@ function findCallsInCode(code, parser) {
3107
5065
  const patternNode = node.childForFieldName('pattern');
3108
5066
  const valueNode = node.childForFieldName('value');
3109
5067
  const typeAnnotation = node.childForFieldName('type');
5068
+ if (patternNode && patternNode.type !== 'identifier') {
5069
+ // A destructuring `let` shadows every name it binds; their
5070
+ // earlier declared reference layers no longer apply.
5071
+ const refKinds = scopeTypes.get(functionStack[functionStack.length - 1].startLine)?.refKinds;
5072
+ if (refKinds) {
5073
+ const pending = [patternNode];
5074
+ while (pending.length > 0) {
5075
+ const current = pending.pop();
5076
+ if (current.type === 'identifier') refKinds.delete(current.text);
5077
+ for (let i = 0; i < current.namedChildCount; i++) pending.push(current.namedChild(i));
5078
+ }
5079
+ }
5080
+ }
3110
5081
  if (patternNode && patternNode.type === 'identifier') {
3111
5082
  const varName = patternNode.text;
3112
5083
  const scopeKey = functionStack[functionStack.length - 1].startLine;
@@ -3114,12 +5085,58 @@ function findCallsInCode(code, parser) {
3114
5085
  if (typeMap) {
3115
5086
  let typeName = null;
3116
5087
  let typeQualifier = null;
5088
+ let stdLiteral = false;
5089
+ // Std deref wrappers (fix #401): `let a: Arc<Store>`,
5090
+ // `let m = ManuallyDrop::new(self)` receive as the target.
5091
+ let derefVia = null;
5092
+ let derefSource = null;
5093
+ let derefInnerRef = 'owned';
5094
+ let derefOuterRef = 'owned';
5095
+ let derefStd = false;
3117
5096
  // Pattern 3: explicit type annotation — let s: Server = ...
3118
5097
  if (typeAnnotation) {
3119
- typeName = extractTypeName(typeAnnotation);
3120
- typeQualifier = extractTypeQualifier(typeAnnotation);
5098
+ const deref = rustDerefTypedName(typeAnnotation);
5099
+ const declared = deref ? deref.inner : typeAnnotation;
5100
+ if (deref) {
5101
+ derefVia = deref.via;
5102
+ derefInnerRef = rustTypeRefKind(declared);
5103
+ derefOuterRef = deref.outerRef;
5104
+ derefStd = !!deref.std;
5105
+ }
5106
+ typeName = deref ? deref.typeName : extractTypeName(declared);
5107
+ if (typeName === 'Self') typeName = findEnclosingImplType(typeAnnotation) || null;
5108
+ if (!typeName && !deref && (rustSliceTypeOf(declared) || rustRawPointerTypeOf(declared))) {
5109
+ typeName = rustSliceTypeOf(declared) || rustRawPointerTypeOf(declared);
5110
+ stdLiteral = true;
5111
+ }
5112
+ typeQualifier = extractTypeQualifier(declared);
5113
+ const indexElement = rustIndexElement(declared);
5114
+ if (indexElement) typeMap.indexElements.set(varName, indexElement);
5115
+ else typeMap.indexElements.delete(varName);
5116
+ } else {
5117
+ typeMap.indexElements.delete(varName);
5118
+ }
5119
+ let indexedValue = valueNode;
5120
+ while (indexedValue && (indexedValue.type === 'reference_expression' ||
5121
+ indexedValue.type === 'parenthesized_expression')) {
5122
+ indexedValue = indexedValue.childForFieldName('value') || indexedValue.namedChild(0);
3121
5123
  }
3122
- if (!typeName && valueNode) {
5124
+ if (!typeName && isElementIndex(indexedValue) &&
5125
+ indexedValue.namedChild(0)?.type === 'identifier') {
5126
+ const element = getReceiverIndexElement(indexedValue.namedChild(0).text, node);
5127
+ if (element) {
5128
+ typeName = element.type;
5129
+ typeQualifier = element.qualifier || null;
5130
+ }
5131
+ }
5132
+ if (!typeName && valueNode && !derefVia) {
5133
+ const rangeType = rustRangeLiteralType(valueNode, true);
5134
+ if (rangeType) {
5135
+ typeName = rangeType;
5136
+ stdLiteral = true;
5137
+ }
5138
+ }
5139
+ if (!typeName && valueNode && !(typeAnnotation && derefVia)) {
3123
5140
  // Pattern 1: struct expression — let s = Server { ... }
3124
5141
  if (valueNode.type === 'struct_expression') {
3125
5142
  const nameNode = valueNode.childForFieldName('name');
@@ -3147,7 +5164,16 @@ function findCallsInCode(code, parser) {
3147
5164
  // Pattern 2: constructor call — let s = Server::new()
3148
5165
  else if (valueNode.type === 'call_expression') {
3149
5166
  const funcNode = valueNode.childForFieldName('function');
3150
- if (funcNode?.type === 'scoped_identifier') {
5167
+ const wrapped = rustStdDerefConstruction(valueNode, node);
5168
+ if (wrapped) {
5169
+ if (wrapped.typeName) {
5170
+ typeName = wrapped.typeName;
5171
+ typeQualifier = wrapped.qualifier || null;
5172
+ derefVia = wrapped.via;
5173
+ derefSource = wrapped.source;
5174
+ derefStd = !!wrapped.std;
5175
+ }
5176
+ } else if (funcNode?.type === 'scoped_identifier') {
3151
5177
  const pathText = funcNode.text;
3152
5178
  const segments = pathText.split('::');
3153
5179
  if (segments.length >= 2) {
@@ -3156,31 +5182,61 @@ function findCallsInCode(code, parser) {
3156
5182
  typeName = segments[segments.length - 2];
3157
5183
  typeQualifier = segments.slice(0, -2).join('::') || null;
3158
5184
  if (!typeName || !/^[A-Z]/.test(typeName)) typeName = null;
5185
+ // `Self::with_capacity(..)` names the impl's
5186
+ // self type (fix #401), never a type `Self`.
5187
+ if (typeName === 'Self' && segments.length === 2) {
5188
+ typeName = findEnclosingImplType(valueNode) || null;
5189
+ }
3159
5190
  }
3160
5191
  }
3161
5192
  }
3162
5193
  }
3163
5194
  }
5195
+ typeMap.refKinds.delete(varName);
5196
+ typeMap.stdTypes.delete(varName);
3164
5197
  if (typeName) {
3165
- typeMap.set(varName, typeName, typeAnnotation ? 'annotation' : valueNode?.type === 'call_expression' ? 'guess' : 'constructor', typeAnnotation || valueNode);
5198
+ const source = stdLiteral && !typeAnnotation ? 'literal'
5199
+ : typeAnnotation || isElementIndex(indexedValue) ? 'annotation'
5200
+ : derefSource || (valueNode?.type === 'call_expression' ? 'guess' : 'constructor');
5201
+ typeMap.set(varName, typeName, derefVia
5202
+ ? { ...typeOrigin(source, typeAnnotation || valueNode), derefVia, derefOuterRef }
5203
+ : source, typeAnnotation || valueNode);
3166
5204
  if (typeQualifier) typeMap.qualifiers.set(varName, typeQualifier);
5205
+ if (derefVia) {
5206
+ // The wrapper's target is a place of the inner type.
5207
+ typeMap.refKinds.set(varName, derefInnerRef);
5208
+ } else if (typeAnnotation) {
5209
+ typeMap.refKinds.set(varName, rustTypeRefKind(typeAnnotation));
5210
+ } else if (valueNode?.type === 'struct_expression' || stdLiteral) {
5211
+ typeMap.refKinds.set(varName, 'owned');
5212
+ }
5213
+ if (stdLiteral || derefStd) typeMap.stdTypes.add(varName);
3167
5214
  }
3168
5215
  }
3169
5216
  }
3170
5217
  }
3171
5218
 
3172
5219
  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
- }
5220
+ };
5221
+ const leaveNode = (node) => {
5222
+ if (isFunctionNode(node)) {
5223
+ const leaving = functionStack.pop();
5224
+ if (leaving) {
5225
+ scopeTypes.delete(leaving.startLine);
5226
+ scopeFlowEvents.delete(leaving.startLine);
3181
5227
  }
3182
5228
  }
3183
- });
5229
+ };
5230
+ traverseTree(tree.rootNode, visitNode, { onLeave: leaveNode });
5231
+ if (itemRecovery) {
5232
+ const ranges = itemRecovery.ranges;
5233
+ const inside = node => ranges.some(([start, end]) => node.startIndex >= start && node.endIndex <= end);
5234
+ const overlaps = node => ranges.some(([start, end]) => node.startIndex < end && node.endIndex > start);
5235
+ traverseTree(itemRecovery.tree.rootNode, node => {
5236
+ if (inside(node)) return visitNode(node);
5237
+ return overlaps(node);
5238
+ }, { onLeave: node => { if (inside(node)) leaveNode(node); } });
5239
+ }
3184
5240
 
3185
5241
  const declaration = declarationTrees(code, parser);
3186
5242
  if (!declaration.macroItemRecovery) return calls;
@@ -3307,6 +5363,22 @@ function findImportsInCode(code, parser) {
3307
5363
  return true;
3308
5364
  }
3309
5365
 
5366
+ // `extern crate itertools as it;` binds the crate under a local
5367
+ // name exactly like `use itertools as it;` (fix #369). A plain
5368
+ // `extern crate x;` adds no name beyond the extern prelude.
5369
+ if (node.type === 'extern_crate_declaration') {
5370
+ const nameNode = node.childForFieldName('name');
5371
+ const aliasNode = node.childForFieldName('alias');
5372
+ if (nameNode && aliasNode && aliasNode.text !== nameNode.text) {
5373
+ const local = aliasNode.text;
5374
+ addLeaf(nameNode.text, local, 'use', false, node.startPosition.row + 1,
5375
+ { original: nameNode.text, local });
5376
+ if (!imports.aliases) imports.aliases = [];
5377
+ imports.aliases.push({ original: nameNode.text, local });
5378
+ }
5379
+ return true;
5380
+ }
5381
+
3310
5382
  // mod declarations (external module imports)
3311
5383
  if (node.type === 'mod_item') {
3312
5384
  const line = node.startPosition.row + 1;
@@ -3435,13 +5507,13 @@ function findExportsInCode(code, parser) {
3435
5507
  return true;
3436
5508
  }
3437
5509
 
3438
- // Public structs
3439
- if (node.type === 'struct_item' && hasVisibility(node)) {
5510
+ // Public structs and unions
5511
+ if ((node.type === 'struct_item' || node.type === 'union_item') && hasVisibility(node)) {
3440
5512
  const nameNode = node.childForFieldName('name');
3441
5513
  if (nameNode) {
3442
5514
  append({
3443
5515
  name: nameNode.text,
3444
- type: 'struct',
5516
+ type: node.type === 'union_item' ? 'union' : 'struct',
3445
5517
  line: node.startPosition.row + 1
3446
5518
  });
3447
5519
  }
@@ -3548,20 +5620,22 @@ function _indexInParent(node, parent) {
3548
5620
  return -1;
3549
5621
  }
3550
5622
 
3551
- function findUsagesInCode(code, name, parser, tree) {
5623
+ function findUsagesInCode(code, name, parser, tree, options = {}) {
3552
5624
  tree = tree || parseTree(parser, code);
3553
5625
  const usages = [];
5626
+ // Lexical scope verdicts (fix #392) only for refactoring internals.
5627
+ const scopeMemo = options.lexicalScopes ? new Map() : null;
3554
5628
  // Lazy same-file enum→variants map: built only when a paren-less
3555
5629
  // `Type::name` reference needs the enum-variant check.
3556
5630
  let _enumVariants = null;
3557
5631
  const sameFileEnumVariant = (enumName, variantName) => {
3558
5632
  if (_enumVariants === null) {
3559
5633
  _enumVariants = new Map();
3560
- traverseTreeCached(tree.rootNode, (n) => {
3561
- if (n.type !== 'enum_item') return;
5634
+ // Native type walk: no node list of the whole file (fix #403).
5635
+ for (const n of tree.rootNode.descendantsOfType('enum_item')) {
3562
5636
  const enName = n.childForFieldName('name')?.text;
3563
5637
  const body = n.childForFieldName('body');
3564
- if (!enName || !body) return;
5638
+ if (!enName || !body) continue;
3565
5639
  let set = _enumVariants.get(enName);
3566
5640
  if (!set) { set = new Set(); _enumVariants.set(enName, set); }
3567
5641
  for (let i = 0; i < body.namedChildCount; i++) {
@@ -3571,7 +5645,7 @@ function findUsagesInCode(code, name, parser, tree) {
3571
5645
  if (vn) set.add(vn);
3572
5646
  }
3573
5647
  }
3574
- });
5648
+ }
3575
5649
  }
3576
5650
  return _enumVariants.get(enumName)?.has(variantName) || false;
3577
5651
  };
@@ -3691,8 +5765,8 @@ function findUsagesInCode(code, name, parser, tree) {
3691
5765
  sameNode(parent.childForFieldName('name'), node)) {
3692
5766
  usageType = 'definition';
3693
5767
  }
3694
- // Definition: struct name
3695
- else if (parent.type === 'struct_item' &&
5768
+ // Definition: struct / union name
5769
+ else if ((parent.type === 'struct_item' || parent.type === 'union_item') &&
3696
5770
  sameNode(parent.childForFieldName('name'), node)) {
3697
5771
  usageType = 'definition';
3698
5772
  }
@@ -3771,7 +5845,11 @@ function findUsagesInCode(code, name, parser, tree) {
3771
5845
  if (idx >= 0) {
3772
5846
  // Check no preceding dot (would be method call handled above)
3773
5847
  const prev = idx > 0 ? parent.child(idx - 1) : null;
3774
- if ((!prev || prev.text !== '.') && callArgs) {
5848
+ if (prev?.type === 'fn') {
5849
+ // `fn name(...)` in a token tree is a definition
5850
+ // template, not a call (fix #360).
5851
+ usageType = 'definition';
5852
+ } else if ((!prev || prev.text !== '.') && callArgs) {
3775
5853
  usageType = 'call';
3776
5854
  }
3777
5855
  }
@@ -3802,21 +5880,62 @@ function findUsagesInCode(code, name, parser, tree) {
3802
5880
  }
3803
5881
 
3804
5882
  let inAttribute = false;
5883
+ let attributeNode = null;
3805
5884
  for (let a = parent; a; a = a.parent) {
3806
5885
  if (a.type === 'attribute' || a.type === 'attribute_item') {
3807
5886
  inAttribute = true;
5887
+ attributeNode = a.type === 'attribute' ? a
5888
+ : a.namedChildren.find(child => child.type === 'attribute') || null;
3808
5889
  break;
3809
5890
  }
3810
5891
  if (a.type === 'function_item' || a.type === 'impl_item' ||
3811
5892
  a.type === 'struct_item') break;
3812
5893
  }
3813
- usages.push({ line, column, usageType, ...(inAttribute && { inAttribute: true }) });
5894
+ // Macro namespace (fix #377): a macro invocation's name, an
5895
+ // attribute's path (`#[must_use]`, `#[tokio::main]`) and a derive
5896
+ // list entry (`#[derive(Debug)]`) name macros or built-in
5897
+ // attributes, never a same-named fn, const or local.
5898
+ const macroNamespace = rustMacroNamespaceName(node, parent, attributeNode);
5899
+ // Where a bare reference resolves (fix #392).
5900
+ const scope = scopeMemo && usageType === 'reference' && !macroNamespace
5901
+ ? scopeFields(inAttribute ? 'unknown' : referenceScope(node, 'rust', scopeMemo)) : null;
5902
+ // `S { name }` names the field too: a rename keeps the key (fix #397).
5903
+ const shorthandProperty = scopeMemo && parent?.type === 'shorthand_field_initializer';
5904
+ usages.push({ line, column, usageType, ...(inAttribute && { inAttribute: true }),
5905
+ ...(macroNamespace && { namespace: 'macro' }), ...scope,
5906
+ ...(shorthandProperty && { shorthandProperty: true }) });
3814
5907
  return true;
3815
5908
  });
3816
5909
 
3817
5910
  return usages;
3818
5911
  }
3819
5912
 
5913
+ /**
5914
+ * Is this identifier a name in the macro namespace (fix #377)? The name of a
5915
+ * macro invocation, the path of an attribute, or an entry of a derive list
5916
+ * (also inside `cfg_attr(..., derive(...))`).
5917
+ */
5918
+ function rustMacroNamespaceName(node, parent, attributeNode) {
5919
+ if (!parent) return false;
5920
+ if (parent.type === 'macro_invocation') return sameNode(parent.childForFieldName('macro'), node);
5921
+ if (parent.type === 'scoped_identifier' && sameNode(parent.childForFieldName('name'), node)) {
5922
+ const up = parent.parent;
5923
+ if (up?.type === 'macro_invocation') return sameNode(up.childForFieldName('macro'), parent);
5924
+ if (up?.type === 'attribute') return sameNode(up.namedChild(0), parent);
5925
+ }
5926
+ if (!attributeNode) return false;
5927
+ if (parent.type === 'attribute') return sameNode(parent.namedChild(0), node);
5928
+ if (parent.type !== 'token_tree') return false;
5929
+ // derive(A, path::B): the token tree follows the `derive` path (the
5930
+ // attribute's own path, or an identifier token inside cfg_attr).
5931
+ const opener = parent.parent?.type === 'attribute' && sameNode(parent.parent.childForFieldName('arguments'), parent)
5932
+ ? parent.parent.namedChild(0)
5933
+ : parent.previousSibling;
5934
+ if (!opener || opener.text !== 'derive') return false;
5935
+ const next = node.nextSibling;
5936
+ return !next || next.type !== '::';
5937
+ }
5938
+
3820
5939
  /**
3821
5940
  * Classify a Rust symbol as a runtime entry point of a specific kind.
3822
5941
  * Returns 'test' | 'main' | 'framework' | null.
@@ -3854,6 +5973,9 @@ function isEntryPoint(symbol) {
3854
5973
  }
3855
5974
 
3856
5975
  module.exports = {
5976
+ primeDeclarationTrees,
5977
+ parseDeclarationsIn,
5978
+ rustMacroInvocationContext,
3857
5979
  findFunctions,
3858
5980
  findClasses,
3859
5981
  findStateObjects,