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