ucn 5.5.0 → 5.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +41 -27
- package/cli/index.js +6 -1
- package/core/accessors.js +341 -54
- package/core/account.js +106 -31
- package/core/analysis.js +266 -127
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +144 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +5111 -1024
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +35 -15
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +1 -0
- package/core/output/reporting.js +6 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +228 -114
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/rust-macro-expansion.js +1202 -157
- package/core/rust-modules.js +21 -3
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +157 -46
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +548 -89
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
package/languages/rust.js
CHANGED
|
@@ -126,6 +126,16 @@ function buildMacroItemRecoveryTree(code, parser, tree) {
|
|
|
126
126
|
return { tree: recovered, itemCount, declarationNameStarts, callRecovery };
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* The item-body call recovery of an expanded file parsed by the caller
|
|
131
|
+
* (fix #405): what rustMacroItemCallRecovery computes for it once its
|
|
132
|
+
* declaration trees are primed with `tree`. Its walk builds the tree's flat
|
|
133
|
+
* node list, which the call extraction then reads.
|
|
134
|
+
*/
|
|
135
|
+
function rustExpandedItemRecovery(code, parser, tree) {
|
|
136
|
+
return buildMacroItemRecoveryTree(code, parser, tree)?.callRecovery || null;
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
/**
|
|
130
140
|
* Rust macro invocation bodies are token trees, even when they contain item
|
|
131
141
|
* declarations verbatim. Reparse only item-position bodies in a byte- and
|
|
@@ -682,7 +692,6 @@ function extractAttributesWithArgs(node, codeOrLines) {
|
|
|
682
692
|
}
|
|
683
693
|
|
|
684
694
|
// --- Module-scope constants for state object detection ---
|
|
685
|
-
const _STATE_PATTERN = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
|
|
686
695
|
|
|
687
696
|
// --- Single-pass helpers: extracted from find* callbacks ---
|
|
688
697
|
|
|
@@ -747,7 +756,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
747
756
|
const paramsNode = node.childForFieldName('parameters');
|
|
748
757
|
|
|
749
758
|
if (nameNode) {
|
|
750
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
759
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
751
760
|
const text = node.text;
|
|
752
761
|
|
|
753
762
|
const qualifiers = rustFunctionQualifiers(node);
|
|
@@ -788,6 +797,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
788
797
|
...(callbackParamTypes && { callbackParamTypes }),
|
|
789
798
|
startLine,
|
|
790
799
|
endLine,
|
|
800
|
+
sourceSpan,
|
|
791
801
|
indent,
|
|
792
802
|
modifiers,
|
|
793
803
|
...(returnType && { returnType }),
|
|
@@ -816,7 +826,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
816
826
|
const fName = child.childForFieldName('name');
|
|
817
827
|
const fParams = child.childForFieldName('parameters');
|
|
818
828
|
if (fName) {
|
|
819
|
-
const { startLine, endLine, indent } = nodeToLocation(child, lines);
|
|
829
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
820
830
|
const visibility = extractVisibility(child.text);
|
|
821
831
|
const returnType = extractReturnType(child);
|
|
822
832
|
const docstring = extractRustDocstring(lines, startLine);
|
|
@@ -833,6 +843,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
833
843
|
...(iteratorItemType && { iteratorItemType }),
|
|
834
844
|
startLine,
|
|
835
845
|
endLine,
|
|
846
|
+
sourceSpan,
|
|
836
847
|
indent,
|
|
837
848
|
modifiers,
|
|
838
849
|
...(returnType && { returnType }),
|
|
@@ -1068,7 +1079,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1068
1079
|
|
|
1069
1080
|
const nameNode = node.childForFieldName('name');
|
|
1070
1081
|
if (nameNode) {
|
|
1071
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1082
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1072
1083
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1073
1084
|
const visibility = extractVisibility(node.text);
|
|
1074
1085
|
const generics = extractGenerics(node);
|
|
@@ -1082,6 +1093,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1082
1093
|
name: nameNode.text,
|
|
1083
1094
|
startLine,
|
|
1084
1095
|
endLine,
|
|
1096
|
+
sourceSpan,
|
|
1085
1097
|
type: node.type === 'union_item' ? 'union' : 'struct',
|
|
1086
1098
|
members,
|
|
1087
1099
|
modifiers,
|
|
@@ -1102,7 +1114,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1102
1114
|
|
|
1103
1115
|
const nameNode = node.childForFieldName('name');
|
|
1104
1116
|
if (nameNode) {
|
|
1105
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1117
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1106
1118
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1107
1119
|
const visibility = extractVisibility(node.text);
|
|
1108
1120
|
const generics = extractGenerics(node);
|
|
@@ -1114,6 +1126,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1114
1126
|
name: nameNode.text,
|
|
1115
1127
|
startLine,
|
|
1116
1128
|
endLine,
|
|
1129
|
+
sourceSpan,
|
|
1117
1130
|
type: 'enum',
|
|
1118
1131
|
members: extractEnumVariants(node, lines),
|
|
1119
1132
|
modifiers,
|
|
@@ -1133,7 +1146,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1133
1146
|
|
|
1134
1147
|
const nameNode = node.childForFieldName('name');
|
|
1135
1148
|
if (nameNode) {
|
|
1136
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1149
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1137
1150
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1138
1151
|
const visibility = extractVisibility(node.text);
|
|
1139
1152
|
const generics = extractGenerics(node);
|
|
@@ -1143,6 +1156,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1143
1156
|
name: nameNode.text,
|
|
1144
1157
|
startLine,
|
|
1145
1158
|
endLine,
|
|
1159
|
+
sourceSpan,
|
|
1146
1160
|
type: 'trait',
|
|
1147
1161
|
members: extractTraitMembers(node, lines),
|
|
1148
1162
|
modifiers: visibility ? [visibility] : [],
|
|
@@ -1161,7 +1175,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1161
1175
|
if (processedRanges.has(rangeKey)) return true;
|
|
1162
1176
|
processedRanges.add(rangeKey);
|
|
1163
1177
|
|
|
1164
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1178
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1165
1179
|
const implInfo = extractImplInfo(node);
|
|
1166
1180
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1167
1181
|
const derefTarget = extractDerefTarget(node, implInfo.traitName);
|
|
@@ -1177,6 +1191,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1177
1191
|
name: implInfo.name,
|
|
1178
1192
|
startLine,
|
|
1179
1193
|
endLine,
|
|
1194
|
+
sourceSpan,
|
|
1180
1195
|
type: 'impl',
|
|
1181
1196
|
traitName: implInfo.traitName,
|
|
1182
1197
|
...rustBlockItemScope(node),
|
|
@@ -1200,7 +1215,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1200
1215
|
|
|
1201
1216
|
const nameNode = node.childForFieldName('name');
|
|
1202
1217
|
if (nameNode) {
|
|
1203
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1218
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1204
1219
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1205
1220
|
const visibility = extractVisibility(node.text);
|
|
1206
1221
|
const attributes = extractAttributes(node, lines);
|
|
@@ -1211,6 +1226,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1211
1226
|
name: nameNode.text,
|
|
1212
1227
|
startLine,
|
|
1213
1228
|
endLine,
|
|
1229
|
+
sourceSpan,
|
|
1214
1230
|
type: 'module',
|
|
1215
1231
|
members: [],
|
|
1216
1232
|
modifiers,
|
|
@@ -1228,7 +1244,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1228
1244
|
|
|
1229
1245
|
const nameNode = node.childForFieldName('name');
|
|
1230
1246
|
if (nameNode) {
|
|
1231
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1247
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1232
1248
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1233
1249
|
const inferred = _inferMacroReturn(node, nameNode.text);
|
|
1234
1250
|
|
|
@@ -1255,6 +1271,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1255
1271
|
name: nameNode.text,
|
|
1256
1272
|
startLine,
|
|
1257
1273
|
endLine,
|
|
1274
|
+
sourceSpan,
|
|
1258
1275
|
type: 'macro',
|
|
1259
1276
|
members: [],
|
|
1260
1277
|
modifiers,
|
|
@@ -1284,7 +1301,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1284
1301
|
|
|
1285
1302
|
const nameNode = node.childForFieldName('name');
|
|
1286
1303
|
if (nameNode) {
|
|
1287
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1304
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1288
1305
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1289
1306
|
const visibility = extractVisibility(node.text);
|
|
1290
1307
|
// `pub type StyledString = SpannedString<Style>;` — the alias IS
|
|
@@ -1300,6 +1317,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1300
1317
|
name: nameNode.text,
|
|
1301
1318
|
startLine,
|
|
1302
1319
|
endLine,
|
|
1320
|
+
sourceSpan,
|
|
1303
1321
|
type: 'type',
|
|
1304
1322
|
members: [],
|
|
1305
1323
|
modifiers: visibility ? [visibility] : [],
|
|
@@ -1367,15 +1385,19 @@ function extractDerefTarget(implNode, traitName) {
|
|
|
1367
1385
|
* Returns true if node was matched, false otherwise
|
|
1368
1386
|
*/
|
|
1369
1387
|
function _processState(node, objects, lines) {
|
|
1370
|
-
//
|
|
1388
|
+
// Const and static items have value identity regardless of spelling or
|
|
1389
|
+
// module nesting. Associated items are extracted with their owner.
|
|
1390
|
+
// The declared type
|
|
1371
1391
|
// (fix #392) types the item when it is a method receiver (`FLAGS.iter()`).
|
|
1372
1392
|
if (node.type === 'const_item' || node.type === 'static_item') {
|
|
1373
|
-
|
|
1393
|
+
for (let scope = node.parent; scope; scope = scope.parent) {
|
|
1394
|
+
if (scope.type === 'impl_item' || scope.type === 'trait_item') return false;
|
|
1395
|
+
}
|
|
1374
1396
|
const nameNode = node.childForFieldName('name');
|
|
1375
1397
|
if (nameNode) {
|
|
1376
1398
|
const name = nameNode.text;
|
|
1377
|
-
|
|
1378
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1399
|
+
{
|
|
1400
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1379
1401
|
// Only a plain type path (behind any references) is recorded:
|
|
1380
1402
|
// a generic wrapper (`Lazy<T>`, `Mutex<T>`) owns the methods
|
|
1381
1403
|
// or derefs to its argument. A `static mut` is never typed.
|
|
@@ -1389,7 +1411,7 @@ function _processState(node, objects, lines) {
|
|
|
1389
1411
|
node.children.some(child => child.type === 'mutable_specifier');
|
|
1390
1412
|
const plain = typeNode && (typeNode.type === 'type_identifier' ||
|
|
1391
1413
|
typeNode.type === 'scoped_type_identifier');
|
|
1392
|
-
objects.push({ name, startLine, endLine,
|
|
1414
|
+
objects.push({ name, startLine, endLine, sourceSpan, ...rustBlockItemScope(node),
|
|
1393
1415
|
...(plain && !mutable && { valueType: typeNode.text,
|
|
1394
1416
|
...(deref && { valueDerefVia: deref.via }) }) });
|
|
1395
1417
|
}
|
|
@@ -1517,6 +1539,45 @@ function rustRawPointerTypeOf(typeNode) {
|
|
|
1517
1539
|
return current.children.some(child => child.type === 'mutable_specifier') ? '*mut' : '*const';
|
|
1518
1540
|
}
|
|
1519
1541
|
|
|
1542
|
+
const primitiveTypeInfoCache = new (require('../core/source-memo').BoundedMap)({ maxEntries: 256 });
|
|
1543
|
+
const derefTypeInfoCache = new (require('../core/source-memo').BoundedMap)({ maxEntries: 256 });
|
|
1544
|
+
|
|
1545
|
+
/** Source-only target of a written deref wrapper; core verifies its binding. */
|
|
1546
|
+
function getDerefTypeInfo(text, parser) {
|
|
1547
|
+
if (typeof text !== 'string' || !text.includes('<')) return null;
|
|
1548
|
+
if (derefTypeInfoCache.has(text)) return derefTypeInfoCache.get(text);
|
|
1549
|
+
const tree = parseTree(parser, `type __ucn_type = ${text};`);
|
|
1550
|
+
const items = tree.rootNode.namedChildren.filter(node => !node.type.endsWith('comment'));
|
|
1551
|
+
const item = items.length === 1 && items[0].type === 'type_item' ? items[0] : null;
|
|
1552
|
+
const declared = !tree.rootNode.hasError && item?.childForFieldName('type');
|
|
1553
|
+
const chain = declared && rustStdDerefChain(declared);
|
|
1554
|
+
const info = chain ? Object.freeze({
|
|
1555
|
+
via: Object.freeze([...chain.via]), innerText: chain.inner.text,
|
|
1556
|
+
}) : null;
|
|
1557
|
+
derefTypeInfoCache.set(text, info);
|
|
1558
|
+
return info;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/** Primitive identity of a declared type, independent of nominal names. */
|
|
1562
|
+
function getPrimitiveTypeInfo(text, parser) {
|
|
1563
|
+
if (typeof text !== 'string' || (!text.includes('[') && !text.includes('*'))) return null;
|
|
1564
|
+
if (primitiveTypeInfoCache.has(text)) return primitiveTypeInfoCache.get(text);
|
|
1565
|
+
const tree = parseTree(parser, `type __ucn_type = ${text};`);
|
|
1566
|
+
const items = tree.rootNode.namedChildren.filter(node => !node.type.endsWith('comment'));
|
|
1567
|
+
const item = items.length === 1 && items[0].type === 'type_item' ? items[0] : null;
|
|
1568
|
+
const declared = !tree.rootNode.hasError && item?.childForFieldName('type');
|
|
1569
|
+
const deref = declared && rustDerefTypedName(declared);
|
|
1570
|
+
const inner = deref?.inner || declared;
|
|
1571
|
+
const type = rustSliceTypeOf(inner) || rustRawPointerTypeOf(inner);
|
|
1572
|
+
const info = type ? Object.freeze({
|
|
1573
|
+
type,
|
|
1574
|
+
...(deref && { derefVia: Object.freeze([...deref.via]),
|
|
1575
|
+
derefOuterRef: deref.outerRef }),
|
|
1576
|
+
}) : null;
|
|
1577
|
+
primitiveTypeInfoCache.set(text, info);
|
|
1578
|
+
return info;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1520
1581
|
/**
|
|
1521
1582
|
* Standard-library wrappers whose `Deref::Target` is their (first) type
|
|
1522
1583
|
* argument (fix #401): smart pointers, `ManuallyDrop`, lazy cells, `RefCell`
|
|
@@ -1671,11 +1732,12 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
1671
1732
|
// `self.0.method()` exactly.
|
|
1672
1733
|
if (field.type === 'visibility_modifier' ||
|
|
1673
1734
|
field.type === 'attribute_item') continue;
|
|
1674
|
-
const { startLine, endLine } = nodeToLocation(field, code);
|
|
1735
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
|
|
1675
1736
|
fields.push({
|
|
1676
1737
|
name: String(position++),
|
|
1677
1738
|
startLine,
|
|
1678
1739
|
endLine,
|
|
1740
|
+
sourceSpan,
|
|
1679
1741
|
memberType: 'field',
|
|
1680
1742
|
fieldType: field.text,
|
|
1681
1743
|
});
|
|
@@ -1686,7 +1748,7 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
1686
1748
|
for (let i = 0; i < bodyNode.namedChildCount; i++) {
|
|
1687
1749
|
const field = bodyNode.namedChild(i);
|
|
1688
1750
|
if (field.type === 'field_declaration') {
|
|
1689
|
-
const { startLine, endLine } = nodeToLocation(field, code);
|
|
1751
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
|
|
1690
1752
|
const nameNode = field.childForFieldName('name');
|
|
1691
1753
|
const typeNode = field.childForFieldName('type');
|
|
1692
1754
|
|
|
@@ -1700,6 +1762,7 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
1700
1762
|
name: nameNode.text,
|
|
1701
1763
|
startLine,
|
|
1702
1764
|
endLine,
|
|
1765
|
+
sourceSpan,
|
|
1703
1766
|
memberType: 'field',
|
|
1704
1767
|
...(visibility && { modifiers: [visibility] }),
|
|
1705
1768
|
...(typeNode && { fieldType: typeNode.text })
|
|
@@ -1853,7 +1916,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
|
|
|
1853
1916
|
if (child.type === 'enum_variant') {
|
|
1854
1917
|
const nameNode = child.childForFieldName('name');
|
|
1855
1918
|
if (nameNode) {
|
|
1856
|
-
const { startLine, endLine } = nodeToLocation(child, code);
|
|
1919
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
|
|
1857
1920
|
// Check for tuple/struct variant data
|
|
1858
1921
|
let params = undefined;
|
|
1859
1922
|
// A unit variant names its value, a tuple variant its
|
|
@@ -1870,6 +1933,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
|
|
|
1870
1933
|
name: nameNode.text,
|
|
1871
1934
|
startLine,
|
|
1872
1935
|
endLine,
|
|
1936
|
+
sourceSpan,
|
|
1873
1937
|
memberType: 'variant',
|
|
1874
1938
|
...(params !== undefined && { params }),
|
|
1875
1939
|
...(valueShape && { valueShape }),
|
|
@@ -1894,7 +1958,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
|
|
|
1894
1958
|
if (child.type === 'function_item' || child.type === 'function_signature_item') {
|
|
1895
1959
|
const nameNode = child.childForFieldName('name');
|
|
1896
1960
|
if (nameNode) {
|
|
1897
|
-
const { startLine, endLine } = nodeToLocation(child, code);
|
|
1961
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
|
|
1898
1962
|
const paramsNode = child.childForFieldName('parameters');
|
|
1899
1963
|
const returnType = extractReturnType(child);
|
|
1900
1964
|
const iteratorItemType = extractRustIteratorItemType(child);
|
|
@@ -1911,6 +1975,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
|
|
|
1911
1975
|
name: nameNode.text,
|
|
1912
1976
|
startLine,
|
|
1913
1977
|
endLine,
|
|
1978
|
+
sourceSpan,
|
|
1914
1979
|
memberType: 'method',
|
|
1915
1980
|
isMethod: true,
|
|
1916
1981
|
...(qualifiers.async && { isAsync: true }),
|
|
@@ -2050,7 +2115,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
|
|
|
2050
2115
|
const paramsNode = child.childForFieldName('parameters');
|
|
2051
2116
|
|
|
2052
2117
|
if (nameNode) {
|
|
2053
|
-
const { startLine, endLine } = nodeToLocation(child, code);
|
|
2118
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
|
|
2054
2119
|
const text = child.text;
|
|
2055
2120
|
const returnType = extractReturnType(child);
|
|
2056
2121
|
const iteratorItemType = extractRustIteratorItemType(child);
|
|
@@ -2100,6 +2165,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
|
|
|
2100
2165
|
...(callbackParamTypes && { callbackParamTypes }),
|
|
2101
2166
|
startLine,
|
|
2102
2167
|
endLine,
|
|
2168
|
+
sourceSpan,
|
|
2103
2169
|
memberType: 'method',
|
|
2104
2170
|
isAsync: qualifiers.async,
|
|
2105
2171
|
isMethod: hasSelf, // Only true methods (with self) — associated functions are false
|
|
@@ -2179,10 +2245,51 @@ function parse(code, parser) {
|
|
|
2179
2245
|
const names = rustAsyncClosureNames(tree);
|
|
2180
2246
|
return names.length > 0 ? { asyncClosureNames: names } : {};
|
|
2181
2247
|
})(),
|
|
2248
|
+
...(() => {
|
|
2249
|
+
const itemMacros = rustModuleItemMacros(tree.rootNode);
|
|
2250
|
+
return itemMacros.length > 0 ? { itemMacros } : {};
|
|
2251
|
+
})(),
|
|
2182
2252
|
imports: [], exports: [],
|
|
2183
2253
|
};
|
|
2184
2254
|
}
|
|
2185
2255
|
|
|
2256
|
+
/**
|
|
2257
|
+
* Macro invocations in item position of a module (fix #422): `name!(..)`
|
|
2258
|
+
* directly in a file or an inline module body, with the identifier words
|
|
2259
|
+
* of their arguments. Such an invocation may declare items named by those
|
|
2260
|
+
* words, so a lookup of one of them in that module cannot rule out a name
|
|
2261
|
+
* the expansion binds.
|
|
2262
|
+
*/
|
|
2263
|
+
function rustModuleItemMacros(root) {
|
|
2264
|
+
const out = [];
|
|
2265
|
+
const visit = container => {
|
|
2266
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
2267
|
+
let item = container.namedChild(i);
|
|
2268
|
+
if (item.type === 'expression_statement' && item.namedChildCount === 1) item = item.namedChild(0);
|
|
2269
|
+
if (item.type === 'macro_invocation') {
|
|
2270
|
+
const macro = item.childForFieldName('macro');
|
|
2271
|
+
if (!macro) continue;
|
|
2272
|
+
const name = macro.type === 'scoped_identifier'
|
|
2273
|
+
? macro.childForFieldName('name')?.text : macro.text;
|
|
2274
|
+
if (!name) continue;
|
|
2275
|
+
const words = new Set();
|
|
2276
|
+
for (const child of item.namedChildren) {
|
|
2277
|
+
if (child.type !== 'token_tree') continue;
|
|
2278
|
+
for (const token of child.descendantsOfType('identifier')) words.add(token.text);
|
|
2279
|
+
}
|
|
2280
|
+
out.push({ name, line: item.startPosition.row + 1, words: [...words].sort() });
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
if (item.type === 'mod_item') {
|
|
2284
|
+
const body = item.childForFieldName('body');
|
|
2285
|
+
if (body) visit(body);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
};
|
|
2289
|
+
visit(root);
|
|
2290
|
+
return out;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2186
2293
|
const DECLARATION_NODE_TYPES = [
|
|
2187
2294
|
'function_item', 'foreign_mod_item', 'struct_item', 'union_item', 'enum_item', 'trait_item', 'impl_item',
|
|
2188
2295
|
'mod_item', 'macro_definition', 'type_item', 'const_item', 'static_item',
|
|
@@ -2193,13 +2300,19 @@ const DECLARATION_NODE_TYPES = [
|
|
|
2193
2300
|
* extracts them (fix #374: the declarations a macro expansion generates,
|
|
2194
2301
|
* without walking the rest of the expanded file).
|
|
2195
2302
|
*/
|
|
2196
|
-
function parseDeclarationsIn(code, roots) {
|
|
2303
|
+
function parseDeclarationsIn(code, roots, { descendants = null } = {}) {
|
|
2197
2304
|
const lines = code.split('\n');
|
|
2198
2305
|
const functions = [], classes = [], stateObjects = [];
|
|
2199
2306
|
const processedFn = new Set(), processedCls = new Set();
|
|
2200
|
-
|
|
2307
|
+
// `descendants(root, i)`: the root's descendant nodes the caller already
|
|
2308
|
+
// listed (a superset of DECLARATION_NODE_TYPES, in document order).
|
|
2309
|
+
for (let i = 0; i < roots.length; i++) {
|
|
2310
|
+
const root = roots[i];
|
|
2201
2311
|
const nodes = DECLARATION_NODE_TYPES.includes(root.type) ? [root] : [];
|
|
2202
|
-
|
|
2312
|
+
const below = descendants
|
|
2313
|
+
? descendants(root, i).filter(node => DECLARATION_NODE_TYPES.includes(node.type))
|
|
2314
|
+
: root.descendantsOfType(DECLARATION_NODE_TYPES);
|
|
2315
|
+
for (const node of nodes.concat(below)) {
|
|
2203
2316
|
_processFunction(node, functions, processedFn, lines, code);
|
|
2204
2317
|
_processClass(node, classes, processedCls, lines, code);
|
|
2205
2318
|
_processState(node, stateObjects, lines);
|
|
@@ -2686,7 +2799,10 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
2686
2799
|
// associated-call shape `Type::default()`. It is still an AST token,
|
|
2687
2800
|
// so admitting it here preserves the AST-first rule while recovering
|
|
2688
2801
|
// calls nested inside macro token trees (assert_eq!, matches!, ...).
|
|
2689
|
-
|
|
2802
|
+
// Primitive type words can also name value-namespace functions and
|
|
2803
|
+
// methods (`width.char(...)`). The following call-shape check still
|
|
2804
|
+
// requires argument tokens, so type annotations are not calls.
|
|
2805
|
+
if (tok.type !== 'identifier' && tok.type !== 'default' && tok.type !== 'primitive_type') continue;
|
|
2690
2806
|
const next = children[i + 1];
|
|
2691
2807
|
const prev = children[i - 1];
|
|
2692
2808
|
// $metavariable(...) — a macro fragment, not a named call
|
|
@@ -2771,6 +2887,8 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
2771
2887
|
isPathCall: true,
|
|
2772
2888
|
receiver: segments.length > 0 ? segments.join('::') : undefined,
|
|
2773
2889
|
...(tokenArgTypes && { argTypes: tokenArgTypes }),
|
|
2890
|
+
...(segments.length > 0 && rustReceiverScopeBindingField(tok, segments.join('::'))),
|
|
2891
|
+
...(segments.length > 0 && rustTokenTreeMayBind(tok, segments[0]) && { macroScopeUnknown: true }),
|
|
2774
2892
|
...macroFields,
|
|
2775
2893
|
enclosingFunction
|
|
2776
2894
|
};
|
|
@@ -2860,6 +2978,9 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
2860
2978
|
callEnd: callArgs.endIndex,
|
|
2861
2979
|
isMethod: false,
|
|
2862
2980
|
...macroFields,
|
|
2981
|
+
...(rustBareNameShadowedByLocal(tok, tok.text) && { localShadow: true }),
|
|
2982
|
+
...rustScopeBindingField(tok, tok.text),
|
|
2983
|
+
...(rustTokenTreeMayBind(tok, tok.text) && { macroScopeUnknown: true }),
|
|
2863
2984
|
enclosingFunction
|
|
2864
2985
|
};
|
|
2865
2986
|
calls.push(record);
|
|
@@ -3369,6 +3490,41 @@ function rustMacroCallIdentity(macroNode) {
|
|
|
3369
3490
|
* value then has the fn's declared return type, which lets a trait path call
|
|
3370
3491
|
* (`Trait::assoc(..)`) infer its Self from the declaration.
|
|
3371
3492
|
*/
|
|
3493
|
+
/**
|
|
3494
|
+
* Where a call's value goes with a written type (fix #422): a struct
|
|
3495
|
+
* expression's field (`Self { inner: Default::default() }` ->
|
|
3496
|
+
* `{ fieldInit: { struct: 'Self', field: 'inner' } }`) or an annotated `let`
|
|
3497
|
+
* (`let x: T = T::new()` -> `{ expectedType: 'T' }`). The type a
|
|
3498
|
+
* Self-returning trait member is selected by.
|
|
3499
|
+
*/
|
|
3500
|
+
function rustExpectedTypeFields(node) {
|
|
3501
|
+
const parent = node.parent;
|
|
3502
|
+
if (parent?.type === 'field_initializer' && sameNode(parent.childForFieldName('value'), node)) {
|
|
3503
|
+
const field = parent.childForFieldName('field');
|
|
3504
|
+
const expression = parent.parent?.type === 'field_initializer_list' ? parent.parent.parent : null;
|
|
3505
|
+
const nameNode = expression?.type === 'struct_expression' ? expression.childForFieldName('name') : null;
|
|
3506
|
+
const struct = rustPlainPathText(nameNode?.type === 'generic_type_with_turbofish'
|
|
3507
|
+
? nameNode.childForFieldName('type') : nameNode) || (nameNode?.type === 'type_identifier' ? nameNode.text : null);
|
|
3508
|
+
if (field?.type === 'field_identifier' && struct) return { fieldInit: { struct, field: field.text } };
|
|
3509
|
+
return null;
|
|
3510
|
+
}
|
|
3511
|
+
if (parent?.type === 'let_declaration' && sameNode(parent.childForFieldName('value'), node)) {
|
|
3512
|
+
const type = parent.childForFieldName('type');
|
|
3513
|
+
if (type) return { expectedType: type.text.replace(/\s+/g, '') };
|
|
3514
|
+
}
|
|
3515
|
+
// An argument of a plain or path function call (`run(Default::default())`,
|
|
3516
|
+
// fix #434): the callee's declared parameter type at that position.
|
|
3517
|
+
if (parent?.type === 'arguments' && parent.parent?.type === 'call_expression') {
|
|
3518
|
+
const callee = parent.parent.childForFieldName('function');
|
|
3519
|
+
const path = callee?.type === 'identifier' ? callee.text : rustPlainPathText(callee);
|
|
3520
|
+
if (!path) return null;
|
|
3521
|
+
const index = parent.namedChildren.filter(child => !child.type.endsWith('comment'))
|
|
3522
|
+
.findIndex(child => sameNode(child, node));
|
|
3523
|
+
if (index >= 0) return { argumentOf: { callee: path, index } };
|
|
3524
|
+
}
|
|
3525
|
+
return null;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3372
3528
|
function rustFunctionReturnPosition(node) {
|
|
3373
3529
|
const parent = node.parent;
|
|
3374
3530
|
if (!parent) return false;
|
|
@@ -3426,6 +3582,283 @@ function rustPatternBindsName(pattern, name) {
|
|
|
3426
3582
|
return false;
|
|
3427
3583
|
}
|
|
3428
3584
|
|
|
3585
|
+
/**
|
|
3586
|
+
* Join two Rust use-path pieces (`a::b` + `c` -> `a::b::c`).
|
|
3587
|
+
*/
|
|
3588
|
+
function rustJoinUsePath(prefix, suffix) {
|
|
3589
|
+
// Paths are compared as written without whitespace (`a:: b` is `a::b`).
|
|
3590
|
+
const left = String(prefix || '').replace(/\s+/g, '').replace(/::$/, '');
|
|
3591
|
+
const right = String(suffix || '').replace(/\s+/g, '').replace(/^::/, '');
|
|
3592
|
+
return left && right ? `${left}::${right}` : left || right;
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
/**
|
|
3596
|
+
* Every binding a `use` tree makes (fix #422): `visit({ path, local,
|
|
3597
|
+
* original })` for a named leaf (the path ends with the item's ORIGINAL
|
|
3598
|
+
* name; `local` is the name the scope binds, an alias for `as`), and
|
|
3599
|
+
* `visit({ path, glob: true })` for `path::*`. `use a::b::{self}` binds
|
|
3600
|
+
* `b` to the path `a::b`.
|
|
3601
|
+
*/
|
|
3602
|
+
function walkRustUseTree(node, prefix, visit) {
|
|
3603
|
+
if (!node || node.type === 'visibility_modifier') return;
|
|
3604
|
+
if (node.type === 'scoped_use_list') {
|
|
3605
|
+
const pathNode = node.childForFieldName('path');
|
|
3606
|
+
const listNode = node.childForFieldName('list');
|
|
3607
|
+
if (listNode) walkRustUseTree(listNode, rustJoinUsePath(prefix, pathNode?.text), visit);
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
if (node.type === 'use_list') {
|
|
3611
|
+
for (let i = 0; i < node.namedChildCount; i++) walkRustUseTree(node.namedChild(i), prefix, visit);
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
if (node.type === 'use_as_clause') {
|
|
3615
|
+
const pathNode = node.namedChild(0);
|
|
3616
|
+
const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
|
|
3617
|
+
if (pathNode && aliasNode) {
|
|
3618
|
+
const original = String(pathNode.text).replace(/\s+/g, '').split('::').pop();
|
|
3619
|
+
visit({ path: rustJoinUsePath(prefix, pathNode.text), local: aliasNode.text, original, node });
|
|
3620
|
+
}
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
if (node.type === 'use_wildcard') {
|
|
3624
|
+
visit({ path: rustJoinUsePath(prefix, node.namedChild(0)?.text), glob: true, node });
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
if (node.type === 'identifier' || node.type === 'scoped_identifier' ||
|
|
3628
|
+
node.type === 'crate' || node.type === 'self' || node.type === 'super') {
|
|
3629
|
+
if (node.text === 'self' && prefix) {
|
|
3630
|
+
const local = prefix.split('::').pop();
|
|
3631
|
+
visit({ path: prefix, local, original: local, node });
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
const local = node.text.replace(/\s+/g, '').split('::').pop();
|
|
3635
|
+
visit({ path: rustJoinUsePath(prefix, node.text), local, original: local, node });
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
// Item kinds a block declares in the value namespace, and in the type
|
|
3640
|
+
// namespace (fix #422). A tuple or unit struct names a value too.
|
|
3641
|
+
const RUST_BLOCK_VALUE_ITEMS = new Set(['function_item', 'const_item', 'static_item']);
|
|
3642
|
+
const RUST_BLOCK_TYPE_ITEMS = new Set(['struct_item', 'enum_item', 'union_item', 'trait_item',
|
|
3643
|
+
'type_item', 'mod_item']);
|
|
3644
|
+
|
|
3645
|
+
/**
|
|
3646
|
+
* The names a block's own items and `use` declarations bind (fix #422):
|
|
3647
|
+
* `{ value: Map, type: Map, globs }`, each map name -> `{ kind: 'use', path,
|
|
3648
|
+
* line }` or `{ kind: 'item', line }`. Rust resolves a name through the
|
|
3649
|
+
* innermost block that binds it, its explicit names before its globs.
|
|
3650
|
+
*/
|
|
3651
|
+
function rustBlockBindingTable(block) {
|
|
3652
|
+
let value = null;
|
|
3653
|
+
let type = null;
|
|
3654
|
+
let globs = null;
|
|
3655
|
+
const bind = (map, name, binding) => {
|
|
3656
|
+
if (!name) return map;
|
|
3657
|
+
const out = map || new Map();
|
|
3658
|
+
if (!out.has(name)) out.set(name, binding);
|
|
3659
|
+
return out;
|
|
3660
|
+
};
|
|
3661
|
+
for (let i = 0; i < block.namedChildCount; i++) {
|
|
3662
|
+
const item = block.namedChild(i);
|
|
3663
|
+
if (item.type === 'use_declaration') {
|
|
3664
|
+
const line = item.startPosition.row + 1;
|
|
3665
|
+
for (let j = 0; j < item.namedChildCount; j++) {
|
|
3666
|
+
walkRustUseTree(item.namedChild(j), '', leaf => {
|
|
3667
|
+
if (leaf.glob) {
|
|
3668
|
+
if (leaf.path) (globs || (globs = [])).push(leaf.path);
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
const binding = { kind: 'use', path: leaf.path, line };
|
|
3672
|
+
value = bind(value, leaf.local, binding);
|
|
3673
|
+
type = bind(type, leaf.local, binding);
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3676
|
+
continue;
|
|
3677
|
+
}
|
|
3678
|
+
if (item.type === 'foreign_mod_item') {
|
|
3679
|
+
const body = item.namedChildren.find(child => child.type === 'declaration_list');
|
|
3680
|
+
for (const decl of body?.namedChildren || []) {
|
|
3681
|
+
if (decl.type !== 'function_signature_item' && decl.type !== 'static_item') continue;
|
|
3682
|
+
const nameNode = decl.childForFieldName('name');
|
|
3683
|
+
value = bind(value, nameNode?.text, { kind: 'item', line: decl.startPosition.row + 1 });
|
|
3684
|
+
}
|
|
3685
|
+
continue;
|
|
3686
|
+
}
|
|
3687
|
+
const nameNode = (RUST_BLOCK_VALUE_ITEMS.has(item.type) || RUST_BLOCK_TYPE_ITEMS.has(item.type))
|
|
3688
|
+
? item.childForFieldName('name') : null;
|
|
3689
|
+
if (!nameNode) continue;
|
|
3690
|
+
const binding = { kind: 'item', line: item.startPosition.row + 1 };
|
|
3691
|
+
if (RUST_BLOCK_VALUE_ITEMS.has(item.type)) {
|
|
3692
|
+
value = bind(value, nameNode.text, binding);
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
type = bind(type, nameNode.text, binding);
|
|
3696
|
+
if (item.type === 'struct_item' && rustStructValueShape(item)) value = bind(value, nameNode.text, binding);
|
|
3697
|
+
}
|
|
3698
|
+
return value || type || globs ? { value, type, globs } : EMPTY_BLOCK_TABLE;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
const EMPTY_BLOCK_TABLE = Object.freeze({ value: null, type: null, globs: null });
|
|
3702
|
+
|
|
3703
|
+
/**
|
|
3704
|
+
* What the enclosing blocks of `node` bind `name` to in namespace `ns`
|
|
3705
|
+
* ('value' or 'type'), fix #422: the innermost block naming it explicitly
|
|
3706
|
+
* (`{ kind, path?, line }`), with `innerGlobs` when a nearer block has glob
|
|
3707
|
+
* imports that may provide it, or `{ kind: 'globs', paths }` when only
|
|
3708
|
+
* blocks' globs may. null when no block between the node and its module
|
|
3709
|
+
* binds the name (the module's own scope decides). Items of outer blocks
|
|
3710
|
+
* are visible inside nested fns and impls; an inline module starts a new
|
|
3711
|
+
* scope.
|
|
3712
|
+
*/
|
|
3713
|
+
const rustBlockTablesByTree = new WeakMap();
|
|
3714
|
+
|
|
3715
|
+
/** The memo of block binding tables for the tree holding `node`. */
|
|
3716
|
+
function rustBlockTables(node) {
|
|
3717
|
+
const tree = node?.tree;
|
|
3718
|
+
if (!tree) return new Map();
|
|
3719
|
+
let tables = rustBlockTablesByTree.get(tree);
|
|
3720
|
+
if (!tables) rustBlockTablesByTree.set(tree, tables = new Map());
|
|
3721
|
+
return tables;
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
/**
|
|
3725
|
+
* The receiver a bracketed path head spells (fix #422): `<T as Trait>` ->
|
|
3726
|
+
* `{ receiver: 'Trait', selfType: 'T' }`, `<T>` -> `{ receiver: 'T' }`;
|
|
3727
|
+
* null for any other head.
|
|
3728
|
+
*/
|
|
3729
|
+
function rustQualifiedPathHead(pathNode) {
|
|
3730
|
+
if (pathNode?.type !== 'bracketed_type') return null;
|
|
3731
|
+
const inner = pathNode.namedChild(0);
|
|
3732
|
+
const plainPath = n => n && (n.type === 'type_identifier' || n.type === 'scoped_type_identifier' ||
|
|
3733
|
+
n.type === 'identifier' || n.type === 'scoped_identifier') ? n.text.replace(/\s+/g, '') : null;
|
|
3734
|
+
if (inner?.type === 'qualified_type') {
|
|
3735
|
+
const trait = plainPath(inner.childForFieldName('alias'));
|
|
3736
|
+
if (!trait) return null;
|
|
3737
|
+
const selfType = plainPath(inner.childForFieldName('type'));
|
|
3738
|
+
return { receiver: trait, ...(selfType && { selfType }) };
|
|
3739
|
+
}
|
|
3740
|
+
const typeName = plainPath(inner);
|
|
3741
|
+
return typeName ? { receiver: typeName } : null;
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
const rustTokenTreeScopeFacts = new WeakMap();
|
|
3745
|
+
const RUST_SCOPING_KEYWORD = /\b(?:use|fn|mod|impl)\b/;
|
|
3746
|
+
|
|
3747
|
+
/**
|
|
3748
|
+
* Whether the macro invocation holding a token may bind `name` itself
|
|
3749
|
+
* (fix #422): its tokens hold a `use`, `fn`, `mod` or `impl` keyword (items
|
|
3750
|
+
* and imports scoped by what the macro expands to), or spell `name` other
|
|
3751
|
+
* than as a callee (a `let`, closure parameter or pattern). The scopes
|
|
3752
|
+
* around the invocation then do not decide what the call names. The tokens
|
|
3753
|
+
* are read only when the invocation's text can hold such a token: every
|
|
3754
|
+
* keyword token spells its keyword there, and a second token spelling the
|
|
3755
|
+
* name spells it a second time.
|
|
3756
|
+
*/
|
|
3757
|
+
function rustTokenTreeMayBind(token, name) {
|
|
3758
|
+
let top = null;
|
|
3759
|
+
for (let p = token.parent; p && p.type === 'token_tree'; p = p.parent) top = p;
|
|
3760
|
+
if (!top) return false;
|
|
3761
|
+
const tree = top.tree;
|
|
3762
|
+
let byTree = tree ? rustTokenTreeScopeFacts.get(tree) : null;
|
|
3763
|
+
if (tree && !byTree) rustTokenTreeScopeFacts.set(tree, byTree = new Map());
|
|
3764
|
+
let facts = byTree?.get(top.id);
|
|
3765
|
+
if (!facts) {
|
|
3766
|
+
const text = top.text;
|
|
3767
|
+
facts = { text, keyword: RUST_SCOPING_KEYWORD.test(text), tokens: null };
|
|
3768
|
+
byTree?.set(top.id, facts);
|
|
3769
|
+
}
|
|
3770
|
+
const tokens = () => facts.tokens || (facts.tokens = rustTokenTreeTokens(top));
|
|
3771
|
+
if (facts.keyword && tokens().scoped) return true;
|
|
3772
|
+
return rustWordSpelledTwice(facts.text, name) && tokens().words.has(name);
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
/** The keyword tokens and the non-callee identifier words of a token tree. */
|
|
3776
|
+
function rustTokenTreeTokens(top) {
|
|
3777
|
+
const facts = { scoped: false, words: new Set() };
|
|
3778
|
+
const visit = node => {
|
|
3779
|
+
const children = node.children;
|
|
3780
|
+
for (let i = 0; i < children.length; i++) {
|
|
3781
|
+
const child = children[i];
|
|
3782
|
+
const type = child.type;
|
|
3783
|
+
if (type === 'use' || type === 'fn' || type === 'mod' || type === 'impl') facts.scoped = true;
|
|
3784
|
+
if (type === 'token_tree') { visit(child); continue; }
|
|
3785
|
+
if (type !== 'identifier') continue;
|
|
3786
|
+
const next = children[i + 1];
|
|
3787
|
+
const callee = next && ((next.type === 'token_tree' && next.child(0)?.type === '(') ||
|
|
3788
|
+
next.type === '!' || next.type === '::');
|
|
3789
|
+
if (!callee) facts.words.add(child.text);
|
|
3790
|
+
}
|
|
3791
|
+
};
|
|
3792
|
+
visit(top);
|
|
3793
|
+
return facts;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
/** ASCII identifier characters: any other character counts as a boundary, so no token is missed. */
|
|
3797
|
+
function rustAsciiWordChar(code) {
|
|
3798
|
+
return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
/** Whether `word` stands at least twice in `text` between non-word characters. */
|
|
3802
|
+
function rustWordSpelledTwice(text, word) {
|
|
3803
|
+
if (!word) return false;
|
|
3804
|
+
let count = 0;
|
|
3805
|
+
for (let at = text.indexOf(word); at !== -1; at = text.indexOf(word, at + 1)) {
|
|
3806
|
+
if (at > 0 && rustAsciiWordChar(text.charCodeAt(at - 1))) continue;
|
|
3807
|
+
if (rustAsciiWordChar(text.charCodeAt(at + word.length))) continue;
|
|
3808
|
+
if (++count >= 2) return true;
|
|
3809
|
+
}
|
|
3810
|
+
return false;
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
/**
|
|
3814
|
+
* A plain path's text without whitespace or type arguments (`a::b::T`,
|
|
3815
|
+
* `Vec::<u8>` -> `Vec`), or null for any other path head.
|
|
3816
|
+
*/
|
|
3817
|
+
function rustPlainPathText(pathNode) {
|
|
3818
|
+
if (!pathNode) return null;
|
|
3819
|
+
if (pathNode.type === 'generic_type') return rustPlainPathText(pathNode.childForFieldName('type'));
|
|
3820
|
+
if (!['identifier', 'scoped_identifier', 'type_identifier', 'scoped_type_identifier',
|
|
3821
|
+
'self', 'super', 'crate'].includes(pathNode.type)) return null;
|
|
3822
|
+
const text = pathNode.text.replace(/\s+/g, '');
|
|
3823
|
+
return /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*$/.test(text) ? text : null;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
/** `scopeBinding` record field of a bare value name (fix #422), or null. */
|
|
3827
|
+
function rustScopeBindingField(node, name) {
|
|
3828
|
+
const binding = rustBlockScopeBinding(node, name, 'value', rustBlockTables(node));
|
|
3829
|
+
return binding ? { scopeBinding: binding } : null;
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
/** `receiverScopeBinding` of a path's first segment in the type namespace (fix #422). */
|
|
3833
|
+
function rustReceiverScopeBindingField(node, receiver) {
|
|
3834
|
+
const head = String(receiver || '').split('::')[0];
|
|
3835
|
+
if (!head || head === 'crate' || head === 'self' || head === 'super' || head === 'Self' ||
|
|
3836
|
+
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(head)) return null;
|
|
3837
|
+
const binding = rustBlockScopeBinding(node, head, 'type', rustBlockTables(node));
|
|
3838
|
+
return binding ? { receiverScopeBinding: binding } : null;
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3841
|
+
function rustBlockScopeBinding(node, name, ns, tables) {
|
|
3842
|
+
let globs = null;
|
|
3843
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
3844
|
+
if (p.type === 'mod_item' || p.type === 'source_file') break;
|
|
3845
|
+
if (p.type !== 'block') continue;
|
|
3846
|
+
let table = tables.get(p.id);
|
|
3847
|
+
if (!table) {
|
|
3848
|
+
table = rustBlockBindingTable(p);
|
|
3849
|
+
tables.set(p.id, table);
|
|
3850
|
+
}
|
|
3851
|
+
const hit = table[ns]?.get(name);
|
|
3852
|
+
if (hit) {
|
|
3853
|
+
const binding = { ...hit };
|
|
3854
|
+
if (globs) binding.innerGlobs = globs;
|
|
3855
|
+
return binding;
|
|
3856
|
+
}
|
|
3857
|
+
if (table.globs) globs = (globs || []).concat(table.globs);
|
|
3858
|
+
}
|
|
3859
|
+
return globs ? { kind: 'globs', paths: globs } : null;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3429
3862
|
function rustBareNameShadowedByLocal(callNode, name) {
|
|
3430
3863
|
let child = callNode;
|
|
3431
3864
|
for (let p = callNode.parent; p; child = p, p = p.parent) {
|
|
@@ -3737,8 +4170,17 @@ function rustFieldAliasOf(identNode) {
|
|
|
3737
4170
|
return null;
|
|
3738
4171
|
}
|
|
3739
4172
|
|
|
3740
|
-
|
|
3741
|
-
|
|
4173
|
+
/**
|
|
4174
|
+
* Call records of Rust code. `options` (fix #405, macro-expansion calls):
|
|
4175
|
+
* `tree` - the already parsed tree of `code` (an expanded file parsed with
|
|
4176
|
+
* included ranges), whose item-recovery facts are `itemRecovery` (null when
|
|
4177
|
+
* none); `skip(node)` - nodes whose subtrees are not read (other units of an
|
|
4178
|
+
* expanded file: every scope the extractor keeps is a function's own, so a
|
|
4179
|
+
* unit's records do not depend on its sibling units).
|
|
4180
|
+
*/
|
|
4181
|
+
function findCallsInCode(code, parser, options = {}) {
|
|
4182
|
+
const tree = options.tree || parseTree(parser, code);
|
|
4183
|
+
const skip = typeof options.skip === 'function' ? options.skip : null;
|
|
3742
4184
|
const calls = [];
|
|
3743
4185
|
// Words of every value-binding pattern seen so far in document order
|
|
3744
4186
|
// (fix #369). A binding shadows a bare call only if it precedes the call,
|
|
@@ -4378,7 +4820,7 @@ function findCallsInCode(code, parser) {
|
|
|
4378
4820
|
// Item bodies of item-position macros (`quickcheck! { fn p(..) { .. } }`,
|
|
4379
4821
|
// fix #401) are parsed as items: their calls are read from that parse,
|
|
4380
4822
|
// with their functions' scopes and local types, instead of as tokens.
|
|
4381
|
-
const itemRecovery = rustMacroItemCallRecovery(code, parser);
|
|
4823
|
+
const itemRecovery = options.tree ? (options.itemRecovery || null) : rustMacroItemCallRecovery(code, parser);
|
|
4382
4824
|
const recoveredTokenTrees = new Set((itemRecovery?.ranges || []).map(([start]) => start - 1));
|
|
4383
4825
|
|
|
4384
4826
|
const visitNode = (node) => {
|
|
@@ -4501,6 +4943,7 @@ function findCallsInCode(code, parser) {
|
|
|
4501
4943
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
4502
4944
|
...(mayBeLocallyBound(funcNode.text) &&
|
|
4503
4945
|
rustBareNameShadowedByLocal(node, funcNode.text) && { localShadow: true }),
|
|
4946
|
+
...rustScopeBindingField(node, funcNode.text),
|
|
4504
4947
|
});
|
|
4505
4948
|
} else if (funcNode.type === 'field_expression') {
|
|
4506
4949
|
// Method call: obj.method()
|
|
@@ -4847,8 +5290,13 @@ function findCallsInCode(code, parser) {
|
|
|
4847
5290
|
// Turbofish receivers (`Vec::<String>::new`) carry the type
|
|
4848
5291
|
// arguments as their own `::`-split segments — drop them so
|
|
4849
5292
|
// the receiver is the plain type/module path (fix #222).
|
|
4850
|
-
|
|
5293
|
+
let recvSegments = segments.slice(0, -1)
|
|
4851
5294
|
.filter(s => !s.startsWith('<') && !s.endsWith('>'));
|
|
5295
|
+
// A qualified path (fix #422): `<T as Trait>::m(x)` names
|
|
5296
|
+
// the trait's member with Self = T; `<T>::m()` is `T::m()`.
|
|
5297
|
+
const qualified = rustQualifiedPathHead(funcNode.childForFieldName('path'));
|
|
5298
|
+
if (qualified) recvSegments = qualified.receiver.split('::');
|
|
5299
|
+
const receiverText = recvSegments.length > 0 ? recvSegments.join('::') : undefined;
|
|
4852
5300
|
calls.push({
|
|
4853
5301
|
name: name,
|
|
4854
5302
|
line: node.startPosition.row + 1,
|
|
@@ -4858,7 +5306,9 @@ function findCallsInCode(code, parser) {
|
|
|
4858
5306
|
...(consumingMethod && { consumingMethod }),
|
|
4859
5307
|
isMethod: segments.length > 1,
|
|
4860
5308
|
isPathCall: true, // Distinguishes Type::func()/module::func() from obj.method()
|
|
4861
|
-
receiver:
|
|
5309
|
+
receiver: receiverText,
|
|
5310
|
+
...(qualified?.selfType && { receiverQualifiedSelf: qualified.selfType }),
|
|
5311
|
+
...(receiverText && rustReceiverScopeBindingField(node, receiverText)),
|
|
4862
5312
|
argCount,
|
|
4863
5313
|
...(segments.length > 1 && (() => {
|
|
4864
5314
|
const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
|
|
@@ -4872,6 +5322,7 @@ function findCallsInCode(code, parser) {
|
|
|
4872
5322
|
enclosingFunction,
|
|
4873
5323
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
4874
5324
|
...(rustFunctionReturnPosition(node) && { returnPosition: true }),
|
|
5325
|
+
...(receiverText && rustExpectedTypeFields(node)),
|
|
4875
5326
|
});
|
|
4876
5327
|
}
|
|
4877
5328
|
return true;
|
|
@@ -5057,6 +5508,32 @@ function findCallsInCode(code, parser) {
|
|
|
5057
5508
|
}
|
|
5058
5509
|
}
|
|
5059
5510
|
|
|
5511
|
+
// Path values passed as arguments (fix #422): `case(&t,
|
|
5512
|
+
// T::unknown_bits)`, `.map(Self::parse)`. A method value is
|
|
5513
|
+
// path-only in Rust; the path's qualifier decides what it names.
|
|
5514
|
+
if (node.type === 'scoped_identifier' && node.parent?.type === 'arguments') {
|
|
5515
|
+
const nameNode = node.childForFieldName('name');
|
|
5516
|
+
const pathNode = node.childForFieldName('path');
|
|
5517
|
+
const qualified = rustQualifiedPathHead(pathNode);
|
|
5518
|
+
const receiver = qualified ? qualified.receiver : rustPlainPathText(pathNode);
|
|
5519
|
+
if (nameNode?.type === 'identifier' && receiver) {
|
|
5520
|
+
calls.push({
|
|
5521
|
+
name: nameNode.text,
|
|
5522
|
+
line: nameNode.startPosition.row + 1,
|
|
5523
|
+
callStart: node.startIndex,
|
|
5524
|
+
callEnd: node.endIndex,
|
|
5525
|
+
isMethod: true,
|
|
5526
|
+
isPathCall: true,
|
|
5527
|
+
receiver,
|
|
5528
|
+
...(qualified?.selfType && { receiverQualifiedSelf: qualified.selfType }),
|
|
5529
|
+
...rustReceiverScopeBindingField(node, receiver),
|
|
5530
|
+
isFunctionReference: true,
|
|
5531
|
+
isPotentialCallback: true,
|
|
5532
|
+
enclosingFunction: getCurrentEnclosingFunction(),
|
|
5533
|
+
});
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5060
5537
|
// Track local variable types from let declarations
|
|
5061
5538
|
// Pattern 1: let s = Server { ... } (struct expression)
|
|
5062
5539
|
// Pattern 2: let s = Server::new() / ::from() / ::default() (scoped constructor)
|
|
@@ -5227,17 +5704,21 @@ function findCallsInCode(code, parser) {
|
|
|
5227
5704
|
}
|
|
5228
5705
|
}
|
|
5229
5706
|
};
|
|
5230
|
-
traverseTree(tree.rootNode,
|
|
5707
|
+
traverseTree(tree.rootNode, skip ? node => (skip(node) ? false : visitNode(node)) : visitNode,
|
|
5708
|
+
{ onLeave: leaveNode });
|
|
5231
5709
|
if (itemRecovery) {
|
|
5232
5710
|
const ranges = itemRecovery.ranges;
|
|
5233
5711
|
const inside = node => ranges.some(([start, end]) => node.startIndex >= start && node.endIndex <= end);
|
|
5234
5712
|
const overlaps = node => ranges.some(([start, end]) => node.startIndex < end && node.endIndex > start);
|
|
5235
5713
|
traverseTree(itemRecovery.tree.rootNode, node => {
|
|
5714
|
+
if (skip && skip(node)) return false;
|
|
5236
5715
|
if (inside(node)) return visitNode(node);
|
|
5237
5716
|
return overlaps(node);
|
|
5238
5717
|
}, { onLeave: node => { if (inside(node)) leaveNode(node); } });
|
|
5239
5718
|
}
|
|
5240
5719
|
|
|
5720
|
+
// A tree handed in is an expanded file: no unexpanded item bodies.
|
|
5721
|
+
if (options.tree) return calls;
|
|
5241
5722
|
const declaration = declarationTrees(code, parser);
|
|
5242
5723
|
if (!declaration.macroItemRecovery) return calls;
|
|
5243
5724
|
const functions = findFunctions(code, parser);
|
|
@@ -5273,12 +5754,7 @@ function findImportsInCode(code, parser) {
|
|
|
5273
5754
|
const tree = parseTree(parser, code);
|
|
5274
5755
|
const imports = [];
|
|
5275
5756
|
|
|
5276
|
-
const
|
|
5277
|
-
const left = String(prefix || '').replace(/::$/, '');
|
|
5278
|
-
const right = String(suffix || '').replace(/^::/, '');
|
|
5279
|
-
return left && right ? `${left}::${right}` : left || right;
|
|
5280
|
-
};
|
|
5281
|
-
const addLeaf = (module, localName, type = 'use', dynamic = false, line, rename = null) => {
|
|
5757
|
+
const addLeaf = (module, localName, type = 'use', dynamic = false, line, rename = null, scope = null) => {
|
|
5282
5758
|
if (!module || !localName) return;
|
|
5283
5759
|
imports.push({
|
|
5284
5760
|
module,
|
|
@@ -5287,63 +5763,33 @@ function findImportsInCode(code, parser) {
|
|
|
5287
5763
|
dynamic,
|
|
5288
5764
|
line,
|
|
5289
5765
|
...(rename && { renames: [rename] }),
|
|
5766
|
+
// A `use` in a block binds its names in that block only (fix
|
|
5767
|
+
// #422): its lines bound where the bindings are visible.
|
|
5768
|
+
...scope,
|
|
5290
5769
|
});
|
|
5291
5770
|
};
|
|
5292
|
-
const collectUseTree = (node,
|
|
5293
|
-
if (
|
|
5294
|
-
|
|
5295
|
-
const pathNode = node.childForFieldName('path');
|
|
5296
|
-
const listNode = node.childForFieldName('list');
|
|
5297
|
-
const nextPrefix = joinUsePath(prefix, pathNode?.text);
|
|
5298
|
-
if (listNode) collectUseTree(listNode, nextPrefix, line);
|
|
5771
|
+
const collectUseTree = (node, line, scope) => walkRustUseTree(node, '', leaf => {
|
|
5772
|
+
if (leaf.glob) {
|
|
5773
|
+
addLeaf(leaf.path, '*', 'use-glob', true, line, null, scope);
|
|
5299
5774
|
return;
|
|
5300
5775
|
}
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5776
|
+
// fix #353: `use alpha::widget as renamed; renamed()` — the alias
|
|
5777
|
+
// pairing feeds findCallers' import-rename surface (calledAs),
|
|
5778
|
+
// exactly like Python/JS `import x as y`. fix #357: `renames` pairs
|
|
5779
|
+
// the alias with ITS record so createImportBindings emits {name:
|
|
5780
|
+
// original, alias: local} (the #348 Python shape); two renames of
|
|
5781
|
+
// one source name from different modules each pair with their own
|
|
5782
|
+
// module instead of both bindings matching both calls. `names` keeps
|
|
5783
|
+
// the local spelling (parser contract, imports command).
|
|
5784
|
+
const renamed = leaf.node.type === 'use_as_clause' && leaf.original &&
|
|
5785
|
+
leaf.original !== leaf.local && leaf.original !== 'self';
|
|
5786
|
+
addLeaf(leaf.path, leaf.local, 'use', false, line,
|
|
5787
|
+
renamed ? { original: leaf.original, local: leaf.local } : null, scope);
|
|
5788
|
+
if (renamed) {
|
|
5789
|
+
if (!imports.aliases) imports.aliases = [];
|
|
5790
|
+
imports.aliases.push({ original: leaf.original, local: leaf.local });
|
|
5306
5791
|
}
|
|
5307
|
-
|
|
5308
|
-
const pathNode = node.namedChild(0);
|
|
5309
|
-
const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
|
|
5310
|
-
if (pathNode && aliasNode) {
|
|
5311
|
-
// fix #353: `use alpha::widget as renamed; renamed()` — the
|
|
5312
|
-
// alias pairing feeds findCallers' import-rename surface
|
|
5313
|
-
// (calledAs), exactly like Python/JS `import x as y`.
|
|
5314
|
-
// fix #357: `renames` pairs the alias with ITS record so
|
|
5315
|
-
// createImportBindings emits {name: original, alias: local}
|
|
5316
|
-
// (the #348 Python shape); two renames of one source name
|
|
5317
|
-
// from different modules each pair with their own module
|
|
5318
|
-
// instead of both bindings matching both calls. `names`
|
|
5319
|
-
// keeps the local spelling (parser contract, imports command).
|
|
5320
|
-
const original = String(pathNode.text).split('::').pop();
|
|
5321
|
-
const renamed = original && original !== aliasNode.text && original !== 'self';
|
|
5322
|
-
addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
|
|
5323
|
-
'use', false, line, renamed ? { original, local: aliasNode.text } : null);
|
|
5324
|
-
if (renamed) {
|
|
5325
|
-
if (!imports.aliases) imports.aliases = [];
|
|
5326
|
-
imports.aliases.push({ original, local: aliasNode.text });
|
|
5327
|
-
}
|
|
5328
|
-
}
|
|
5329
|
-
return;
|
|
5330
|
-
}
|
|
5331
|
-
if (node.type === 'use_wildcard') {
|
|
5332
|
-
const pathNode = node.namedChild(0);
|
|
5333
|
-
addLeaf(joinUsePath(prefix, pathNode?.text), '*',
|
|
5334
|
-
'use-glob', true, line);
|
|
5335
|
-
return;
|
|
5336
|
-
}
|
|
5337
|
-
if (node.type === 'identifier' || node.type === 'scoped_identifier' ||
|
|
5338
|
-
node.type === 'crate' || node.type === 'self' || node.type === 'super') {
|
|
5339
|
-
if (node.text === 'self' && prefix) {
|
|
5340
|
-
addLeaf(prefix, prefix.split('::').pop(), 'use', false, line);
|
|
5341
|
-
return;
|
|
5342
|
-
}
|
|
5343
|
-
const module = joinUsePath(prefix, node.text);
|
|
5344
|
-
addLeaf(module, node.text.split('::').pop(), 'use', false, line);
|
|
5345
|
-
}
|
|
5346
|
-
};
|
|
5792
|
+
});
|
|
5347
5793
|
|
|
5348
5794
|
traverseTreeCached(tree.rootNode, (node) => {
|
|
5349
5795
|
// use declarations
|
|
@@ -5356,9 +5802,16 @@ function findImportsInCode(code, parser) {
|
|
|
5356
5802
|
// bindings `crate::haystack::Haystack` and
|
|
5357
5803
|
// `crate::haystack::Builder`, rather than the lossy old
|
|
5358
5804
|
// `{ module: "crate", name: "haystack" }` approximation.
|
|
5805
|
+
const scope = rustBlockItemScope(node);
|
|
5806
|
+
// A `use` is an item: its visibility decides which modules a
|
|
5807
|
+
// glob of its module reaches it from (fix #422).
|
|
5808
|
+
const visibility = node.namedChildren.find(child => child.type === 'visibility_modifier')
|
|
5809
|
+
?.text.replace(/\s+/g, '');
|
|
5810
|
+
const recordScope = scope.lexicalScopeStartLine != null || visibility
|
|
5811
|
+
? { ...scope, ...(visibility && { visibility }) } : null;
|
|
5359
5812
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5360
5813
|
const child = node.namedChild(i);
|
|
5361
|
-
collectUseTree(child,
|
|
5814
|
+
collectUseTree(child, line, recordScope);
|
|
5362
5815
|
}
|
|
5363
5816
|
return true;
|
|
5364
5817
|
}
|
|
@@ -5973,7 +6426,13 @@ function isEntryPoint(symbol) {
|
|
|
5973
6426
|
}
|
|
5974
6427
|
|
|
5975
6428
|
module.exports = {
|
|
6429
|
+
// Query-time type lookup uses the same lexical ribs as call extraction.
|
|
6430
|
+
typePathScopeBinding: (node, written) => rustReceiverScopeBindingField(node, written)?.receiverScopeBinding || null,
|
|
6431
|
+
getPrimitiveTypeInfo,
|
|
6432
|
+
getDerefTypeInfo,
|
|
5976
6433
|
primeDeclarationTrees,
|
|
6434
|
+
DECLARATION_NODE_TYPES,
|
|
6435
|
+
rustExpandedItemRecovery,
|
|
5977
6436
|
parseDeclarationsIn,
|
|
5978
6437
|
rustMacroInvocationContext,
|
|
5979
6438
|
findFunctions,
|