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