ucn 5.5.0 → 5.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/cli/index.js +5 -0
- package/core/account.js +32 -11
- package/core/analysis.js +9 -2
- package/core/cache.js +8 -1
- package/core/callers.js +378 -77
- package/core/project.js +15 -6
- package/core/rust-macro-expansion.js +1061 -148
- package/core/rust-modules.js +20 -2
- package/core/type-denotation.js +33 -9
- package/languages/rust.js +38 -7
- package/package.json +1 -1
package/core/callers.js
CHANGED
|
@@ -54,7 +54,74 @@ const RESERVED_RECEIVER_NAMES = new Set([
|
|
|
54
54
|
]);
|
|
55
55
|
const CROSS_OPERATION_FLOW_CACHE_LIMIT = 4096;
|
|
56
56
|
|
|
57
|
+
// The options confirmationFacts reads besides its resolvers; a falsy value
|
|
58
|
+
// reads as absent.
|
|
59
|
+
const _FACT_OPTION_KEYS = ['receiverType', 'receiverTypeSource', 'receiverOrigin', 'originFile', 'bindingId', 'sameClass'];
|
|
60
|
+
|
|
57
61
|
function _confirmationFacts(index, file, call, targets, options = {}) {
|
|
62
|
+
// Only the target identities depend on the targets: the facts of a
|
|
63
|
+
// record are collected once per operation (fix #405b: orientation refines
|
|
64
|
+
// every definition of a name over the same records, and each refinement
|
|
65
|
+
// collected them again for every record it excluded or kept). The engine
|
|
66
|
+
// reaches here with a view of the record built anew per query, so a view
|
|
67
|
+
// and options equal in every field to ones seen before reuse their facts.
|
|
68
|
+
const memo = _repeatedQueryMemo(index, 'confirmationFacts');
|
|
69
|
+
if (!memo) return _confirmationFactsUncached(index, file, call, targets, options);
|
|
70
|
+
const read = {};
|
|
71
|
+
for (const key of _FACT_OPTION_KEYS) if (options[key]) read[key] = options[key];
|
|
72
|
+
if (Number.isInteger(options.receiverTypeLine)) read.receiverTypeLine = options.receiverTypeLine;
|
|
73
|
+
// Views of one record share its file, line and name.
|
|
74
|
+
let byLine = memo.get(file);
|
|
75
|
+
if (!byLine) memo.set(file, byLine = new Map());
|
|
76
|
+
let byName = byLine.get(call.line);
|
|
77
|
+
if (!byName) byLine.set(call.line, byName = new Map());
|
|
78
|
+
let seen = byName.get(call.name);
|
|
79
|
+
if (!seen) byName.set(call.name, seen = []);
|
|
80
|
+
for (const entry of seen) {
|
|
81
|
+
if (_sameRecordValue(entry.call, call, 0) && _sameRecordValue(entry.read, read, 0)) {
|
|
82
|
+
return { ...entry.facts, targets: targets.map(declarationIdentity) };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const facts = _confirmationFactsUncached(index, file, call, targets, options);
|
|
86
|
+
// A copy: a consumer may add its own verdicts to the facts it was given.
|
|
87
|
+
if (seen.length < 16) seen.push({ call, read, facts: { ...facts } });
|
|
88
|
+
return facts;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A memo for the current operation once it runs a second caller query (an
|
|
93
|
+
* orientation ranking, a rename plan): a single query reads each record once
|
|
94
|
+
* and would only pay for the memo.
|
|
95
|
+
*/
|
|
96
|
+
function _repeatedQueryMemo(index, key) {
|
|
97
|
+
if (!(index._opFindCallersCaches?.runs > 1)) return null;
|
|
98
|
+
return index._opMemo?.(key, () => new Map()) || null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Deep equality of two call records or record views (own enumerable fields). */
|
|
102
|
+
function _sameRecordValue(a, b, depth) {
|
|
103
|
+
if (a === b) return true;
|
|
104
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null || depth > 6) return false;
|
|
105
|
+
if (Array.isArray(a)) {
|
|
106
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
107
|
+
for (let i = 0; i < a.length; i++) if (!_sameRecordValue(a[i], b[i], depth + 1)) return false;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
// Plain objects only: a Map or Set holds no own enumerable fields.
|
|
111
|
+
const proto = Object.getPrototypeOf(a);
|
|
112
|
+
if (Array.isArray(b) || (proto !== Object.prototype && proto !== null) ||
|
|
113
|
+
Object.getPrototypeOf(b) !== proto) return false;
|
|
114
|
+
let count = 0;
|
|
115
|
+
for (const key in a) {
|
|
116
|
+
if (!Object.hasOwn(a, key)) continue;
|
|
117
|
+
if (!Object.hasOwn(b, key) || !_sameRecordValue(a[key], b[key], depth + 1)) return false;
|
|
118
|
+
count++;
|
|
119
|
+
}
|
|
120
|
+
for (const key in b) if (Object.hasOwn(b, key)) count--;
|
|
121
|
+
return count === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function _confirmationFactsUncached(index, file, call, targets, options) {
|
|
58
125
|
return confirmationFacts(index, file, call, targets, {
|
|
59
126
|
...options,
|
|
60
127
|
selectOverload: selectProvenanceOverload,
|
|
@@ -147,6 +214,31 @@ function getCachedCalls(index, filePath, options = {}) {
|
|
|
147
214
|
return calls === result.calls ? result : { ...result, calls };
|
|
148
215
|
}
|
|
149
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Some of the call records of a Rust file whose macro expansions' calls are
|
|
219
|
+
* not derived yet (fix #405): `{ name, keep }` the records `keep` accepts,
|
|
220
|
+
* each bearing `name` as its own name or written receiver, or `{ lines }`
|
|
221
|
+
* the records of a line range; the same records, in the same order, the
|
|
222
|
+
* file's derived calls hold, deriving only the parts that can hold them.
|
|
223
|
+
* undefined when the file has no such pending calls (read them whole).
|
|
224
|
+
*/
|
|
225
|
+
function _pendingCallRecords(index, filePath, select) {
|
|
226
|
+
if (!index.files.get(filePath)?.rustMacroExpansion?.callsPending) return undefined;
|
|
227
|
+
const raw = _getCachedCallsRaw(index, filePath);
|
|
228
|
+
if (!Array.isArray(raw)) return undefined;
|
|
229
|
+
return require('./rust-macro-expansion').rustPendingRecords(index, filePath, select);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Calls of a file for a consumer that reads only the records bearing `name`
|
|
234
|
+
* (fix #405), on the lines of `lineSet` when given: for a Rust file whose
|
|
235
|
+
* macro expansions' calls are not derived yet, only those records.
|
|
236
|
+
*/
|
|
237
|
+
function getCachedCallsBearing(index, filePath, name, lineSet = null) {
|
|
238
|
+
const records = _pendingCallRecords(index, filePath, { name, lineSet, keep: call => _callBearsName(call, name) });
|
|
239
|
+
return records !== undefined ? records : getCachedCalls(index, filePath);
|
|
240
|
+
}
|
|
241
|
+
|
|
150
242
|
const _opStatMemos = new WeakMap(); // operation content cache -> Map<filePath, mtimeMs>
|
|
151
243
|
function _opStatMemo(scope) {
|
|
152
244
|
let memo = _opStatMemos.get(scope);
|
|
@@ -370,9 +462,15 @@ function isDataMemberReference(fileEntry, call, definitions) {
|
|
|
370
462
|
// loop's largest fixed cost (fix #365).
|
|
371
463
|
function _callsBearingName(index, filePath, calls, name) {
|
|
372
464
|
const caches = index._opFindCallersCaches;
|
|
373
|
-
if (!caches) return calls
|
|
465
|
+
if (!caches) return _recordsBearing(calls, name);
|
|
374
466
|
let memo = caches.callsByName.get(filePath);
|
|
467
|
+
// A query reads one name per file: the index pays off from a file's
|
|
468
|
+
// second name in the operation (fix #405).
|
|
375
469
|
if (!memo || memo.calls !== calls) {
|
|
470
|
+
caches.callsByName.set(filePath, { calls, byName: null });
|
|
471
|
+
return _recordsBearing(calls, name);
|
|
472
|
+
}
|
|
473
|
+
if (!memo.byName) {
|
|
376
474
|
const byName = new Map();
|
|
377
475
|
const add = (key, call) => {
|
|
378
476
|
let list = byName.get(key);
|
|
@@ -387,8 +485,7 @@ function _callsBearingName(index, filePath, calls, name) {
|
|
|
387
485
|
for (const resolved of call.resolvedNames) if (resolved) add(resolved, call);
|
|
388
486
|
}
|
|
389
487
|
}
|
|
390
|
-
memo =
|
|
391
|
-
caches.callsByName.set(filePath, memo);
|
|
488
|
+
memo.byName = byName;
|
|
392
489
|
}
|
|
393
490
|
return memo.byName.get(name) || [];
|
|
394
491
|
}
|
|
@@ -413,6 +510,16 @@ function _bindingsNamed(index, fileEntry, name) {
|
|
|
413
510
|
return memo.byName.get(name) || [];
|
|
414
511
|
}
|
|
415
512
|
|
|
513
|
+
/** The records bearing `name`, in record order (_callBearsName, unrolled). */
|
|
514
|
+
function _recordsBearing(calls, name) {
|
|
515
|
+
const out = [];
|
|
516
|
+
for (const call of calls) {
|
|
517
|
+
if (call && (call.name === name || call.resolvedName === name ||
|
|
518
|
+
(Array.isArray(call.resolvedNames) && call.resolvedNames.includes(name)))) out.push(call);
|
|
519
|
+
}
|
|
520
|
+
return out;
|
|
521
|
+
}
|
|
522
|
+
|
|
416
523
|
function _callBearsName(call, name) {
|
|
417
524
|
return !!call && (call.name === name || call.resolvedName === name ||
|
|
418
525
|
(Array.isArray(call.resolvedNames) && call.resolvedNames.includes(name)));
|
|
@@ -565,7 +672,9 @@ function findCallers(index, name, options = {}) {
|
|
|
565
672
|
// The name-level subtype closure cannot tell apart local classes
|
|
566
673
|
// with the same spelling. An override in another function must not
|
|
567
674
|
// hide the member this particular local class inherits.
|
|
568
|
-
|
|
675
|
+
const receiver = call?.receiver;
|
|
676
|
+
const receiverType = call?.receiverType;
|
|
677
|
+
for (const name of receiverType === receiver ? [receiver] : [receiver, receiverType]) {
|
|
569
678
|
if (!name || types.has(name) || !file ||
|
|
570
679
|
!_functionLocalTypeInScope(index, file, name, call.line)) continue;
|
|
571
680
|
if (_resolveReceiverTypeIdentity(index, file, name, targetDefs, call.line) === 'target') {
|
|
@@ -1022,7 +1131,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1022
1131
|
// maps per candidate made grpc-go's `repo` cost 40s (1375 calls).
|
|
1023
1132
|
if (!index._opFindCallersCaches) {
|
|
1024
1133
|
index._opFindCallersCaches = {
|
|
1025
|
-
localTypeCache: new Map(), //
|
|
1134
|
+
localTypeCache: new Map(), // filePath -> startLine -> localTypes Map or null
|
|
1026
1135
|
returnFlowCache: new Map(), // filePath -> return-type-flow map (see _buildReturnTypeFlowMap)
|
|
1027
1136
|
foldCtxCache: new Map(), // filePath -> chained-receiver fold context (fix #258)
|
|
1028
1137
|
pythonIndexedReceiverCache: new Map(),
|
|
@@ -1033,6 +1142,9 @@ function findCallers(index, name, options = {}) {
|
|
|
1033
1142
|
}
|
|
1034
1143
|
const { localTypeCache, returnFlowCache, foldCtxCache, pythonIndexedReceiverCache } =
|
|
1035
1144
|
index._opFindCallersCaches;
|
|
1145
|
+
// Caller queries run so far in this operation: the per-record memos below
|
|
1146
|
+
// pay off once the same records are read again (fix #405b).
|
|
1147
|
+
index._opFindCallersCaches.runs = (index._opFindCallersCaches.runs || 0) + 1;
|
|
1036
1148
|
|
|
1037
1149
|
// Use inverted callee index to skip files that don't contain calls to this name
|
|
1038
1150
|
let calleeFiles = index.getCalleeFiles(name);
|
|
@@ -1045,6 +1157,19 @@ function findCallers(index, name, options = {}) {
|
|
|
1045
1157
|
}
|
|
1046
1158
|
if (union.size > 0) calleeFiles = union;
|
|
1047
1159
|
}
|
|
1160
|
+
// Files whose macro expansions' calls are not derived yet and whose
|
|
1161
|
+
// expanded text holds the name (fix #405): a derived record can bear a
|
|
1162
|
+
// name the callee index does not list the file under yet. A null file
|
|
1163
|
+
// list already visits every file.
|
|
1164
|
+
if (calleeFiles) {
|
|
1165
|
+
const pending = require('./rust-macro-expansion').rustPendingFilesBearing(index,
|
|
1166
|
+
hasAliasSurfaces ? [name, ...aliasNames] : [name]);
|
|
1167
|
+
if (pending.some(f => !calleeFiles.has(f))) {
|
|
1168
|
+
const union = new Set(calleeFiles);
|
|
1169
|
+
for (const f of pending) union.add(f);
|
|
1170
|
+
calleeFiles = union;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1048
1173
|
// C# target-typed `new(..)` whose type the call it is an argument of
|
|
1049
1174
|
// (or the member it is assigned to) decides (fix #400): records named
|
|
1050
1175
|
// `new`, typed here.
|
|
@@ -1099,16 +1224,31 @@ function findCallers(index, name, options = {}) {
|
|
|
1099
1224
|
fileEntry.rustMacroExpansion?.callsPending &&
|
|
1100
1225
|
!require('./rust-macro-expansion').rustPendingMayBear(fileEntry, name)) continue;
|
|
1101
1226
|
try {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1227
|
+
const typedViews = csharpTargetTypedByFile.get(filePath);
|
|
1228
|
+
// A record that cannot match below is skipped by the loop's first
|
|
1229
|
+
// test: a file whose macro expansions' calls are not derived yet
|
|
1230
|
+
// derives only what can hold the others (fix #405); its whole
|
|
1231
|
+
// records are derived when a step needs them.
|
|
1232
|
+
const typeQueryBareReaches = targetIsTypeQuery && langTraits(fileEntry.language)?.bareCallReachesMethods;
|
|
1233
|
+
const pendingRecords = hasAliasSurfaces || typedViews ? undefined
|
|
1234
|
+
: _pendingCallRecords(index, filePath, {
|
|
1235
|
+
name,
|
|
1236
|
+
keep: targetIsTypeQuery
|
|
1237
|
+
? call => !!call.destructured || call.receiver === name || _callBearsName(call, name) ||
|
|
1238
|
+
!!(call.receiverIsTypeQualified && typeQueryBareReaches)
|
|
1239
|
+
: call => _callBearsName(call, name),
|
|
1240
|
+
});
|
|
1241
|
+
let calls = pendingRecords === undefined ? getCachedCalls(index, filePath) : null;
|
|
1242
|
+
if (pendingRecords === undefined && !calls) continue;
|
|
1243
|
+
const fileCalls = () => calls || (calls = getCachedCalls(index, filePath) || []);
|
|
1104
1244
|
const structuralLanguage =
|
|
1105
1245
|
langTraits(fileEntry.language)?.typeSystem === 'structural';
|
|
1106
1246
|
// Only records bearing the name can match below unless an alias
|
|
1107
1247
|
// surface or a type-qualifier reference can (fix #365): visit
|
|
1108
1248
|
// those, in record order, instead of every call in the file.
|
|
1109
|
-
let visitCalls =
|
|
1110
|
-
|
|
1111
|
-
|
|
1249
|
+
let visitCalls = pendingRecords !== undefined ? pendingRecords
|
|
1250
|
+
: hasAliasSurfaces || targetIsTypeQuery
|
|
1251
|
+
? calls : _callsBearingName(index, filePath, calls, name);
|
|
1112
1252
|
if (typedViews) {
|
|
1113
1253
|
// A file reached only through such records visits only them.
|
|
1114
1254
|
visitCalls = !nameBearingFiles || nameBearingFiles.has(filePath) || hasAliasSurfaces
|
|
@@ -1219,7 +1359,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1219
1359
|
|
|
1220
1360
|
if (call.receiverDerefAmbiguous && routeDerefAmbiguous(filePath, fileEntry, call, calledAs)) continue;
|
|
1221
1361
|
const standardWrapper = fileEntry.language === 'rust'
|
|
1222
|
-
? _rustStandardWrapperMethod(index, filePath, call,
|
|
1362
|
+
? _rustStandardWrapperMethod(index, filePath, call, fileCalls) : null;
|
|
1223
1363
|
if (standardWrapper) {
|
|
1224
1364
|
const facts = { language: 'rust', site: occurrenceIdentity(fileEntry.relativePath, call),
|
|
1225
1365
|
targets: (options.targetDefinitions || definitions).map(declarationIdentity), standardWrapper };
|
|
@@ -1715,7 +1855,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1715
1855
|
queryProfile.returnFlowBuilds =
|
|
1716
1856
|
(queryProfile.returnFlowBuilds || 0) + 1;
|
|
1717
1857
|
}
|
|
1718
|
-
flowMap = _buildReturnTypeFlowMap(index, filePath,
|
|
1858
|
+
flowMap = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
1719
1859
|
returnFlowCache.set(filePath, flowMap);
|
|
1720
1860
|
}
|
|
1721
1861
|
const flowEntry = flowMap && _lookupReturnTypeFlow(flowMap, call);
|
|
@@ -1783,7 +1923,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1783
1923
|
index, filePath, call, () => {
|
|
1784
1924
|
let flowMap = returnFlowCache.get(filePath);
|
|
1785
1925
|
if (flowMap === undefined) {
|
|
1786
|
-
flowMap = _buildReturnTypeFlowMap(index, filePath,
|
|
1926
|
+
flowMap = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
1787
1927
|
returnFlowCache.set(filePath, flowMap);
|
|
1788
1928
|
}
|
|
1789
1929
|
return flowMap;
|
|
@@ -1817,7 +1957,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1817
1957
|
if (!rootType && call.receiverIterationRoot) {
|
|
1818
1958
|
let flowMap = returnFlowCache.get(filePath);
|
|
1819
1959
|
if (flowMap === undefined) {
|
|
1820
|
-
flowMap = _buildReturnTypeFlowMap(index, filePath,
|
|
1960
|
+
flowMap = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
1821
1961
|
returnFlowCache.set(filePath, flowMap);
|
|
1822
1962
|
}
|
|
1823
1963
|
const inferred = flowMap && _lookupReturnTypeFlow(flowMap, {
|
|
@@ -2047,11 +2187,11 @@ function findCallers(index, name, options = {}) {
|
|
|
2047
2187
|
// from the producer link), one-hop agreement as fallback.
|
|
2048
2188
|
let foldCtx = foldCtxCache.get(filePath);
|
|
2049
2189
|
if (!foldCtx) {
|
|
2050
|
-
foldCtx = { memo: new Map(), visiting: new Set(), records:
|
|
2190
|
+
foldCtx = { memo: new Map(), visiting: new Set(), records: fileCalls(),
|
|
2051
2191
|
getFlowMap: () => {
|
|
2052
2192
|
let fm = returnFlowCache.get(filePath);
|
|
2053
2193
|
if (fm === undefined) {
|
|
2054
|
-
fm = _buildReturnTypeFlowMap(index, filePath,
|
|
2194
|
+
fm = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
2055
2195
|
returnFlowCache.set(filePath, fm);
|
|
2056
2196
|
}
|
|
2057
2197
|
return fm;
|
|
@@ -2098,11 +2238,11 @@ function findCallers(index, name, options = {}) {
|
|
|
2098
2238
|
// agreement rule dies on multi-owner `Self` returns.
|
|
2099
2239
|
let foldCtx = foldCtxCache.get(filePath);
|
|
2100
2240
|
if (!foldCtx) {
|
|
2101
|
-
foldCtx = { memo: new Map(), visiting: new Set(), records:
|
|
2241
|
+
foldCtx = { memo: new Map(), visiting: new Set(), records: fileCalls(),
|
|
2102
2242
|
getFlowMap: () => {
|
|
2103
2243
|
let fm = returnFlowCache.get(filePath);
|
|
2104
2244
|
if (fm === undefined) {
|
|
2105
|
-
fm = _buildReturnTypeFlowMap(index, filePath,
|
|
2245
|
+
fm = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
2106
2246
|
returnFlowCache.set(filePath, fm);
|
|
2107
2247
|
}
|
|
2108
2248
|
return fm;
|
|
@@ -2142,11 +2282,11 @@ function findCallers(index, name, options = {}) {
|
|
|
2142
2282
|
(call.receiverIterationCall || call.receiverIterationVariable)) {
|
|
2143
2283
|
let foldCtx = foldCtxCache.get(filePath);
|
|
2144
2284
|
if (!foldCtx) {
|
|
2145
|
-
foldCtx = { memo: new Map(), visiting: new Set(), records:
|
|
2285
|
+
foldCtx = { memo: new Map(), visiting: new Set(), records: fileCalls(),
|
|
2146
2286
|
getFlowMap: () => {
|
|
2147
2287
|
let fm = returnFlowCache.get(filePath);
|
|
2148
2288
|
if (fm === undefined) {
|
|
2149
|
-
fm = _buildReturnTypeFlowMap(index, filePath,
|
|
2289
|
+
fm = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
2150
2290
|
returnFlowCache.set(filePath, fm);
|
|
2151
2291
|
}
|
|
2152
2292
|
return fm;
|
|
@@ -2177,11 +2317,11 @@ function findCallers(index, name, options = {}) {
|
|
|
2177
2317
|
call.enclosingFunction?.closureSourceCall) {
|
|
2178
2318
|
let foldCtx = foldCtxCache.get(filePath);
|
|
2179
2319
|
if (!foldCtx) {
|
|
2180
|
-
foldCtx = { memo: new Map(), visiting: new Set(), records:
|
|
2320
|
+
foldCtx = { memo: new Map(), visiting: new Set(), records: fileCalls(),
|
|
2181
2321
|
getFlowMap: () => {
|
|
2182
2322
|
let fm = returnFlowCache.get(filePath);
|
|
2183
2323
|
if (fm === undefined) {
|
|
2184
|
-
fm = _buildReturnTypeFlowMap(index, filePath,
|
|
2324
|
+
fm = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
2185
2325
|
returnFlowCache.set(filePath, fm);
|
|
2186
2326
|
}
|
|
2187
2327
|
return fm;
|
|
@@ -3540,7 +3680,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3540
3680
|
if (!fieldHopRootType && call.receiverRoot) {
|
|
3541
3681
|
let flowMap = returnFlowCache.get(filePath);
|
|
3542
3682
|
if (flowMap === undefined) {
|
|
3543
|
-
flowMap = _buildReturnTypeFlowMap(index, filePath,
|
|
3683
|
+
flowMap = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
3544
3684
|
returnFlowCache.set(filePath, flowMap);
|
|
3545
3685
|
}
|
|
3546
3686
|
const inferredRoot = flowMap && _lookupReturnTypeFlow(flowMap, {
|
|
@@ -5421,14 +5561,15 @@ function findCallers(index, name, options = {}) {
|
|
|
5421
5561
|
if (langTraits(fileEntry.language)?.typeSystem === 'nominal') {
|
|
5422
5562
|
const callerSym = index.findEnclosingFunction(filePath, call.line, true, call);
|
|
5423
5563
|
if (callerSym && callerSym.startLine != null && callerSym.endLine != null) {
|
|
5424
|
-
|
|
5425
|
-
|
|
5564
|
+
let localTypesByLine = localTypeCache.get(filePath);
|
|
5565
|
+
if (!localTypesByLine) localTypeCache.set(filePath, localTypesByLine = new Map());
|
|
5566
|
+
let localTypes = localTypesByLine.get(callerSym.startLine);
|
|
5426
5567
|
if (localTypes === undefined) {
|
|
5427
5568
|
const callsForFile = getCachedCalls(index, filePath);
|
|
5428
5569
|
localTypes = callsForFile ? _buildTypedLocalTypeMap(index,
|
|
5429
5570
|
{ file: filePath, startLine: callerSym.startLine, endLine: callerSym.endLine },
|
|
5430
5571
|
callsForFile) : null;
|
|
5431
|
-
|
|
5572
|
+
localTypesByLine.set(callerSym.startLine, localTypes);
|
|
5432
5573
|
}
|
|
5433
5574
|
if (localTypes) {
|
|
5434
5575
|
// The inference map re-derives receiver types
|
|
@@ -6931,7 +7072,7 @@ function findCallers(index, name, options = {}) {
|
|
|
6931
7072
|
}
|
|
6932
7073
|
let demoteFlowMap = returnFlowCache.get(filePath);
|
|
6933
7074
|
if (demoteFlowMap === undefined) {
|
|
6934
|
-
demoteFlowMap = _buildReturnTypeFlowMap(index, filePath,
|
|
7075
|
+
demoteFlowMap = _buildReturnTypeFlowMap(index, filePath, fileCalls());
|
|
6935
7076
|
returnFlowCache.set(filePath, demoteFlowMap);
|
|
6936
7077
|
}
|
|
6937
7078
|
if (_receiverAssignedUntyped(demoteFlowMap, call)) {
|
|
@@ -7639,8 +7780,13 @@ function findCallees(index, definition, options = {}) {
|
|
|
7639
7780
|
|
|
7640
7781
|
try {
|
|
7641
7782
|
// Get all calls from the file's cache (now includes enclosingFunction)
|
|
7642
|
-
|
|
7643
|
-
|
|
7783
|
+
// A file whose macro expansions' calls are not derived yet derives
|
|
7784
|
+
// only the parts holding this definition's lines (fix #405); its
|
|
7785
|
+
// whole records are derived when a step needs them.
|
|
7786
|
+
const rangeRecords = _pendingCallRecords(index, def.file, { lines: [def.startLine, def.endLine] });
|
|
7787
|
+
let allCalls = rangeRecords === undefined ? getCachedCalls(index, def.file) : null;
|
|
7788
|
+
if (rangeRecords === undefined && !allCalls) return [];
|
|
7789
|
+
const fileCalls = () => allCalls || (allCalls = getCachedCalls(index, def.file) || []);
|
|
7644
7790
|
|
|
7645
7791
|
// A file can contain hundreds of symbols and thousands of calls.
|
|
7646
7792
|
// Reachability invokes findCallees once per reachable symbol; scanning
|
|
@@ -7650,8 +7796,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
7650
7796
|
// remain in the slice and retain the existing inner-symbol rules.
|
|
7651
7797
|
// Annotation element keys (fix #389) name an element of the
|
|
7652
7798
|
// annotation type; the annotated declaration makes no call.
|
|
7653
|
-
const calls = _callsInDefinitionRange(index, def.file, allCalls,
|
|
7654
|
-
def.startLine, def.endLine).filter(call => !call.annotationElement && !isDataMemberReference(
|
|
7799
|
+
const calls = (rangeRecords !== undefined ? rangeRecords : _callsInDefinitionRange(index, def.file, allCalls,
|
|
7800
|
+
def.startLine, def.endLine)).filter(call => !call.annotationElement && !isDataMemberReference(
|
|
7655
7801
|
index.files.get(def.file), call, index.symbols.get(call.name) || []));
|
|
7656
7802
|
// The reachability walk uses the legacy (non-accounting) path and most
|
|
7657
7803
|
// entry/test symbols contain no calls. Avoid constructing receiver,
|
|
@@ -7839,7 +7985,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
7839
7985
|
queryProfile.returnFlowBuilds =
|
|
7840
7986
|
(queryProfile.returnFlowBuilds || 0) + 1;
|
|
7841
7987
|
}
|
|
7842
|
-
_flowMap = _buildReturnTypeFlowMap(index, def.file,
|
|
7988
|
+
_flowMap = _buildReturnTypeFlowMap(index, def.file, fileCalls());
|
|
7843
7989
|
}
|
|
7844
7990
|
return _flowMap;
|
|
7845
7991
|
};
|
|
@@ -7864,7 +8010,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
7864
8010
|
// the current callee definition. Keep the narrow records
|
|
7865
8011
|
// set for ordinary producer indexing, but expose the
|
|
7866
8012
|
// already-loaded file records for exact returned spans.
|
|
7867
|
-
allRecords
|
|
8013
|
+
get allRecords() { return fileCalls(); },
|
|
7868
8014
|
getFlowMap: () => flowMap() };
|
|
7869
8015
|
}
|
|
7870
8016
|
return calleeFoldCtx;
|
|
@@ -8054,7 +8200,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
8054
8200
|
}
|
|
8055
8201
|
}
|
|
8056
8202
|
|
|
8057
|
-
if (language === 'rust' && _rustStandardWrapperMethod(index, def.file, call,
|
|
8203
|
+
if (language === 'rust' && _rustStandardWrapperMethod(index, def.file, call, fileCalls)) {
|
|
8058
8204
|
noteSite(siteId, 'external', 'standard-wrapper-method', call);
|
|
8059
8205
|
continue;
|
|
8060
8206
|
}
|
|
@@ -12626,7 +12772,9 @@ function _rustStandardWrapperMethod(index, file, call, calls) {
|
|
|
12626
12772
|
if (!call.isMethod || !call.receiver || call.isPathCall ||
|
|
12627
12773
|
call.receiverPatternShadow || call.receiverFlowInvalidated ||
|
|
12628
12774
|
!['unwrap', 'expect'].includes(call.name)) return null;
|
|
12629
|
-
|
|
12775
|
+
// `calls` may be a function reading the file's records when needed.
|
|
12776
|
+
const records = typeof calls === 'function' ? calls() : calls;
|
|
12777
|
+
const flow = _lookupReturnTypeFlow(_buildReturnTypeFlowMap(index, file, records), call);
|
|
12630
12778
|
const contract = flow?.rustWrapper;
|
|
12631
12779
|
if (contract && validateRustWrapperContract(contract, true)) {
|
|
12632
12780
|
return { method: call.name, receiver: call.receiver, callKind: 'method',
|
|
@@ -15318,12 +15466,7 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
15318
15466
|
* never exclude. Returns the trait name or null.
|
|
15319
15467
|
*/
|
|
15320
15468
|
function _traitPathImplMemberRoute(index, filePath, receiverSegment, targetDefs) {
|
|
15321
|
-
const
|
|
15322
|
-
definition.type === 'trait' && definition.file);
|
|
15323
|
-
if (traitDefs.length === 0) return null;
|
|
15324
|
-
const origin = _resolveFlowTypeOrigin(index, filePath, receiverSegment);
|
|
15325
|
-
const trait = origin?.fromFile
|
|
15326
|
-
? traitDefs.find(definition => definition.file === origin.fromFile) : null;
|
|
15469
|
+
const trait = _pathTraitDefinition(index, filePath, receiverSegment);
|
|
15327
15470
|
if (!trait) return null;
|
|
15328
15471
|
for (const target of targetDefs) {
|
|
15329
15472
|
const head = String(target.traitName || '').replace(/<.*$/s, '').trim()
|
|
@@ -15335,6 +15478,24 @@ function _traitPathImplMemberRoute(index, filePath, receiverSegment, targetDefs)
|
|
|
15335
15478
|
return null;
|
|
15336
15479
|
}
|
|
15337
15480
|
|
|
15481
|
+
/** The project trait a path segment names from a file, or null; per operation (fix #405b). */
|
|
15482
|
+
function _pathTraitDefinition(index, filePath, receiverSegment) {
|
|
15483
|
+
const memo = index._opMemo?.('pathTraitDefinition', () => new Map());
|
|
15484
|
+
let byName = memo?.get(filePath);
|
|
15485
|
+
if (memo && !byName) memo.set(filePath, byName = new Map());
|
|
15486
|
+
const known = byName?.get(receiverSegment);
|
|
15487
|
+
if (known !== undefined) return known;
|
|
15488
|
+
let trait = null;
|
|
15489
|
+
const traitDefs = (index.symbols.get(receiverSegment) || []).filter(definition =>
|
|
15490
|
+
definition.type === 'trait' && definition.file);
|
|
15491
|
+
if (traitDefs.length > 0) {
|
|
15492
|
+
const origin = _resolveFlowTypeOrigin(index, filePath, receiverSegment);
|
|
15493
|
+
trait = (origin?.fromFile ? traitDefs.find(definition => definition.file === origin.fromFile) : null) || null;
|
|
15494
|
+
}
|
|
15495
|
+
byName?.set(receiverSegment, trait);
|
|
15496
|
+
return trait;
|
|
15497
|
+
}
|
|
15498
|
+
|
|
15338
15499
|
function _traitDeclPinImplementorRoute(index, fileEntry, receiverSegment, targetDefs) {
|
|
15339
15500
|
if (langTraits(fileEntry.language)?.typeQualifiedCallStyle !== 'path') return null;
|
|
15340
15501
|
let traitName = null;
|
|
@@ -18161,6 +18322,30 @@ function _typedFieldDefinitions(index, defs) {
|
|
|
18161
18322
|
function _declaredFieldType(
|
|
18162
18323
|
index, rootType, fieldName, language, info = null, rootNamespace = undefined, rootArity = null
|
|
18163
18324
|
) {
|
|
18325
|
+
// A pure function of its arguments and the index, plus the facts it
|
|
18326
|
+
// writes into `info`: kept per operation (fix #405b: an exact orientation
|
|
18327
|
+
// ranking asked it again per record of every refinement).
|
|
18328
|
+
const memo = _repeatedQueryMemo(index, 'declaredFieldType');
|
|
18329
|
+
if (!memo) return _declaredFieldTypeUncached(index, rootType, fieldName, language, info, rootNamespace, rootArity);
|
|
18330
|
+
const namespaceKey = rootNamespace === undefined ? '\x01' : rootNamespace === null ? '\x02' : rootNamespace;
|
|
18331
|
+
const key = `${rootType}\0${fieldName}\0${language}\0${namespaceKey}` +
|
|
18332
|
+
`\0${rootArity == null ? '\x01' : rootArity}\0${info ? 1 : 0}`;
|
|
18333
|
+
let known = memo.get(key);
|
|
18334
|
+
if (!known) {
|
|
18335
|
+
const written = info ? {} : null;
|
|
18336
|
+
known = { type: _declaredFieldTypeUncached(index, rootType, fieldName, language, written, rootNamespace, rootArity), written };
|
|
18337
|
+
memo.set(key, known);
|
|
18338
|
+
}
|
|
18339
|
+
if (info && known.written) {
|
|
18340
|
+
for (const field in known.written) {
|
|
18341
|
+
const value = known.written[field];
|
|
18342
|
+
info[field] = Array.isArray(value) ? [...value] : value;
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
return known.type;
|
|
18346
|
+
}
|
|
18347
|
+
|
|
18348
|
+
function _declaredFieldTypeUncached(index, rootType, fieldName, language, info, rootNamespace, rootArity) {
|
|
18164
18349
|
const defs = index.symbols.get(fieldName) || [];
|
|
18165
18350
|
if (defs.length === 0 && language !== 'python') return null;
|
|
18166
18351
|
// 'private field' (JS #-fields, fix #219): equally compiler-true, and
|
|
@@ -18602,10 +18787,20 @@ function _enclosingFunctionOfDefinitionUncached(index, def) {
|
|
|
18602
18787
|
*/
|
|
18603
18788
|
let _rustModulesApi = null;
|
|
18604
18789
|
function _rustModulePathRoute(index, filePath, call, name, targets) {
|
|
18605
|
-
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
const
|
|
18790
|
+
// The module items a record's qualifier names, kept per record while the
|
|
18791
|
+
// index lives (fix #405b: orientation asked per record for every
|
|
18792
|
+
// definition of the name it refined).
|
|
18793
|
+
const memo = index._rustScopeMemo || (index._rustScopeMemo = new Map());
|
|
18794
|
+
let byRecord = memo.get('\x01pathRouteItems');
|
|
18795
|
+
if (!byRecord) memo.set('\x01pathRouteItems', byRecord = new WeakMap());
|
|
18796
|
+
let known = byRecord.get(call);
|
|
18797
|
+
if (!known || known.name !== name) {
|
|
18798
|
+
const { rustPathModule, rustModuleItems } = _rustModulesApi || (_rustModulesApi = require('./rust-modules'));
|
|
18799
|
+
const resolved = rustPathModule(index, filePath, call.line, call.receiver);
|
|
18800
|
+
known = { name, items: resolved ? rustModuleItems(index, resolved, name) : [] };
|
|
18801
|
+
byRecord.set(call, known);
|
|
18802
|
+
}
|
|
18803
|
+
const items = known.items;
|
|
18609
18804
|
if (items.length === 0) return null;
|
|
18610
18805
|
const pinned = items.filter(item => targets.some(target =>
|
|
18611
18806
|
sameDeclaration(declarationIdentity(item), declarationIdentity(target))));
|
|
@@ -22727,10 +22922,9 @@ function _pathQualifierAliasIdentity(index, filePath, fileEntry, segment, target
|
|
|
22727
22922
|
? 'target' : null;
|
|
22728
22923
|
}
|
|
22729
22924
|
// `use path::Type as Local;` renames a type import: Local names Type.
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
const original =
|
|
22733
|
-
if (!original || original === segment) continue;
|
|
22925
|
+
const renamed = _typeImportRenameOf(fileEntry, segment);
|
|
22926
|
+
if (renamed) {
|
|
22927
|
+
const { binding, original } = renamed;
|
|
22734
22928
|
const relative = fileEntry.moduleResolved?.[binding.module];
|
|
22735
22929
|
if (!targetTypes.has(original)) {
|
|
22736
22930
|
// A rename of a DIFFERENT project type is positive evidence.
|
|
@@ -22755,9 +22949,41 @@ function _pathQualifierAliasIdentity(index, filePath, fileEntry, segment, target
|
|
|
22755
22949
|
alias.definition.startLine);
|
|
22756
22950
|
}
|
|
22757
22951
|
|
|
22952
|
+
/** Type aliases named `name`, per operation (fix #405b: asked per path-call record). */
|
|
22953
|
+
function _typeAliasesNamed(index, name) {
|
|
22954
|
+
const definitions = index.symbols.get(name);
|
|
22955
|
+
if (!definitions) return [];
|
|
22956
|
+
const memo = index._opMemo?.('typeAliasesNamed', () => new WeakMap());
|
|
22957
|
+
let aliases = memo?.get(definitions);
|
|
22958
|
+
if (!aliases) {
|
|
22959
|
+
aliases = definitions.filter(definition => definition.type === 'type' && definition.aliasOf);
|
|
22960
|
+
memo?.set(definitions, aliases);
|
|
22961
|
+
}
|
|
22962
|
+
return aliases;
|
|
22963
|
+
}
|
|
22964
|
+
|
|
22965
|
+
// The first import binding renaming a type to `segment` (`use m::T as L;`),
|
|
22966
|
+
// per file entry (fix #405b: asked per path-call record).
|
|
22967
|
+
const _TYPE_IMPORT_RENAMES = new WeakMap();
|
|
22968
|
+
function _typeImportRenameOf(fileEntry, segment) {
|
|
22969
|
+
let byName = _TYPE_IMPORT_RENAMES.get(fileEntry);
|
|
22970
|
+
if (!byName) _TYPE_IMPORT_RENAMES.set(fileEntry, byName = new Map());
|
|
22971
|
+
let renamed = byName.get(segment);
|
|
22972
|
+
if (renamed !== undefined) return renamed;
|
|
22973
|
+
renamed = null;
|
|
22974
|
+
for (const binding of fileEntry.importBindings || []) {
|
|
22975
|
+
if (!binding.alias || binding.alias !== segment) continue;
|
|
22976
|
+
const original = String(binding.module || '').split(/::|\./).pop();
|
|
22977
|
+
if (!original || original === segment) continue;
|
|
22978
|
+
renamed = { binding, original };
|
|
22979
|
+
break;
|
|
22980
|
+
}
|
|
22981
|
+
byName.set(segment, renamed);
|
|
22982
|
+
return renamed;
|
|
22983
|
+
}
|
|
22984
|
+
|
|
22758
22985
|
function _pathAliasInScope(index, filePath, fileEntry, name, qualifier) {
|
|
22759
|
-
const aliases = (index
|
|
22760
|
-
definition.type === 'type' && definition.aliasOf);
|
|
22986
|
+
const aliases = _typeAliasesNamed(index, name);
|
|
22761
22987
|
if (aliases.length === 0) return null;
|
|
22762
22988
|
// `module::Alias` names the alias declared in that module's file.
|
|
22763
22989
|
const segments = String(qualifier || '').split('::').filter(Boolean);
|
|
@@ -26975,6 +27201,25 @@ function _orderedTypeParamsUncached(genericsText) {
|
|
|
26975
27201
|
* such a parameter or its declaration is not visible.
|
|
26976
27202
|
*/
|
|
26977
27203
|
function _rustGenericBoundReceiver(index, filePath, call) {
|
|
27204
|
+
// Only `self`, a receiver annotated with its type, or a `self.field` can
|
|
27205
|
+
// be one (the shapes _rustGenericBoundReceiverUncached reads).
|
|
27206
|
+
if (!(call.receiver === 'self' && !call.receiverField && !call.receiverType) &&
|
|
27207
|
+
!(call.receiverType && call.receiver && call.receiver !== 'self' && !call.receiverField &&
|
|
27208
|
+
call.receiverTypeSource === 'annotation') &&
|
|
27209
|
+
!(!call.receiverType && call.receiverRoot === 'self' && call.receiverField)) return null;
|
|
27210
|
+
// Per record while the index lives (fix #405b).
|
|
27211
|
+
const memo = index._rustScopeMemo || (index._rustScopeMemo = new Map());
|
|
27212
|
+
let byRecord = memo.get('\x01boundReceiver');
|
|
27213
|
+
if (!byRecord) memo.set('\x01boundReceiver', byRecord = new WeakMap());
|
|
27214
|
+
let bound = byRecord.get(call);
|
|
27215
|
+
if (bound === undefined) {
|
|
27216
|
+
bound = _rustGenericBoundReceiverUncached(index, filePath, call);
|
|
27217
|
+
byRecord.set(call, bound);
|
|
27218
|
+
}
|
|
27219
|
+
return bound;
|
|
27220
|
+
}
|
|
27221
|
+
|
|
27222
|
+
function _rustGenericBoundReceiverUncached(index, filePath, call) {
|
|
26978
27223
|
// `self` inside a trait's own method body (fix #369) is the generic
|
|
26979
27224
|
// `Self: Trait`, bounded by the trait and its declared supertraits.
|
|
26980
27225
|
if (call.receiver === 'self' && !call.receiverField && !call.receiverType) {
|
|
@@ -27039,6 +27284,22 @@ function _rustGenericBoundReceiver(index, filePath, call) {
|
|
|
27039
27284
|
* crate cannot name this project's traits, so they add nothing.
|
|
27040
27285
|
*/
|
|
27041
27286
|
function _rustTraitClosure(index, fromFile, names) {
|
|
27287
|
+
// Per file and bound list while the index lives (fix #405b).
|
|
27288
|
+
const memo = index._rustScopeMemo || (index._rustScopeMemo = new Map());
|
|
27289
|
+
let byFile = memo.get('\x01traitClosure');
|
|
27290
|
+
if (!byFile) memo.set('\x01traitClosure', byFile = new Map());
|
|
27291
|
+
let byNames = byFile.get(fromFile);
|
|
27292
|
+
if (!byNames) byFile.set(fromFile, byNames = new Map());
|
|
27293
|
+
const namesKey = names.join('\0');
|
|
27294
|
+
let closure = byNames.get(namesKey);
|
|
27295
|
+
if (!closure) {
|
|
27296
|
+
closure = _rustTraitClosureUncached(index, fromFile, names);
|
|
27297
|
+
byNames.set(namesKey, closure);
|
|
27298
|
+
}
|
|
27299
|
+
return closure;
|
|
27300
|
+
}
|
|
27301
|
+
|
|
27302
|
+
function _rustTraitClosureUncached(index, fromFile, names) {
|
|
27042
27303
|
const keys = new Map();
|
|
27043
27304
|
let complete = true;
|
|
27044
27305
|
const pending = names.map(name => ({ name, fromFile }));
|
|
@@ -27079,13 +27340,21 @@ function _rustTraitClosure(index, fromFile, names) {
|
|
|
27079
27340
|
* traits) stays with the existing receiver physics.
|
|
27080
27341
|
*/
|
|
27081
27342
|
function _rustGenericBoundDispatchVerdict(index, filePath, bound, targetDefs) {
|
|
27082
|
-
|
|
27083
|
-
|
|
27084
|
-
|
|
27085
|
-
|
|
27086
|
-
|
|
27087
|
-
|
|
27088
|
-
|
|
27343
|
+
// Per target list, file and bound list (fix #405b: no composed key of
|
|
27344
|
+
// absolute paths per record).
|
|
27345
|
+
const memo = index._rustScopeMemo || (index._rustScopeMemo = new Map());
|
|
27346
|
+
let byTargets = memo.get('\x01boundVerdict');
|
|
27347
|
+
if (!byTargets) memo.set('\x01boundVerdict', byTargets = new WeakMap());
|
|
27348
|
+
let byFile = byTargets.get(targetDefs);
|
|
27349
|
+
if (!byFile) byTargets.set(targetDefs, byFile = new Map());
|
|
27350
|
+
let byBounds = byFile.get(filePath);
|
|
27351
|
+
if (!byBounds) byFile.set(filePath, byBounds = new Map());
|
|
27352
|
+
const boundsKey = bound.bounds.join(',');
|
|
27353
|
+
let verdict = byBounds.get(boundsKey);
|
|
27354
|
+
if (verdict === undefined) {
|
|
27355
|
+
verdict = _rustGenericBoundDispatchVerdictUncached(index, filePath, bound, targetDefs);
|
|
27356
|
+
byBounds.set(boundsKey, verdict);
|
|
27357
|
+
}
|
|
27089
27358
|
return verdict;
|
|
27090
27359
|
}
|
|
27091
27360
|
|
|
@@ -27095,29 +27364,25 @@ function _rustGenericBoundDispatchVerdictUncached(index, filePath, bound, target
|
|
|
27095
27364
|
(definition.className || definition.receiver));
|
|
27096
27365
|
if (targets.length === 0 || targets.length !== targetDefs.filter(definition =>
|
|
27097
27366
|
!NON_CALLABLE_TYPES.has(definition.type)).length) return null;
|
|
27098
|
-
|
|
27367
|
+
const facts = targets.map(target => _rustBoundTargetFacts(index, target));
|
|
27368
|
+
if (facts.some(fact => fact.genericOwner)) return null;
|
|
27099
27369
|
// Trait declaration members keep the existing trait-slot physics.
|
|
27100
|
-
if (
|
|
27101
|
-
.some(type => type.type === 'trait' && type.file === definition.file))) return null;
|
|
27370
|
+
if (facts.some(fact => fact.traitMember)) return null;
|
|
27102
27371
|
const closure = _rustTraitClosure(index, filePath, bound.bounds);
|
|
27103
27372
|
let via = null;
|
|
27104
27373
|
let upstream = false;
|
|
27105
27374
|
let allOutside = true;
|
|
27106
|
-
for (const
|
|
27107
|
-
if (
|
|
27108
|
-
const head =
|
|
27109
|
-
|
|
27110
|
-
const traitDefs = (index.symbols.get(head) || []).filter(definition =>
|
|
27111
|
-
definition.type === 'trait' && definition.file);
|
|
27112
|
-
if (traitDefs.length === 0) {
|
|
27375
|
+
for (const fact of facts) {
|
|
27376
|
+
if (fact.inherent) continue; // inherent member: unreachable through a bound
|
|
27377
|
+
const head = fact.head;
|
|
27378
|
+
if (fact.upstream) {
|
|
27113
27379
|
// Upstream trait (fix #369): its supertrait graph is invisible,
|
|
27114
27380
|
// except that a bound naming the SAME upstream trait fills the
|
|
27115
27381
|
// target's slot (`I: Iterator` reaching `impl Iterator for Tee`):
|
|
27116
27382
|
// one external-contract family. Any other upstream relation
|
|
27117
27383
|
// stays unknown.
|
|
27118
|
-
if (bound.bounds.some(name => String(name).replace(/<.*$/s, '').trim()
|
|
27119
|
-
.split('::').filter(Boolean).pop() === head
|
|
27120
|
-
!(index.symbols.get(head) || []).some(definition => definition.type === 'trait'))) {
|
|
27384
|
+
if (fact.noProjectTrait && bound.bounds.some(name => String(name).replace(/<.*$/s, '').trim()
|
|
27385
|
+
.split('::').filter(Boolean).pop() === head)) {
|
|
27121
27386
|
via = via || head;
|
|
27122
27387
|
upstream = true;
|
|
27123
27388
|
allOutside = false;
|
|
@@ -27125,11 +27390,8 @@ function _rustGenericBoundDispatchVerdictUncached(index, filePath, bound, target
|
|
|
27125
27390
|
}
|
|
27126
27391
|
return null;
|
|
27127
27392
|
}
|
|
27128
|
-
|
|
27129
|
-
|
|
27130
|
-
? traitDefs.filter(definition => definition.file === origin.fromFile) : [];
|
|
27131
|
-
if (pinned.length !== 1) return null;
|
|
27132
|
-
if (closure.keys.has(`${pinned[0].file}\0${head}`)) {
|
|
27393
|
+
if (!fact.pinnedKey) return null;
|
|
27394
|
+
if (closure.keys.has(fact.pinnedKey)) {
|
|
27133
27395
|
via = via || head;
|
|
27134
27396
|
allOutside = false;
|
|
27135
27397
|
}
|
|
@@ -27139,6 +27401,45 @@ function _rustGenericBoundDispatchVerdictUncached(index, filePath, bound, target
|
|
|
27139
27401
|
return null;
|
|
27140
27402
|
}
|
|
27141
27403
|
|
|
27404
|
+
/**
|
|
27405
|
+
* What the bound verdict reads of one pinned definition, independent of the
|
|
27406
|
+
* site (fix #405b: asked again for every bounded receiver of every
|
|
27407
|
+
* refinement): its owner is generic, it is a trait declaration's member,
|
|
27408
|
+
* an inherent member, a member of an upstream trait, or the key of the one
|
|
27409
|
+
* project trait its impl names.
|
|
27410
|
+
*/
|
|
27411
|
+
function _rustBoundTargetFacts(index, target) {
|
|
27412
|
+
const memo = index._rustScopeMemo || (index._rustScopeMemo = new Map());
|
|
27413
|
+
let byTarget = memo.get('\x01boundTarget');
|
|
27414
|
+
if (!byTarget) memo.set('\x01boundTarget', byTarget = new WeakMap());
|
|
27415
|
+
let fact = byTarget.get(target);
|
|
27416
|
+
if (fact) return fact;
|
|
27417
|
+
fact = {
|
|
27418
|
+
genericOwner: _definitionHasGenericOwner(target),
|
|
27419
|
+
traitMember: (index.symbols.get(target.className) || [])
|
|
27420
|
+
.some(type => type.type === 'trait' && type.file === target.file),
|
|
27421
|
+
};
|
|
27422
|
+
if (!target.traitName) {
|
|
27423
|
+
fact.inherent = true;
|
|
27424
|
+
} else {
|
|
27425
|
+
const head = String(target.traitName).replace(/<.*$/s, '').trim().split('::').filter(Boolean).pop();
|
|
27426
|
+
fact.head = head;
|
|
27427
|
+
const traitDefs = (index.symbols.get(head) || []).filter(definition =>
|
|
27428
|
+
definition.type === 'trait' && definition.file);
|
|
27429
|
+
if (traitDefs.length === 0) {
|
|
27430
|
+
fact.upstream = true;
|
|
27431
|
+
fact.noProjectTrait = !(index.symbols.get(head) || []).some(definition => definition.type === 'trait');
|
|
27432
|
+
} else {
|
|
27433
|
+
const origin = _resolveFlowTypeOrigin(index, target.file, head);
|
|
27434
|
+
const pinned = origin?.fromFile
|
|
27435
|
+
? traitDefs.filter(definition => definition.file === origin.fromFile) : [];
|
|
27436
|
+
fact.pinnedKey = pinned.length === 1 ? `${pinned[0].file}\0${head}` : null;
|
|
27437
|
+
}
|
|
27438
|
+
}
|
|
27439
|
+
byTarget.set(target, fact);
|
|
27440
|
+
return fact;
|
|
27441
|
+
}
|
|
27442
|
+
|
|
27142
27443
|
/**
|
|
27143
27444
|
* Rust method lookup through auto-deref (fix #369): the receiver type itself
|
|
27144
27445
|
* when it owns a method named `name` (or when lookup cannot proceed), else
|
|
@@ -28205,6 +28506,6 @@ function findCallbackUsages(index, name) {
|
|
|
28205
28506
|
return usages;
|
|
28206
28507
|
}
|
|
28207
28508
|
|
|
28208
|
-
module.exports = { _cppMemberUsingBases, rustTypeNameDenotes, _textualIncludeClosures, _textualLinkVerdict, isProvenanceBuiltinReceiver, provenanceParameterIdentity: _overloadTypeIdentity, selectProvenanceOverload, _unresolvedModuleIsGap, _importReaches, _sameNominalPackageDir, getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable, _buildReturnTypeFlowMap, _lookupReturnTypeFlow, _cFamilySignatureKey,
|
|
28509
|
+
module.exports = { _cppMemberUsingBases, rustTypeNameDenotes, _textualIncludeClosures, _textualLinkVerdict, isProvenanceBuiltinReceiver, provenanceParameterIdentity: _overloadTypeIdentity, selectProvenanceOverload, _unresolvedModuleIsGap, _importReaches, _sameNominalPackageDir, getCachedCalls, getCachedCallsBearing, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable, _buildReturnTypeFlowMap, _lookupReturnTypeFlow, _cFamilySignatureKey,
|
|
28209
28510
|
_isConfigurationAlternative, _isCrossFileConfigurationMember, _configurationAlternativeFiles, _sameOwnerPath,
|
|
28210
28511
|
_goFileConstraint };
|