ucn 5.5.1 → 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 +30 -21
- package/cli/index.js +1 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- 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 +217 -112
- 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 +151 -19
- package/core/rust-modules.js +1 -1
- 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 +124 -37
- 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 +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
package/languages/rust.js
CHANGED
|
@@ -692,7 +692,6 @@ function extractAttributesWithArgs(node, codeOrLines) {
|
|
|
692
692
|
}
|
|
693
693
|
|
|
694
694
|
// --- Module-scope constants for state object detection ---
|
|
695
|
-
const _STATE_PATTERN = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
|
|
696
695
|
|
|
697
696
|
// --- Single-pass helpers: extracted from find* callbacks ---
|
|
698
697
|
|
|
@@ -757,7 +756,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
757
756
|
const paramsNode = node.childForFieldName('parameters');
|
|
758
757
|
|
|
759
758
|
if (nameNode) {
|
|
760
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
759
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
761
760
|
const text = node.text;
|
|
762
761
|
|
|
763
762
|
const qualifiers = rustFunctionQualifiers(node);
|
|
@@ -798,6 +797,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
798
797
|
...(callbackParamTypes && { callbackParamTypes }),
|
|
799
798
|
startLine,
|
|
800
799
|
endLine,
|
|
800
|
+
sourceSpan,
|
|
801
801
|
indent,
|
|
802
802
|
modifiers,
|
|
803
803
|
...(returnType && { returnType }),
|
|
@@ -826,7 +826,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
826
826
|
const fName = child.childForFieldName('name');
|
|
827
827
|
const fParams = child.childForFieldName('parameters');
|
|
828
828
|
if (fName) {
|
|
829
|
-
const { startLine, endLine, indent } = nodeToLocation(child, lines);
|
|
829
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
830
830
|
const visibility = extractVisibility(child.text);
|
|
831
831
|
const returnType = extractReturnType(child);
|
|
832
832
|
const docstring = extractRustDocstring(lines, startLine);
|
|
@@ -843,6 +843,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
|
|
|
843
843
|
...(iteratorItemType && { iteratorItemType }),
|
|
844
844
|
startLine,
|
|
845
845
|
endLine,
|
|
846
|
+
sourceSpan,
|
|
846
847
|
indent,
|
|
847
848
|
modifiers,
|
|
848
849
|
...(returnType && { returnType }),
|
|
@@ -1078,7 +1079,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1078
1079
|
|
|
1079
1080
|
const nameNode = node.childForFieldName('name');
|
|
1080
1081
|
if (nameNode) {
|
|
1081
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1082
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1082
1083
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1083
1084
|
const visibility = extractVisibility(node.text);
|
|
1084
1085
|
const generics = extractGenerics(node);
|
|
@@ -1092,6 +1093,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1092
1093
|
name: nameNode.text,
|
|
1093
1094
|
startLine,
|
|
1094
1095
|
endLine,
|
|
1096
|
+
sourceSpan,
|
|
1095
1097
|
type: node.type === 'union_item' ? 'union' : 'struct',
|
|
1096
1098
|
members,
|
|
1097
1099
|
modifiers,
|
|
@@ -1112,7 +1114,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1112
1114
|
|
|
1113
1115
|
const nameNode = node.childForFieldName('name');
|
|
1114
1116
|
if (nameNode) {
|
|
1115
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1117
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1116
1118
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1117
1119
|
const visibility = extractVisibility(node.text);
|
|
1118
1120
|
const generics = extractGenerics(node);
|
|
@@ -1124,6 +1126,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1124
1126
|
name: nameNode.text,
|
|
1125
1127
|
startLine,
|
|
1126
1128
|
endLine,
|
|
1129
|
+
sourceSpan,
|
|
1127
1130
|
type: 'enum',
|
|
1128
1131
|
members: extractEnumVariants(node, lines),
|
|
1129
1132
|
modifiers,
|
|
@@ -1143,7 +1146,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1143
1146
|
|
|
1144
1147
|
const nameNode = node.childForFieldName('name');
|
|
1145
1148
|
if (nameNode) {
|
|
1146
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1149
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1147
1150
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1148
1151
|
const visibility = extractVisibility(node.text);
|
|
1149
1152
|
const generics = extractGenerics(node);
|
|
@@ -1153,6 +1156,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1153
1156
|
name: nameNode.text,
|
|
1154
1157
|
startLine,
|
|
1155
1158
|
endLine,
|
|
1159
|
+
sourceSpan,
|
|
1156
1160
|
type: 'trait',
|
|
1157
1161
|
members: extractTraitMembers(node, lines),
|
|
1158
1162
|
modifiers: visibility ? [visibility] : [],
|
|
@@ -1171,7 +1175,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1171
1175
|
if (processedRanges.has(rangeKey)) return true;
|
|
1172
1176
|
processedRanges.add(rangeKey);
|
|
1173
1177
|
|
|
1174
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1178
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1175
1179
|
const implInfo = extractImplInfo(node);
|
|
1176
1180
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1177
1181
|
const derefTarget = extractDerefTarget(node, implInfo.traitName);
|
|
@@ -1187,6 +1191,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1187
1191
|
name: implInfo.name,
|
|
1188
1192
|
startLine,
|
|
1189
1193
|
endLine,
|
|
1194
|
+
sourceSpan,
|
|
1190
1195
|
type: 'impl',
|
|
1191
1196
|
traitName: implInfo.traitName,
|
|
1192
1197
|
...rustBlockItemScope(node),
|
|
@@ -1210,7 +1215,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1210
1215
|
|
|
1211
1216
|
const nameNode = node.childForFieldName('name');
|
|
1212
1217
|
if (nameNode) {
|
|
1213
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1218
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1214
1219
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1215
1220
|
const visibility = extractVisibility(node.text);
|
|
1216
1221
|
const attributes = extractAttributes(node, lines);
|
|
@@ -1221,6 +1226,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1221
1226
|
name: nameNode.text,
|
|
1222
1227
|
startLine,
|
|
1223
1228
|
endLine,
|
|
1229
|
+
sourceSpan,
|
|
1224
1230
|
type: 'module',
|
|
1225
1231
|
members: [],
|
|
1226
1232
|
modifiers,
|
|
@@ -1238,7 +1244,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1238
1244
|
|
|
1239
1245
|
const nameNode = node.childForFieldName('name');
|
|
1240
1246
|
if (nameNode) {
|
|
1241
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1247
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1242
1248
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1243
1249
|
const inferred = _inferMacroReturn(node, nameNode.text);
|
|
1244
1250
|
|
|
@@ -1265,6 +1271,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1265
1271
|
name: nameNode.text,
|
|
1266
1272
|
startLine,
|
|
1267
1273
|
endLine,
|
|
1274
|
+
sourceSpan,
|
|
1268
1275
|
type: 'macro',
|
|
1269
1276
|
members: [],
|
|
1270
1277
|
modifiers,
|
|
@@ -1294,7 +1301,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1294
1301
|
|
|
1295
1302
|
const nameNode = node.childForFieldName('name');
|
|
1296
1303
|
if (nameNode) {
|
|
1297
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1304
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1298
1305
|
const docstring = extractRustDocstring(lines, startLine);
|
|
1299
1306
|
const visibility = extractVisibility(node.text);
|
|
1300
1307
|
// `pub type StyledString = SpannedString<Style>;` — the alias IS
|
|
@@ -1310,6 +1317,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
1310
1317
|
name: nameNode.text,
|
|
1311
1318
|
startLine,
|
|
1312
1319
|
endLine,
|
|
1320
|
+
sourceSpan,
|
|
1313
1321
|
type: 'type',
|
|
1314
1322
|
members: [],
|
|
1315
1323
|
modifiers: visibility ? [visibility] : [],
|
|
@@ -1377,15 +1385,19 @@ function extractDerefTarget(implNode, traitName) {
|
|
|
1377
1385
|
* Returns true if node was matched, false otherwise
|
|
1378
1386
|
*/
|
|
1379
1387
|
function _processState(node, objects, lines) {
|
|
1380
|
-
//
|
|
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
|
|
1381
1391
|
// (fix #392) types the item when it is a method receiver (`FLAGS.iter()`).
|
|
1382
1392
|
if (node.type === 'const_item' || node.type === 'static_item') {
|
|
1383
|
-
|
|
1393
|
+
for (let scope = node.parent; scope; scope = scope.parent) {
|
|
1394
|
+
if (scope.type === 'impl_item' || scope.type === 'trait_item') return false;
|
|
1395
|
+
}
|
|
1384
1396
|
const nameNode = node.childForFieldName('name');
|
|
1385
1397
|
if (nameNode) {
|
|
1386
1398
|
const name = nameNode.text;
|
|
1387
|
-
|
|
1388
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
1399
|
+
{
|
|
1400
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
1389
1401
|
// Only a plain type path (behind any references) is recorded:
|
|
1390
1402
|
// a generic wrapper (`Lazy<T>`, `Mutex<T>`) owns the methods
|
|
1391
1403
|
// or derefs to its argument. A `static mut` is never typed.
|
|
@@ -1399,7 +1411,7 @@ function _processState(node, objects, lines) {
|
|
|
1399
1411
|
node.children.some(child => child.type === 'mutable_specifier');
|
|
1400
1412
|
const plain = typeNode && (typeNode.type === 'type_identifier' ||
|
|
1401
1413
|
typeNode.type === 'scoped_type_identifier');
|
|
1402
|
-
objects.push({ name, startLine, endLine,
|
|
1414
|
+
objects.push({ name, startLine, endLine, sourceSpan, ...rustBlockItemScope(node),
|
|
1403
1415
|
...(plain && !mutable && { valueType: typeNode.text,
|
|
1404
1416
|
...(deref && { valueDerefVia: deref.via }) }) });
|
|
1405
1417
|
}
|
|
@@ -1527,6 +1539,45 @@ function rustRawPointerTypeOf(typeNode) {
|
|
|
1527
1539
|
return current.children.some(child => child.type === 'mutable_specifier') ? '*mut' : '*const';
|
|
1528
1540
|
}
|
|
1529
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
|
+
|
|
1530
1581
|
/**
|
|
1531
1582
|
* Standard-library wrappers whose `Deref::Target` is their (first) type
|
|
1532
1583
|
* argument (fix #401): smart pointers, `ManuallyDrop`, lazy cells, `RefCell`
|
|
@@ -1681,11 +1732,12 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
1681
1732
|
// `self.0.method()` exactly.
|
|
1682
1733
|
if (field.type === 'visibility_modifier' ||
|
|
1683
1734
|
field.type === 'attribute_item') continue;
|
|
1684
|
-
const { startLine, endLine } = nodeToLocation(field, code);
|
|
1735
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
|
|
1685
1736
|
fields.push({
|
|
1686
1737
|
name: String(position++),
|
|
1687
1738
|
startLine,
|
|
1688
1739
|
endLine,
|
|
1740
|
+
sourceSpan,
|
|
1689
1741
|
memberType: 'field',
|
|
1690
1742
|
fieldType: field.text,
|
|
1691
1743
|
});
|
|
@@ -1696,7 +1748,7 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
1696
1748
|
for (let i = 0; i < bodyNode.namedChildCount; i++) {
|
|
1697
1749
|
const field = bodyNode.namedChild(i);
|
|
1698
1750
|
if (field.type === 'field_declaration') {
|
|
1699
|
-
const { startLine, endLine } = nodeToLocation(field, code);
|
|
1751
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(field, code);
|
|
1700
1752
|
const nameNode = field.childForFieldName('name');
|
|
1701
1753
|
const typeNode = field.childForFieldName('type');
|
|
1702
1754
|
|
|
@@ -1710,6 +1762,7 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
1710
1762
|
name: nameNode.text,
|
|
1711
1763
|
startLine,
|
|
1712
1764
|
endLine,
|
|
1765
|
+
sourceSpan,
|
|
1713
1766
|
memberType: 'field',
|
|
1714
1767
|
...(visibility && { modifiers: [visibility] }),
|
|
1715
1768
|
...(typeNode && { fieldType: typeNode.text })
|
|
@@ -1863,7 +1916,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
|
|
|
1863
1916
|
if (child.type === 'enum_variant') {
|
|
1864
1917
|
const nameNode = child.childForFieldName('name');
|
|
1865
1918
|
if (nameNode) {
|
|
1866
|
-
const { startLine, endLine } = nodeToLocation(child, code);
|
|
1919
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
|
|
1867
1920
|
// Check for tuple/struct variant data
|
|
1868
1921
|
let params = undefined;
|
|
1869
1922
|
// A unit variant names its value, a tuple variant its
|
|
@@ -1880,6 +1933,7 @@ function extractEnumVariants(enumNode, codeOrLines) {
|
|
|
1880
1933
|
name: nameNode.text,
|
|
1881
1934
|
startLine,
|
|
1882
1935
|
endLine,
|
|
1936
|
+
sourceSpan,
|
|
1883
1937
|
memberType: 'variant',
|
|
1884
1938
|
...(params !== undefined && { params }),
|
|
1885
1939
|
...(valueShape && { valueShape }),
|
|
@@ -1904,7 +1958,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
|
|
|
1904
1958
|
if (child.type === 'function_item' || child.type === 'function_signature_item') {
|
|
1905
1959
|
const nameNode = child.childForFieldName('name');
|
|
1906
1960
|
if (nameNode) {
|
|
1907
|
-
const { startLine, endLine } = nodeToLocation(child, code);
|
|
1961
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
|
|
1908
1962
|
const paramsNode = child.childForFieldName('parameters');
|
|
1909
1963
|
const returnType = extractReturnType(child);
|
|
1910
1964
|
const iteratorItemType = extractRustIteratorItemType(child);
|
|
@@ -1921,6 +1975,7 @@ function extractTraitMembers(traitNode, codeOrLines) {
|
|
|
1921
1975
|
name: nameNode.text,
|
|
1922
1976
|
startLine,
|
|
1923
1977
|
endLine,
|
|
1978
|
+
sourceSpan,
|
|
1924
1979
|
memberType: 'method',
|
|
1925
1980
|
isMethod: true,
|
|
1926
1981
|
...(qualifiers.async && { isAsync: true }),
|
|
@@ -2060,7 +2115,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
|
|
|
2060
2115
|
const paramsNode = child.childForFieldName('parameters');
|
|
2061
2116
|
|
|
2062
2117
|
if (nameNode) {
|
|
2063
|
-
const { startLine, endLine } = nodeToLocation(child, code);
|
|
2118
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
|
|
2064
2119
|
const text = child.text;
|
|
2065
2120
|
const returnType = extractReturnType(child);
|
|
2066
2121
|
const iteratorItemType = extractRustIteratorItemType(child);
|
|
@@ -2110,6 +2165,7 @@ function extractImplMembers(implNode, codeOrLines, typeName) {
|
|
|
2110
2165
|
...(callbackParamTypes && { callbackParamTypes }),
|
|
2111
2166
|
startLine,
|
|
2112
2167
|
endLine,
|
|
2168
|
+
sourceSpan,
|
|
2113
2169
|
memberType: 'method',
|
|
2114
2170
|
isAsync: qualifiers.async,
|
|
2115
2171
|
isMethod: hasSelf, // Only true methods (with self) — associated functions are false
|
|
@@ -2189,10 +2245,51 @@ function parse(code, parser) {
|
|
|
2189
2245
|
const names = rustAsyncClosureNames(tree);
|
|
2190
2246
|
return names.length > 0 ? { asyncClosureNames: names } : {};
|
|
2191
2247
|
})(),
|
|
2248
|
+
...(() => {
|
|
2249
|
+
const itemMacros = rustModuleItemMacros(tree.rootNode);
|
|
2250
|
+
return itemMacros.length > 0 ? { itemMacros } : {};
|
|
2251
|
+
})(),
|
|
2192
2252
|
imports: [], exports: [],
|
|
2193
2253
|
};
|
|
2194
2254
|
}
|
|
2195
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
|
+
|
|
2196
2293
|
const DECLARATION_NODE_TYPES = [
|
|
2197
2294
|
'function_item', 'foreign_mod_item', 'struct_item', 'union_item', 'enum_item', 'trait_item', 'impl_item',
|
|
2198
2295
|
'mod_item', 'macro_definition', 'type_item', 'const_item', 'static_item',
|
|
@@ -2702,7 +2799,10 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
2702
2799
|
// associated-call shape `Type::default()`. It is still an AST token,
|
|
2703
2800
|
// so admitting it here preserves the AST-first rule while recovering
|
|
2704
2801
|
// calls nested inside macro token trees (assert_eq!, matches!, ...).
|
|
2705
|
-
|
|
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;
|
|
2706
2806
|
const next = children[i + 1];
|
|
2707
2807
|
const prev = children[i - 1];
|
|
2708
2808
|
// $metavariable(...) — a macro fragment, not a named call
|
|
@@ -2787,6 +2887,8 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
2787
2887
|
isPathCall: true,
|
|
2788
2888
|
receiver: segments.length > 0 ? segments.join('::') : undefined,
|
|
2789
2889
|
...(tokenArgTypes && { argTypes: tokenArgTypes }),
|
|
2890
|
+
...(segments.length > 0 && rustReceiverScopeBindingField(tok, segments.join('::'))),
|
|
2891
|
+
...(segments.length > 0 && rustTokenTreeMayBind(tok, segments[0]) && { macroScopeUnknown: true }),
|
|
2790
2892
|
...macroFields,
|
|
2791
2893
|
enclosingFunction
|
|
2792
2894
|
};
|
|
@@ -2876,6 +2978,9 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
2876
2978
|
callEnd: callArgs.endIndex,
|
|
2877
2979
|
isMethod: false,
|
|
2878
2980
|
...macroFields,
|
|
2981
|
+
...(rustBareNameShadowedByLocal(tok, tok.text) && { localShadow: true }),
|
|
2982
|
+
...rustScopeBindingField(tok, tok.text),
|
|
2983
|
+
...(rustTokenTreeMayBind(tok, tok.text) && { macroScopeUnknown: true }),
|
|
2879
2984
|
enclosingFunction
|
|
2880
2985
|
};
|
|
2881
2986
|
calls.push(record);
|
|
@@ -3385,6 +3490,41 @@ function rustMacroCallIdentity(macroNode) {
|
|
|
3385
3490
|
* value then has the fn's declared return type, which lets a trait path call
|
|
3386
3491
|
* (`Trait::assoc(..)`) infer its Self from the declaration.
|
|
3387
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
|
+
|
|
3388
3528
|
function rustFunctionReturnPosition(node) {
|
|
3389
3529
|
const parent = node.parent;
|
|
3390
3530
|
if (!parent) return false;
|
|
@@ -3442,6 +3582,283 @@ function rustPatternBindsName(pattern, name) {
|
|
|
3442
3582
|
return false;
|
|
3443
3583
|
}
|
|
3444
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
|
+
|
|
3445
3862
|
function rustBareNameShadowedByLocal(callNode, name) {
|
|
3446
3863
|
let child = callNode;
|
|
3447
3864
|
for (let p = callNode.parent; p; child = p, p = p.parent) {
|
|
@@ -4526,6 +4943,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
4526
4943
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
4527
4944
|
...(mayBeLocallyBound(funcNode.text) &&
|
|
4528
4945
|
rustBareNameShadowedByLocal(node, funcNode.text) && { localShadow: true }),
|
|
4946
|
+
...rustScopeBindingField(node, funcNode.text),
|
|
4529
4947
|
});
|
|
4530
4948
|
} else if (funcNode.type === 'field_expression') {
|
|
4531
4949
|
// Method call: obj.method()
|
|
@@ -4872,8 +5290,13 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
4872
5290
|
// Turbofish receivers (`Vec::<String>::new`) carry the type
|
|
4873
5291
|
// arguments as their own `::`-split segments — drop them so
|
|
4874
5292
|
// the receiver is the plain type/module path (fix #222).
|
|
4875
|
-
|
|
5293
|
+
let recvSegments = segments.slice(0, -1)
|
|
4876
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;
|
|
4877
5300
|
calls.push({
|
|
4878
5301
|
name: name,
|
|
4879
5302
|
line: node.startPosition.row + 1,
|
|
@@ -4883,7 +5306,9 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
4883
5306
|
...(consumingMethod && { consumingMethod }),
|
|
4884
5307
|
isMethod: segments.length > 1,
|
|
4885
5308
|
isPathCall: true, // Distinguishes Type::func()/module::func() from obj.method()
|
|
4886
|
-
receiver:
|
|
5309
|
+
receiver: receiverText,
|
|
5310
|
+
...(qualified?.selfType && { receiverQualifiedSelf: qualified.selfType }),
|
|
5311
|
+
...(receiverText && rustReceiverScopeBindingField(node, receiverText)),
|
|
4887
5312
|
argCount,
|
|
4888
5313
|
...(segments.length > 1 && (() => {
|
|
4889
5314
|
const argTypes = rustArgTypesOfNode(argNodes, astArgTypeOfName);
|
|
@@ -4897,6 +5322,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
4897
5322
|
enclosingFunction,
|
|
4898
5323
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
4899
5324
|
...(rustFunctionReturnPosition(node) && { returnPosition: true }),
|
|
5325
|
+
...(receiverText && rustExpectedTypeFields(node)),
|
|
4900
5326
|
});
|
|
4901
5327
|
}
|
|
4902
5328
|
return true;
|
|
@@ -5082,6 +5508,32 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
5082
5508
|
}
|
|
5083
5509
|
}
|
|
5084
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
|
+
|
|
5085
5537
|
// Track local variable types from let declarations
|
|
5086
5538
|
// Pattern 1: let s = Server { ... } (struct expression)
|
|
5087
5539
|
// Pattern 2: let s = Server::new() / ::from() / ::default() (scoped constructor)
|
|
@@ -5302,12 +5754,7 @@ function findImportsInCode(code, parser) {
|
|
|
5302
5754
|
const tree = parseTree(parser, code);
|
|
5303
5755
|
const imports = [];
|
|
5304
5756
|
|
|
5305
|
-
const
|
|
5306
|
-
const left = String(prefix || '').replace(/::$/, '');
|
|
5307
|
-
const right = String(suffix || '').replace(/^::/, '');
|
|
5308
|
-
return left && right ? `${left}::${right}` : left || right;
|
|
5309
|
-
};
|
|
5310
|
-
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) => {
|
|
5311
5758
|
if (!module || !localName) return;
|
|
5312
5759
|
imports.push({
|
|
5313
5760
|
module,
|
|
@@ -5316,63 +5763,33 @@ function findImportsInCode(code, parser) {
|
|
|
5316
5763
|
dynamic,
|
|
5317
5764
|
line,
|
|
5318
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,
|
|
5319
5769
|
});
|
|
5320
5770
|
};
|
|
5321
|
-
const collectUseTree = (node,
|
|
5322
|
-
if (
|
|
5323
|
-
|
|
5324
|
-
const pathNode = node.childForFieldName('path');
|
|
5325
|
-
const listNode = node.childForFieldName('list');
|
|
5326
|
-
const nextPrefix = joinUsePath(prefix, pathNode?.text);
|
|
5327
|
-
if (listNode) collectUseTree(listNode, nextPrefix, line);
|
|
5328
|
-
return;
|
|
5329
|
-
}
|
|
5330
|
-
if (node.type === 'use_list') {
|
|
5331
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5332
|
-
collectUseTree(node.namedChild(i), prefix, line);
|
|
5333
|
-
}
|
|
5334
|
-
return;
|
|
5335
|
-
}
|
|
5336
|
-
if (node.type === 'use_as_clause') {
|
|
5337
|
-
const pathNode = node.namedChild(0);
|
|
5338
|
-
const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
|
|
5339
|
-
if (pathNode && aliasNode) {
|
|
5340
|
-
// fix #353: `use alpha::widget as renamed; renamed()` — the
|
|
5341
|
-
// alias pairing feeds findCallers' import-rename surface
|
|
5342
|
-
// (calledAs), exactly like Python/JS `import x as y`.
|
|
5343
|
-
// fix #357: `renames` pairs the alias with ITS record so
|
|
5344
|
-
// createImportBindings emits {name: original, alias: local}
|
|
5345
|
-
// (the #348 Python shape); two renames of one source name
|
|
5346
|
-
// from different modules each pair with their own module
|
|
5347
|
-
// instead of both bindings matching both calls. `names`
|
|
5348
|
-
// keeps the local spelling (parser contract, imports command).
|
|
5349
|
-
const original = String(pathNode.text).split('::').pop();
|
|
5350
|
-
const renamed = original && original !== aliasNode.text && original !== 'self';
|
|
5351
|
-
addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
|
|
5352
|
-
'use', false, line, renamed ? { original, local: aliasNode.text } : null);
|
|
5353
|
-
if (renamed) {
|
|
5354
|
-
if (!imports.aliases) imports.aliases = [];
|
|
5355
|
-
imports.aliases.push({ original, local: aliasNode.text });
|
|
5356
|
-
}
|
|
5357
|
-
}
|
|
5771
|
+
const collectUseTree = (node, line, scope) => walkRustUseTree(node, '', leaf => {
|
|
5772
|
+
if (leaf.glob) {
|
|
5773
|
+
addLeaf(leaf.path, '*', 'use-glob', true, line, null, scope);
|
|
5358
5774
|
return;
|
|
5359
5775
|
}
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
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 });
|
|
5365
5791
|
}
|
|
5366
|
-
|
|
5367
|
-
node.type === 'crate' || node.type === 'self' || node.type === 'super') {
|
|
5368
|
-
if (node.text === 'self' && prefix) {
|
|
5369
|
-
addLeaf(prefix, prefix.split('::').pop(), 'use', false, line);
|
|
5370
|
-
return;
|
|
5371
|
-
}
|
|
5372
|
-
const module = joinUsePath(prefix, node.text);
|
|
5373
|
-
addLeaf(module, node.text.split('::').pop(), 'use', false, line);
|
|
5374
|
-
}
|
|
5375
|
-
};
|
|
5792
|
+
});
|
|
5376
5793
|
|
|
5377
5794
|
traverseTreeCached(tree.rootNode, (node) => {
|
|
5378
5795
|
// use declarations
|
|
@@ -5385,9 +5802,16 @@ function findImportsInCode(code, parser) {
|
|
|
5385
5802
|
// bindings `crate::haystack::Haystack` and
|
|
5386
5803
|
// `crate::haystack::Builder`, rather than the lossy old
|
|
5387
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;
|
|
5388
5812
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5389
5813
|
const child = node.namedChild(i);
|
|
5390
|
-
collectUseTree(child,
|
|
5814
|
+
collectUseTree(child, line, recordScope);
|
|
5391
5815
|
}
|
|
5392
5816
|
return true;
|
|
5393
5817
|
}
|
|
@@ -6002,6 +6426,10 @@ function isEntryPoint(symbol) {
|
|
|
6002
6426
|
}
|
|
6003
6427
|
|
|
6004
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,
|
|
6005
6433
|
primeDeclarationTrees,
|
|
6006
6434
|
DECLARATION_NODE_TYPES,
|
|
6007
6435
|
rustExpandedItemRecovery,
|