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/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.filter(call => _callBearsName(call, name));
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 = { calls, byName };
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
- for (const name of new Set([call?.receiver, call?.receiverType])) {
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(), // `${filePath}:${startLine}` -> localTypes Map or null
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 calls = getCachedCalls(index, filePath);
1103
- if (!calls) continue;
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 = hasAliasSurfaces || targetIsTypeQuery
1110
- ? calls : _callsBearingName(index, filePath, calls, name);
1111
- const typedViews = csharpTargetTypedByFile.get(filePath);
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, calls) : null;
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, calls);
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, calls);
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, calls);
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: calls,
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, calls);
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: calls,
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, calls);
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: calls,
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, calls);
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: calls,
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, calls);
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, calls);
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
- const cacheKey = `${filePath}:${callerSym.startLine}`;
5425
- let localTypes = localTypeCache.get(cacheKey);
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
- localTypeCache.set(cacheKey, localTypes);
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, calls);
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
- const allCalls = getCachedCalls(index, def.file);
7643
- if (!allCalls) return [];
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, allCalls);
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: allCalls,
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, allCalls)) {
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
- const flow = _lookupReturnTypeFlow(_buildReturnTypeFlowMap(index, file, calls), call);
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 traitDefs = (index.symbols.get(receiverSegment) || []).filter(definition =>
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
- const { rustPathModule, rustModuleItems } = _rustModulesApi || (_rustModulesApi = require('./rust-modules'));
18606
- const resolved = rustPathModule(index, filePath, call.line, call.receiver);
18607
- if (!resolved) return null;
18608
- const items = rustModuleItems(index, resolved, name);
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
- for (const binding of fileEntry.importBindings || []) {
22731
- if (!binding.alias || binding.alias !== segment) continue;
22732
- const original = String(binding.module || '').split(/::|\./).pop();
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.symbols.get(name) || []).filter(definition =>
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
- const opCache = index._rustScopeMemo || (index._rustScopeMemo = new Map());
27083
- const cacheKey = opCache ? `\x01bound\x00${filePath}\x00${bound.bounds.join(',')}\x00` +
27084
- targetDefs.map(definition => `${definition.file}:${definition.startLine}`).join('|') : null;
27085
- if (cacheKey && opCache.has(cacheKey)) return opCache.get(cacheKey);
27086
- const verdict = _rustGenericBoundDispatchVerdictUncached(index, filePath, bound, targetDefs);
27087
- if (opCache.size > 50000) opCache.clear();
27088
- opCache.set(cacheKey, verdict);
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
- if (targets.some(definition => _definitionHasGenericOwner(definition))) return null;
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 (targets.some(definition => (index.symbols.get(definition.className) || [])
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 target of targets) {
27107
- if (!target.traitName) continue; // inherent member: unreachable through a bound
27108
- const head = String(target.traitName).replace(/<.*$/s, '').trim()
27109
- .split('::').filter(Boolean).pop();
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
- const origin = _resolveFlowTypeOrigin(index, target.file, head);
27129
- const pinned = origin?.fromFile
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 };