ucn 5.3.4 → 5.3.6
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 +22 -0
- package/core/account.js +5 -1
- package/core/analysis.js +18 -0
- package/core/cache.js +5 -1
- package/core/callers.js +989 -124
- package/core/confidence.js +26 -14
- package/core/imports.js +18 -4
- package/core/index-ir.js +8 -3
- package/core/ir.js +1 -1
- package/core/output/analysis.js +20 -8
- package/core/output/provenance.js +41 -0
- package/core/output/public.js +2 -1
- package/core/output/shared.js +3 -0
- package/core/provenance-facts.js +287 -0
- package/core/provenance-overload.js +118 -0
- package/core/provenance.js +423 -0
- package/core/python-fixture-flow.js +216 -0
- package/core/receiver-types.js +26 -0
- package/core/rust-result-flow.js +206 -0
- package/core/verify.js +7 -3
- package/languages/adapter.js +2 -0
- package/languages/c-family.js +14 -1
- package/languages/csharp.js +21 -9
- package/languages/go.js +105 -86
- package/languages/java.js +23 -10
- package/languages/javascript.js +129 -18
- package/languages/python.js +202 -35
- package/languages/rust.js +185 -77
- package/languages/type-evidence.js +63 -0
- package/package.json +2 -2
package/core/callers.js
CHANGED
|
@@ -12,8 +12,13 @@ const { detectLanguage, getParser, getLanguageAdapter, langTraits } = require('.
|
|
|
12
12
|
const { isTestFile } = require('./discovery');
|
|
13
13
|
const { NON_CALLABLE_TYPES, isOverrideMarked, codeUnitCompare, isTestPath, CALLABLE_SYMBOL_KINDS } = require('./shared');
|
|
14
14
|
const { _resolveJavaPackageImport } = require('./graph-build');
|
|
15
|
-
const { scoreEdge, tierForResolution, TIER } = require('./confidence');
|
|
15
|
+
const { scoreEdge, tierForResolution, TIER, validateConfirmation } = require('./confidence');
|
|
16
|
+
const { summarizeProvenance, declarationIdentity, sameDeclaration } = require('./provenance');
|
|
17
|
+
const { confirmationFacts, occurrenceIdentity } = require('./provenance-facts');
|
|
18
|
+
const { BUILTIN_RECEIVER_TYPES, isProvenanceBuiltinReceiver } = require('./receiver-types');
|
|
16
19
|
const { findGoModule, resolveRustImport } = require('./imports');
|
|
20
|
+
const { rustWrapperContract, validateRustWrapperContract } = require('./rust-result-flow');
|
|
21
|
+
const { pythonFixtureReceiver, pythonFixtureType } = require('./python-fixture-flow');
|
|
17
22
|
|
|
18
23
|
const CONSTRUCTABLE_BINDING_KINDS = new Set([
|
|
19
24
|
'class', 'struct', 'record', 'enum', 'function',
|
|
@@ -23,6 +28,45 @@ const RESERVED_RECEIVER_NAMES = new Set([
|
|
|
23
28
|
]);
|
|
24
29
|
const CROSS_OPERATION_FLOW_CACHE_LIMIT = 4096;
|
|
25
30
|
|
|
31
|
+
function _confirmationFacts(index, file, call, targets, options = {}) {
|
|
32
|
+
return confirmationFacts(index, file, call, targets, {
|
|
33
|
+
...options,
|
|
34
|
+
selectOverload: selectProvenanceOverload,
|
|
35
|
+
typeHead: name => _structuralTypeHead(name, { index, language: index.files.get(file)?.language }),
|
|
36
|
+
resolveType(name, context, line, qualified) {
|
|
37
|
+
const language = index.files.get(context)?.language;
|
|
38
|
+
const parts = name.split(name.includes('::') ? '::' : '.');
|
|
39
|
+
if (parts.length > 1) {
|
|
40
|
+
const simple = parts.pop();
|
|
41
|
+
const qualifier = parts.join(language === 'cpp' ? '::' : '.');
|
|
42
|
+
const named = (index.symbols.get(simple) || []).filter(d =>
|
|
43
|
+
IDENTITY_TYPE_KINDS.has(d.type) && d.namespace === qualifier &&
|
|
44
|
+
(d.file === context || index.importGraph.get(context)?.has(d.file)));
|
|
45
|
+
if (named.length === 1) return named[0];
|
|
46
|
+
}
|
|
47
|
+
if (language === 'java' || language === 'csharp') {
|
|
48
|
+
const definitions = (index.symbols.get(name) || []).filter(d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
49
|
+
const selected = definitions.filter(d => _resolveReceiverTypeIdentity(
|
|
50
|
+
index, context, name, [{ ...d, className: name }], line, qualified) === 'target');
|
|
51
|
+
if (selected.length === 1) return selected[0];
|
|
52
|
+
}
|
|
53
|
+
if (language === 'java' && qualified) {
|
|
54
|
+
const nested = (index.symbols.get(name) || []).filter(d =>
|
|
55
|
+
IDENTITY_TYPE_KINDS.has(d.type) && d.enclosingType === qualified);
|
|
56
|
+
if (nested.length === 1) return nested[0];
|
|
57
|
+
}
|
|
58
|
+
const origin = _resolveFlowTypeOrigin(index, context, name, qualified);
|
|
59
|
+
if (!origin?.fromFile) return null;
|
|
60
|
+
const definitions = (index.symbols.get(name) || []).filter(d =>
|
|
61
|
+
(IDENTITY_TYPE_KINDS.has(d.type) || (d.type === 'type' && d.aliasOf)) &&
|
|
62
|
+
d.file === origin.fromFile &&
|
|
63
|
+
(!d.lexicalScopeStartLine || (line != null &&
|
|
64
|
+
line >= d.lexicalScopeStartLine && line <= d.lexicalScopeEndLine)));
|
|
65
|
+
return definitions.length === 1 ? definitions[0] : null;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
26
70
|
// `base` is a contextual receiver keyword only in C#. TypeScript and the
|
|
27
71
|
// other supported languages may bind an ordinary local named base; treating
|
|
28
72
|
// it as reserved globally suppresses otherwise exact assignment flow.
|
|
@@ -290,8 +334,9 @@ function findCallers(index, name, options = {}) {
|
|
|
290
334
|
// lookup); the rest stay as shadow-style records. Display caps are handled
|
|
291
335
|
// by formatters — this only bounds file reads.
|
|
292
336
|
const unverifiedEnrichLimit = options.unverifiedEnrichLimit ?? 10;
|
|
293
|
-
const recordExcluded = (filePath, line, reason) => {
|
|
294
|
-
if (accountRaw) accountRaw.excludedEntries.push({ file: filePath, line, reason
|
|
337
|
+
const recordExcluded = (filePath, line, reason, provenance) => {
|
|
338
|
+
if (accountRaw) accountRaw.excludedEntries.push({ file: filePath, line, reason,
|
|
339
|
+
...(provenance && { provenance }) });
|
|
295
340
|
};
|
|
296
341
|
|
|
297
342
|
const definitions = index.symbols.get(name) || [];
|
|
@@ -556,7 +601,7 @@ function findCallers(index, name, options = {}) {
|
|
|
556
601
|
// unverified-tier entry (tiered caller contract: shown in its own
|
|
557
602
|
// section, never silently hidden). Does NOT count toward pendingCount —
|
|
558
603
|
// totals describe the confirmed answer.
|
|
559
|
-
const routeUnverified = (filePath, fileEntry, call, reason, calledAs, meta) => {
|
|
604
|
+
const routeUnverified = (filePath, fileEntry, call, reason, calledAs, meta, facts) => {
|
|
560
605
|
if (!collectAccount) return; // non-account paths (trace/blast/verify) keep the plain drop
|
|
561
606
|
const compilerSelectsOverload = cppOverloadDispatchTarget &&
|
|
562
607
|
(reason === 'overload-ambiguous' || reason === 'ambiguous-binding' ||
|
|
@@ -582,11 +627,28 @@ function findCallers(index, name, options = {}) {
|
|
|
582
627
|
_tier: TIER.UNVERIFIED, _reason: reason, _meta: meta,
|
|
583
628
|
// Dispatch-tiered routes carry their own resolution so JSON output
|
|
584
629
|
// distinguishes "possible virtual dispatch" from a bare uncertain.
|
|
585
|
-
_evidence: reason === '
|
|
630
|
+
_evidence: { ...(reason === 'single-owner' ? { hasSingleOwnerEvidence: true }
|
|
631
|
+
: reason === 'possible-dispatch' ? { possibleDispatch: true }
|
|
586
632
|
: reason === 'method-ambiguous' ? { methodAmbiguous: true }
|
|
587
|
-
: { isUncertain: true },
|
|
633
|
+
: { isUncertain: true }), ...(facts && { facts }) },
|
|
588
634
|
});
|
|
589
635
|
};
|
|
636
|
+
const excludeReceiver = (filePath, fileEntry, call, calledAs, receiverOptions = {}) => {
|
|
637
|
+
const targets = options.targetDefinitions || definitions;
|
|
638
|
+
const facts = _confirmationFacts(index, filePath, call, targets, receiverOptions);
|
|
639
|
+
if ((BUILTIN_RECEIVER_TYPES.has(facts.receiverType) ||
|
|
640
|
+
(fileEntry.language === 'rust' && ['Option', 'Result', 'Vec'].includes(facts.receiverType))) &&
|
|
641
|
+
(facts.receiverTypeSource === 'literal' || !facts.receiverTypeDeclaration)) {
|
|
642
|
+
facts.builtinReceiver = { type: facts.receiverType, language: fileEntry.language };
|
|
643
|
+
}
|
|
644
|
+
const provenance = scoreEdge({ hasReceiverType: true, facts }).provenance;
|
|
645
|
+
const checked = validateConfirmation(provenance, facts.targets);
|
|
646
|
+
if (checked.verdict === 'establishes-other' || checked.verdict === 'unsupported') {
|
|
647
|
+
recordExcluded(filePath, call.line, 'receiver-type-mismatch', provenance);
|
|
648
|
+
} else {
|
|
649
|
+
routeUnverified(filePath, fileEntry, call, 'provenance-incomplete', calledAs, undefined, facts);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
590
652
|
const maxResults = options.maxResults;
|
|
591
653
|
// BUG-H1: when consumers (like `about`) need an accurate truncation header
|
|
592
654
|
// ("showing N of <total>"), they pass needsTotal:true so Phase 1 runs to
|
|
@@ -670,6 +732,21 @@ function findCallers(index, name, options = {}) {
|
|
|
670
732
|
calledAs = call.name;
|
|
671
733
|
}
|
|
672
734
|
|
|
735
|
+
const standardWrapper = fileEntry.language === 'rust'
|
|
736
|
+
? _rustStandardWrapperMethod(index, filePath, call, calls) : null;
|
|
737
|
+
if (standardWrapper) {
|
|
738
|
+
const facts = { language: 'rust', site: occurrenceIdentity(fileEntry.relativePath, call),
|
|
739
|
+
targets: (options.targetDefinitions || definitions).map(declarationIdentity), standardWrapper };
|
|
740
|
+
const checked = validateConfirmation({ facts }, facts.targets);
|
|
741
|
+
if (checked.verdict === 'establishes-other') {
|
|
742
|
+
recordExcluded(filePath, call.line, 'standard-wrapper-method', {
|
|
743
|
+
rule: 'rust-standard-wrapper', rules: ['rust-standard-wrapper'], facts,
|
|
744
|
+
validation: checked.verdict, diagnostic: 'standard-wrapper-method',
|
|
745
|
+
});
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
673
750
|
// A direct static call cannot cross an unrelated runtime
|
|
674
751
|
// language boundary. A Python `service.get` is not a possible
|
|
675
752
|
// target of JavaScript `map.get()`, nor can a Java method be
|
|
@@ -692,6 +769,8 @@ function findCallers(index, name, options = {}) {
|
|
|
692
769
|
call = {
|
|
693
770
|
...call,
|
|
694
771
|
receiverType: indexedType.type,
|
|
772
|
+
receiverTypeSource: 'flow',
|
|
773
|
+
receiverTypeEvidence: { source: 'flow', ...indexedType },
|
|
695
774
|
...(indexedType.fromFile && {
|
|
696
775
|
receiverTypeFlowFile: indexedType.fromFile,
|
|
697
776
|
}),
|
|
@@ -925,7 +1004,7 @@ function findCallers(index, name, options = {}) {
|
|
|
925
1004
|
// persisted with this file's calls.
|
|
926
1005
|
if (call.isMethod && call.receiver &&
|
|
927
1006
|
!_isReservedReceiver(fileEntry.language, call.receiver) &&
|
|
928
|
-
(!call.receiverType || call.receiverTypeGuessed) &&
|
|
1007
|
+
(!call.receiverType || call.receiverTypeGuessed || call.receiverTypeSource === 'guess') &&
|
|
929
1008
|
!call.receiverPatternShadow && !call.receiverFlowInvalidated &&
|
|
930
1009
|
!call.receiverIsChainRoot &&
|
|
931
1010
|
(langTraits(fileEntry.language)?.typeSystem === 'structural' ||
|
|
@@ -954,7 +1033,7 @@ function findCallers(index, name, options = {}) {
|
|
|
954
1033
|
receiverExternalConcreteFlow: true,
|
|
955
1034
|
}),
|
|
956
1035
|
};
|
|
957
|
-
if (call.receiverTypeGuessed) {
|
|
1036
|
+
if (call.receiverTypeGuessed || call.receiverTypeSource === 'guess') {
|
|
958
1037
|
call = { ...call, receiverType: undefined, receiverTypeGuessed: undefined };
|
|
959
1038
|
}
|
|
960
1039
|
} else if (flowEntry) {
|
|
@@ -965,12 +1044,30 @@ function findCallers(index, name, options = {}) {
|
|
|
965
1044
|
// annotation says *DefaultCodecRegistry; the guess
|
|
966
1045
|
// excluded all three true RegisterCodec callers).
|
|
967
1046
|
call = { ...call, receiverType: flowEntry.type,
|
|
1047
|
+
receiverTypeSource: 'flow',
|
|
1048
|
+
receiverTypeEvidence: { source: 'flow', ...flowEntry },
|
|
968
1049
|
receiverTypeGuessed: undefined,
|
|
969
1050
|
receiverFlowInvalidated: false,
|
|
970
1051
|
...(flowEntry.fromFile && { receiverTypeFlowFile: flowEntry.fromFile }) };
|
|
971
1052
|
}
|
|
972
1053
|
}
|
|
973
1054
|
|
|
1055
|
+
if (fileEntry.language === 'python' && call.isMethod && !call.receiverType) {
|
|
1056
|
+
const fixture = _pythonFixtureReceiverType(index, filePath, call);
|
|
1057
|
+
if (fixture) call = { ...call, receiverType: fixture.type,
|
|
1058
|
+
receiverTypeSource: 'fixture', receiverTypeEvidence: { source: 'fixture', ...fixture },
|
|
1059
|
+
...(fixture.fromFile && { receiverTypeFlowFile: fixture.fromFile }) };
|
|
1060
|
+
const external = !fixture && _pythonExternalFieldFlow(index, filePath, call);
|
|
1061
|
+
if (external) {
|
|
1062
|
+
call = { ...call, receiverTypeSource: 'flow',
|
|
1063
|
+
receiverTypeEvidence: { source: 'flow', externalFactory: external.proof } };
|
|
1064
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs,
|
|
1065
|
+
{ dispatchVia: external.via, externalContract: true },
|
|
1066
|
+
_confirmationFacts(index, filePath, call, options.targetDefinitions || definitions));
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
974
1071
|
// Python indexed receivers (fix #324): `layout["body"].update()`
|
|
975
1072
|
// dispatch through the container's compiler-visible
|
|
976
1073
|
// `__getitem__` return contract. The parser retains only a
|
|
@@ -992,6 +1089,8 @@ function findCallers(index, name, options = {}) {
|
|
|
992
1089
|
call = {
|
|
993
1090
|
...call,
|
|
994
1091
|
receiverType: indexedType.type,
|
|
1092
|
+
receiverTypeSource: 'flow',
|
|
1093
|
+
receiverTypeEvidence: { source: 'flow', ...indexedType },
|
|
995
1094
|
...(indexedType.fromFile && {
|
|
996
1095
|
receiverTypeFlowFile: indexedType.fromFile,
|
|
997
1096
|
}),
|
|
@@ -1028,7 +1127,9 @@ function findCallers(index, name, options = {}) {
|
|
|
1028
1127
|
const items = _pythonDeclaredIterablePathItems(
|
|
1029
1128
|
index, rootType, call.receiverIterationFields);
|
|
1030
1129
|
const item = items?.[call.receiverIterationIndex || 0];
|
|
1031
|
-
if (item) call = { ...call, receiverType: item
|
|
1130
|
+
if (item) call = { ...call, receiverType: item,
|
|
1131
|
+
receiverTypeSource: 'flow',
|
|
1132
|
+
receiverTypeEvidence: { source: 'flow', type: item, iteration: call.receiver } };
|
|
1032
1133
|
}
|
|
1033
1134
|
}
|
|
1034
1135
|
|
|
@@ -1038,6 +1139,8 @@ function findCallers(index, name, options = {}) {
|
|
|
1038
1139
|
index, fileEntry, call.receiver);
|
|
1039
1140
|
if (importedType) {
|
|
1040
1141
|
call = { ...call, receiverType: importedType.type,
|
|
1142
|
+
receiverTypeSource: 'flow',
|
|
1143
|
+
receiverTypeEvidence: { source: 'flow', ...importedType },
|
|
1041
1144
|
receiverTypeFlowFile: importedType.fromFile };
|
|
1042
1145
|
}
|
|
1043
1146
|
}
|
|
@@ -1082,6 +1185,9 @@ function findCallers(index, name, options = {}) {
|
|
|
1082
1185
|
call = {
|
|
1083
1186
|
...call,
|
|
1084
1187
|
receiverType: returnInfo.type.replace(/\[\]$/, '').split('.').pop(),
|
|
1188
|
+
receiverTypeSource: 'flow',
|
|
1189
|
+
receiverTypeEvidence: { source: 'flow', ...returnInfo,
|
|
1190
|
+
producerPath: call.receiverCallTypePath },
|
|
1085
1191
|
...(returnInfo.fromFile && {
|
|
1086
1192
|
receiverTypeFlowFile: returnInfo.fromFile,
|
|
1087
1193
|
}),
|
|
@@ -1113,7 +1219,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1113
1219
|
// exact indexed variant and positional payload before method
|
|
1114
1220
|
// dispatch; external qualified payloads remain external
|
|
1115
1221
|
// provenance, never borrowed project identity.
|
|
1116
|
-
if (collectAccount && fileEntry.language === 'rust' &&
|
|
1222
|
+
if ((collectAccount || call.receiverPatternSourceCallStart != null) && fileEntry.language === 'rust' &&
|
|
1117
1223
|
call.isMethod && !call.receiverType &&
|
|
1118
1224
|
call.receiverPatternVariant) {
|
|
1119
1225
|
const patternType = _rustPatternReceiverType(
|
|
@@ -1122,6 +1228,8 @@ function findCallers(index, name, options = {}) {
|
|
|
1122
1228
|
call = {
|
|
1123
1229
|
...call,
|
|
1124
1230
|
receiverType: patternType.type,
|
|
1231
|
+
receiverTypeSource: 'flow',
|
|
1232
|
+
receiverTypeEvidence: { source: 'flow', ...patternType },
|
|
1125
1233
|
...(patternType.fromFile && {
|
|
1126
1234
|
receiverTypeFlowFile: patternType.fromFile,
|
|
1127
1235
|
}),
|
|
@@ -1246,10 +1354,14 @@ function findCallers(index, name, options = {}) {
|
|
|
1246
1354
|
const builtinChained = _pythonBuiltinChainedReceiverType(
|
|
1247
1355
|
index, fileEntry, call, foldCtx);
|
|
1248
1356
|
if (builtinChained) {
|
|
1249
|
-
call = { ...call, receiverType: builtinChained,
|
|
1357
|
+
call = { ...call, receiverType: builtinChained.type,
|
|
1358
|
+
receiverTypeSource: 'flow',
|
|
1359
|
+
receiverTypeEvidence: { source: 'flow', ...builtinChained },
|
|
1250
1360
|
receiverTypePlatform: true };
|
|
1251
1361
|
} else if (folded && folded.type) {
|
|
1252
1362
|
call = { ...call, receiverType: folded.type,
|
|
1363
|
+
receiverTypeSource: 'flow',
|
|
1364
|
+
receiverTypeEvidence: { source: 'flow', ...folded },
|
|
1253
1365
|
...(folded.fromFile && { receiverTypeFlowFile: folded.fromFile }) };
|
|
1254
1366
|
} else if (folded && folded.externalVia) {
|
|
1255
1367
|
call = {
|
|
@@ -1262,6 +1374,8 @@ function findCallers(index, name, options = {}) {
|
|
|
1262
1374
|
} else if (!folded?.suppressFallback) {
|
|
1263
1375
|
const chainedType = _chainedReceiverType(index, call, fileEntry.language);
|
|
1264
1376
|
if (chainedType) call = { ...call, receiverType: chainedType.type,
|
|
1377
|
+
receiverTypeSource: 'flow',
|
|
1378
|
+
receiverTypeEvidence: { source: 'flow', ...chainedType },
|
|
1265
1379
|
...(chainedType.fromFile && { receiverTypeFlowFile: chainedType.fromFile }) };
|
|
1266
1380
|
}
|
|
1267
1381
|
} else if (call.isMethod && (!call.receiver || call.receiverIsChainRoot) &&
|
|
@@ -1303,6 +1417,8 @@ function findCallers(index, name, options = {}) {
|
|
|
1303
1417
|
};
|
|
1304
1418
|
} else if (flowEntry) {
|
|
1305
1419
|
call = { ...call, receiverType: flowEntry.type,
|
|
1420
|
+
receiverTypeSource: 'flow',
|
|
1421
|
+
receiverTypeEvidence: { source: 'flow', ...flowEntry },
|
|
1306
1422
|
...(flowEntry.fromFile && { receiverTypeFlowFile: flowEntry.fromFile }) };
|
|
1307
1423
|
}
|
|
1308
1424
|
}
|
|
@@ -1313,7 +1429,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1313
1429
|
// analysis resolves the source owner and its declared
|
|
1314
1430
|
// `Iterator<Item = T>` contract. Item-preserving std adapters
|
|
1315
1431
|
// such as rev/filter/take recurse to that same declaration.
|
|
1316
|
-
if (
|
|
1432
|
+
if (fileEntry.language === 'rust' &&
|
|
1317
1433
|
call.isMethod && call.receiver && !call.receiverType &&
|
|
1318
1434
|
(call.receiverIterationCall || call.receiverIterationVariable)) {
|
|
1319
1435
|
let foldCtx = foldCtxCache.get(filePath);
|
|
@@ -1329,37 +1445,14 @@ function findCallers(index, name, options = {}) {
|
|
|
1329
1445
|
} };
|
|
1330
1446
|
foldCtxCache.set(filePath, foldCtx);
|
|
1331
1447
|
}
|
|
1332
|
-
|
|
1333
|
-
if (
|
|
1334
|
-
const flow = _lookupReturnTypeFlow(foldCtx.getFlowMap(), {
|
|
1335
|
-
...call,
|
|
1336
|
-
receiver: call.receiverIterationVariable,
|
|
1337
|
-
});
|
|
1338
|
-
if (flow?.iteratorItemType) {
|
|
1339
|
-
items = [{
|
|
1340
|
-
type: flow.iteratorItemType,
|
|
1341
|
-
fromFile: flow.iteratorItemFromFile,
|
|
1342
|
-
}];
|
|
1343
|
-
}
|
|
1344
|
-
} else {
|
|
1345
|
-
const sources = _chainedProducerRecords(foldCtx, {
|
|
1346
|
-
receiverCall: call.receiverIterationCall,
|
|
1347
|
-
receiverCallIsMethod: call.receiverIterationCallIsMethod,
|
|
1348
|
-
receiverCallLine: call.receiverIterationCallLine,
|
|
1349
|
-
receiverCallStart: call.receiverIterationCallStart,
|
|
1350
|
-
receiverCallEnd: call.receiverIterationCallEnd,
|
|
1351
|
-
});
|
|
1352
|
-
items = sources.map(source => _rustIteratorOutputItemType(
|
|
1353
|
-
index, fileEntry, filePath, source, foldCtx));
|
|
1354
|
-
}
|
|
1355
|
-
if (items.length > 0 && items.every(Boolean) &&
|
|
1356
|
-
new Set(items.map(item => item.type)).size === 1 &&
|
|
1357
|
-
new Set(items.map(item => item.fromFile)).size === 1 &&
|
|
1358
|
-
items[0].fromFile) {
|
|
1448
|
+
const item = _rustIterationReceiverType(index, fileEntry, filePath, call, foldCtx);
|
|
1449
|
+
if (item) {
|
|
1359
1450
|
call = {
|
|
1360
1451
|
...call,
|
|
1361
|
-
receiverType:
|
|
1362
|
-
|
|
1452
|
+
receiverType: item.type,
|
|
1453
|
+
receiverTypeSource: 'flow',
|
|
1454
|
+
receiverTypeEvidence: { source: 'flow', ...item },
|
|
1455
|
+
receiverTypeFlowFile: item.fromFile,
|
|
1363
1456
|
};
|
|
1364
1457
|
}
|
|
1365
1458
|
}
|
|
@@ -1393,6 +1486,8 @@ function findCallers(index, name, options = {}) {
|
|
|
1393
1486
|
call = {
|
|
1394
1487
|
...call,
|
|
1395
1488
|
receiverType: closureType.type,
|
|
1489
|
+
receiverTypeSource: 'flow',
|
|
1490
|
+
receiverTypeEvidence: { source: 'flow', ...closureType },
|
|
1396
1491
|
receiverTypeFlowFile: closureType.fromFile,
|
|
1397
1492
|
};
|
|
1398
1493
|
}
|
|
@@ -1590,7 +1685,7 @@ function findCallers(index, name, options = {}) {
|
|
|
1590
1685
|
dispatchCandidates: countDispatchCandidates(call.receiverType),
|
|
1591
1686
|
});
|
|
1592
1687
|
} else {
|
|
1593
|
-
|
|
1688
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
1594
1689
|
}
|
|
1595
1690
|
}
|
|
1596
1691
|
continue;
|
|
@@ -1619,9 +1714,9 @@ function findCallers(index, name, options = {}) {
|
|
|
1619
1714
|
const cbTypedMatch = call.receiverType && cbTypes.has(call.receiverType);
|
|
1620
1715
|
const cbAllTypeTargets = cbTargetDefs.length > 0 &&
|
|
1621
1716
|
cbTargetDefs.every(d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
1622
|
-
if (!cbTypeQualified && !cbTypedMatch
|
|
1623
|
-
(
|
|
1624
|
-
|
|
1717
|
+
if (!cbTypeQualified && !cbTypedMatch) {
|
|
1718
|
+
routeUnverified(filePath, fileEntry, call,
|
|
1719
|
+
methodOwnerKeys().size === 1 && !cbAllTypeTargets ? 'single-owner' : 'method-ambiguous', calledAs,
|
|
1625
1720
|
{ dispatchCandidates: methodOwnerKeys().size });
|
|
1626
1721
|
continue;
|
|
1627
1722
|
}
|
|
@@ -2005,6 +2100,7 @@ function findCallers(index, name, options = {}) {
|
|
|
2005
2100
|
// resolution, but its evidence grade is receiver-hint, not
|
|
2006
2101
|
// same-class (the receiver is the field's class).
|
|
2007
2102
|
let resolvedByTypedAttribute = false;
|
|
2103
|
+
let resolvedReceiverFacts = {};
|
|
2008
2104
|
// Receiver/path type known to mismatch the target: such an edge can
|
|
2009
2105
|
// never tier as confirmed even when legacy includeUncertain keeps it
|
|
2010
2106
|
// visible (scoreEdge checks hasReceiverType before isUncertain, so
|
|
@@ -2053,6 +2149,9 @@ function findCallers(index, name, options = {}) {
|
|
|
2053
2149
|
index, fileEntry, call.receiver);
|
|
2054
2150
|
if (staticFieldType) {
|
|
2055
2151
|
call = { ...call, receiverType: staticFieldType,
|
|
2152
|
+
receiverTypeSource: 'field',
|
|
2153
|
+
receiverTypeEvidence: { source: 'field', field: call.receiver,
|
|
2154
|
+
type: staticFieldType, imports: fileEntry.importBindings },
|
|
2056
2155
|
receiverIsTypeQualified: false };
|
|
2057
2156
|
}
|
|
2058
2157
|
}
|
|
@@ -2160,6 +2259,12 @@ function findCallers(index, name, options = {}) {
|
|
|
2160
2259
|
if (compatibleTypes.has(targetClass)) {
|
|
2161
2260
|
resolvedBySameClass = true;
|
|
2162
2261
|
resolvedByTypedAttribute = true;
|
|
2262
|
+
resolvedReceiverFacts = {
|
|
2263
|
+
receiverType: targetClass, receiverTypeSource: 'field',
|
|
2264
|
+
receiverOrigin: { source: 'field', rootType: callerSymbol.className,
|
|
2265
|
+
field: call.selfAttribute, scope: declarationIdentity(callerSymbol),
|
|
2266
|
+
...attrTypes?.origins?.get(call.selfAttribute) },
|
|
2267
|
+
};
|
|
2163
2268
|
} else if (_isAncestorOfTargetClass(index, targetClass, tDefs)) {
|
|
2164
2269
|
// A field declared as a strict ancestor can
|
|
2165
2270
|
// dynamically hold the pinned override.
|
|
@@ -2172,7 +2277,11 @@ function findCallers(index, name, options = {}) {
|
|
|
2172
2277
|
// type is exact negative evidence. Earlier
|
|
2173
2278
|
// code discarded this field contract and
|
|
2174
2279
|
// emitted method-no-evidence.
|
|
2175
|
-
|
|
2280
|
+
excludeReceiver(filePath, fileEntry, call, calledAs, {
|
|
2281
|
+
receiverType: targetClass, receiverTypeSource: 'field',
|
|
2282
|
+
receiverOrigin: { source: 'field', rootType: callerSymbol?.className,
|
|
2283
|
+
field: call.selfAttribute, ...attrTypes?.origins?.get(call.selfAttribute) },
|
|
2284
|
+
});
|
|
2176
2285
|
continue;
|
|
2177
2286
|
} else if (options.collectAccount || !options.includeMethods) {
|
|
2178
2287
|
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
@@ -2351,7 +2460,9 @@ function findCallers(index, name, options = {}) {
|
|
|
2351
2460
|
const builtinFieldType = _pythonBuiltinFieldPathType(
|
|
2352
2461
|
index, fileEntry, call.receiverRoot, call.receiverFields);
|
|
2353
2462
|
if (builtinFieldType) {
|
|
2354
|
-
call = { ...call, receiverType: builtinFieldType,
|
|
2463
|
+
call = { ...call, receiverType: builtinFieldType.type,
|
|
2464
|
+
receiverTypeSource: 'field',
|
|
2465
|
+
receiverTypeEvidence: { source: 'field', ...builtinFieldType },
|
|
2355
2466
|
receiverTypePlatform: true };
|
|
2356
2467
|
}
|
|
2357
2468
|
}
|
|
@@ -2774,6 +2885,42 @@ function findCallers(index, name, options = {}) {
|
|
|
2774
2885
|
// modules must not exclude: a binding to an unresolved module
|
|
2775
2886
|
// whose first segment matches a project directory routes
|
|
2776
2887
|
// visible instead.
|
|
2888
|
+
// Rust item renames are name-level ownership (fix #357): a
|
|
2889
|
+
// bare call spelled by a `use path::name as local` alias
|
|
2890
|
+
// denotes exactly the item that binding resolves to. Two
|
|
2891
|
+
// renames of one source name from different modules
|
|
2892
|
+
// (`use alpha::widget as a; use beta::widget as b`) used to
|
|
2893
|
+
// fall through to file-level import evidence, which BOTH
|
|
2894
|
+
// modules satisfy, so each pin confirmed both sites. The
|
|
2895
|
+
// paired binding decides: resolves into a target file →
|
|
2896
|
+
// confirmable; resolves to another project file → excluded
|
|
2897
|
+
// other-definition-import; unresolvable → visible (a resolver
|
|
2898
|
+
// gap is never exclusion evidence).
|
|
2899
|
+
if (calledAs && !call.isMethod && !call.receiver &&
|
|
2900
|
+
fileEntry.language === 'rust' && call.name === calledAs) {
|
|
2901
|
+
const paired = (fileEntry.importBindings || []).filter(b =>
|
|
2902
|
+
b.alias === call.name);
|
|
2903
|
+
if (paired.length > 0) {
|
|
2904
|
+
const tFiles = new Set(targetDefs.map(d => d.file).filter(Boolean));
|
|
2905
|
+
let verdict = 'unknown';
|
|
2906
|
+
for (const b of paired) {
|
|
2907
|
+
const resolved = _rustBindingResolvedFiles(index, fileEntry, filePath, b);
|
|
2908
|
+
if (resolved.size === 0) { verdict = 'unknown'; break; }
|
|
2909
|
+
const hit = [...resolved].some(f => tFiles.has(f));
|
|
2910
|
+
if (hit) { verdict = 'target'; break; }
|
|
2911
|
+
verdict = 'other';
|
|
2912
|
+
}
|
|
2913
|
+
if (verdict === 'other') {
|
|
2914
|
+
recordExcluded(filePath, call.line, 'other-definition-import');
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
if (verdict === 'unknown' && collectAccount) {
|
|
2918
|
+
routeUnverified(filePath, fileEntry, call, 'no-import-link', calledAs);
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2777
2924
|
if (!bindingId && !call.isMethod &&
|
|
2778
2925
|
langTraits(fileEntry.language)?.typeSystem === 'structural' &&
|
|
2779
2926
|
(fileEntry.importBindings || []).length > 0) {
|
|
@@ -3036,6 +3183,13 @@ function findCallers(index, name, options = {}) {
|
|
|
3036
3183
|
continue;
|
|
3037
3184
|
}
|
|
3038
3185
|
const targetHasClass = targetDefs.some(d => d.className);
|
|
3186
|
+
if (call.isMethod && !targetHasClass && fileEntry.language === 'c') {
|
|
3187
|
+
// C member calls invoke function-pointer fields. The
|
|
3188
|
+
// pointer can hold the pinned free function; member
|
|
3189
|
+
// syntax alone is neither binding nor exclusion proof.
|
|
3190
|
+
routeUnverified(filePath, fileEntry, call, 'callable-field', calledAs);
|
|
3191
|
+
continue;
|
|
3192
|
+
}
|
|
3039
3193
|
if (call.isMethod && !targetHasClass) {
|
|
3040
3194
|
// Method call but target is a standalone function — skip
|
|
3041
3195
|
recordExcluded(filePath, call.line, 'method-kind-mismatch');
|
|
@@ -3227,7 +3381,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3227
3381
|
isUncertain = true;
|
|
3228
3382
|
typeMismatch = true;
|
|
3229
3383
|
if (collectAccount) {
|
|
3230
|
-
|
|
3384
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
3231
3385
|
continue;
|
|
3232
3386
|
}
|
|
3233
3387
|
if (!options.includeUncertain) {
|
|
@@ -3303,7 +3457,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3303
3457
|
isUncertain = true;
|
|
3304
3458
|
typeMismatch = true;
|
|
3305
3459
|
if (collectAccount) {
|
|
3306
|
-
|
|
3460
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
3307
3461
|
continue;
|
|
3308
3462
|
}
|
|
3309
3463
|
if (!options.includeUncertain) {
|
|
@@ -3312,6 +3466,14 @@ function findCallers(index, name, options = {}) {
|
|
|
3312
3466
|
}
|
|
3313
3467
|
}
|
|
3314
3468
|
|
|
3469
|
+
// C# dynamic is an unresolved runtime receiver, never a
|
|
3470
|
+
// concrete foreign type that can disprove a project edge.
|
|
3471
|
+
if (fileEntry.language === 'csharp' && call.receiverType === 'dynamic') {
|
|
3472
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs,
|
|
3473
|
+
{ dispatchVia: 'dynamic' });
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3315
3477
|
// Receiver-class disambiguation:
|
|
3316
3478
|
// When the target definition has a class/receiver type, filter callers
|
|
3317
3479
|
// whose receiverType is known to be a different type.
|
|
@@ -3571,7 +3733,8 @@ function findCallers(index, name, options = {}) {
|
|
|
3571
3733
|
// Not evidence against — visible possible-dispatch.
|
|
3572
3734
|
// Go struct embedding binds statically and stays
|
|
3573
3735
|
// excluded.
|
|
3574
|
-
if (_dispatchCapableSupertype(index, fileEntry.language, knownType, targetDefs, definitions)
|
|
3736
|
+
if (_dispatchCapableSupertype(index, fileEntry.language, knownType, targetDefs, definitions) ||
|
|
3737
|
+
(!knownTypeHasProjectIdentity && externalContractTarget()?.via === knownType)) {
|
|
3575
3738
|
const externalContract = externalContractTarget();
|
|
3576
3739
|
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
3577
3740
|
dispatchVia: knownType,
|
|
@@ -3582,7 +3745,11 @@ function findCallers(index, name, options = {}) {
|
|
|
3582
3745
|
});
|
|
3583
3746
|
continue;
|
|
3584
3747
|
}
|
|
3585
|
-
|
|
3748
|
+
excludeReceiver(filePath, fileEntry, call, calledAs, fieldHopType && !call.receiverType ? {
|
|
3749
|
+
receiverType: fieldHopType, receiverTypeSource: 'field',
|
|
3750
|
+
receiverOrigin: { source: 'field', root: call.receiverRoot,
|
|
3751
|
+
rootType: call.receiverRootType, field: call.receiverField },
|
|
3752
|
+
} : {});
|
|
3586
3753
|
continue;
|
|
3587
3754
|
}
|
|
3588
3755
|
if (!options.includeUncertain) {
|
|
@@ -3695,7 +3862,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3695
3862
|
isUncertain = true;
|
|
3696
3863
|
typeMismatch = true;
|
|
3697
3864
|
if (collectAccount) {
|
|
3698
|
-
|
|
3865
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
3699
3866
|
continue;
|
|
3700
3867
|
}
|
|
3701
3868
|
if (!options.includeUncertain) {
|
|
@@ -3736,6 +3903,10 @@ function findCallers(index, name, options = {}) {
|
|
|
3736
3903
|
}
|
|
3737
3904
|
inferredMatch = true;
|
|
3738
3905
|
nominalInferredMatch = true;
|
|
3906
|
+
resolvedReceiverFacts = {
|
|
3907
|
+
receiverType: typeReceiver, receiverTypeSource: 'type-qualified',
|
|
3908
|
+
receiverOrigin: { source: 'type-qualified', site: occurrenceIdentity(fileEntry.relativePath, call) },
|
|
3909
|
+
};
|
|
3739
3910
|
}
|
|
3740
3911
|
// Still no type — fall back to receiver name matching when
|
|
3741
3912
|
// multiple defs exist. A field-declared interface/trait type
|
|
@@ -4100,7 +4271,9 @@ function findCallers(index, name, options = {}) {
|
|
|
4100
4271
|
let aliasResolvedFile = null;
|
|
4101
4272
|
if (receiverName && !tTypes.has(receiverName)) {
|
|
4102
4273
|
for (const im of (fileEntry.importBindings || [])) {
|
|
4103
|
-
|
|
4274
|
+
// fix #357: Rust rename bindings carry the local
|
|
4275
|
+
// name as `alias` (the original is `name`).
|
|
4276
|
+
if ((im.alias || im.name) !== receiverName) continue;
|
|
4104
4277
|
// fix #353: C# `using BH = Beta.Helper` (and Java
|
|
4105
4278
|
// dotted paths) split on `.`; Rust paths on `::`.
|
|
4106
4279
|
const orig = String(im.module || '').split(/::|\./).pop();
|
|
@@ -4154,6 +4327,13 @@ function findCallers(index, name, options = {}) {
|
|
|
4154
4327
|
continue;
|
|
4155
4328
|
}
|
|
4156
4329
|
}
|
|
4330
|
+
if (typeQualifiedReceiver) {
|
|
4331
|
+
resolvedReceiverFacts = {
|
|
4332
|
+
receiverType: receiverName, receiverTypeSource: 'type-qualified',
|
|
4333
|
+
receiverOrigin: { source: 'type-qualified', site: occurrenceIdentity(fileEntry.relativePath, call) },
|
|
4334
|
+
...(aliasResolvedFile && { originFile: aliasResolvedFile }),
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4157
4337
|
if (!typeQualifiedReceiver) {
|
|
4158
4338
|
// External-producer receiver (fix #220): the variable
|
|
4159
4339
|
// was assigned from a call into an external package
|
|
@@ -4344,6 +4524,15 @@ function findCallers(index, name, options = {}) {
|
|
|
4344
4524
|
}
|
|
4345
4525
|
}
|
|
4346
4526
|
}
|
|
4527
|
+
// Module ownership is evidence for an exported item,
|
|
4528
|
+
// not for a method nested inside that module's type.
|
|
4529
|
+
// A same-named free function and method can share the
|
|
4530
|
+
// target file (itertools::peek_nth vs PeekNth::peek_nth).
|
|
4531
|
+
if (call.moduleOwnedPath && fileEntry.language === 'rust' &&
|
|
4532
|
+
targetDefs2.every(d => d.className || d.receiver)) {
|
|
4533
|
+
routeUnverified(filePath, fileEntry, call, 'provenance-incomplete', calledAs);
|
|
4534
|
+
continue;
|
|
4535
|
+
}
|
|
4347
4536
|
const knownDispatchType = call.receiverType || fieldHopType || fieldDispatchType;
|
|
4348
4537
|
// Module-owned qualified calls (fix #260b) are resolved
|
|
4349
4538
|
// by OWNERSHIP — the dispatch-ambiguity routing below is
|
|
@@ -4373,8 +4562,11 @@ function findCallers(index, name, options = {}) {
|
|
|
4373
4562
|
});
|
|
4374
4563
|
continue;
|
|
4375
4564
|
}
|
|
4376
|
-
if (!call.moduleOwnedPath && !knownDispatchType
|
|
4377
|
-
|
|
4565
|
+
if (!call.moduleOwnedPath && !knownDispatchType) {
|
|
4566
|
+
const contract = externalContractTarget();
|
|
4567
|
+
routeUnverified(filePath, fileEntry, call,
|
|
4568
|
+
contract ? 'possible-dispatch' : methodOwnerKeys().size === 1 ? 'single-owner' : 'method-ambiguous', calledAs, {
|
|
4569
|
+
...(contract && { dispatchVia: contract.via, externalContract: true }),
|
|
4378
4570
|
dispatchCandidates: methodOwnerKeys().size,
|
|
4379
4571
|
});
|
|
4380
4572
|
continue;
|
|
@@ -4566,8 +4758,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4566
4758
|
// in a file that imports _decoders.py is bytes.decode, not
|
|
4567
4759
|
// ContentDecoder.decode. An untyped-receiver method call
|
|
4568
4760
|
// confirms only via binding, same-class, a validated receiver
|
|
4569
|
-
// type
|
|
4570
|
-
//
|
|
4761
|
+
// type or a type-qualified receiver (Class.method static style).
|
|
4762
|
+
// Single-owner and multi-owner name matches
|
|
4571
4763
|
// route VISIBLE method-ambiguous — never dropped. Same for a
|
|
4572
4764
|
// bare call against pure method targets (a bare name cannot
|
|
4573
4765
|
// denote a method in JS/TS/Python — only a rebound alias can,
|
|
@@ -4583,7 +4775,11 @@ function findCallers(index, name, options = {}) {
|
|
|
4583
4775
|
if (call.isMethod && !call.receiverIsModule &&
|
|
4584
4776
|
!recvSubmoduleRel && !call.moduleOwnedPath) {
|
|
4585
4777
|
const tTypes = dispatchTargetTypes(targetDefs2);
|
|
4586
|
-
|
|
4778
|
+
let typeQualifiedReceiver = !!(call.receiver && tTypes.has(call.receiver));
|
|
4779
|
+
if (typeQualifiedReceiver) resolvedReceiverFacts = {
|
|
4780
|
+
receiverType: call.receiver, receiverTypeSource: 'type-qualified',
|
|
4781
|
+
receiverOrigin: { source: 'type-qualified', site: occurrenceIdentity(fileEntry.relativePath, call) },
|
|
4782
|
+
};
|
|
4587
4783
|
const knownDispatchType = call.receiverType ||
|
|
4588
4784
|
fieldHopType || fieldDispatchType;
|
|
4589
4785
|
// A compiler/parser-known receiver type that is not a
|
|
@@ -4758,6 +4954,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4758
4954
|
});
|
|
4759
4955
|
continue;
|
|
4760
4956
|
}
|
|
4957
|
+
typeQualifiedReceiver = true;
|
|
4958
|
+
call = { ...call, moduleOwnedPath: true };
|
|
4761
4959
|
}
|
|
4762
4960
|
// External-contract single owner (fix #210): same
|
|
4763
4961
|
// physics as the nominal gate above — an override
|
|
@@ -4787,7 +4985,7 @@ function findCallers(index, name, options = {}) {
|
|
|
4787
4985
|
});
|
|
4788
4986
|
continue;
|
|
4789
4987
|
}
|
|
4790
|
-
if (!typeQualifiedReceiver
|
|
4988
|
+
if (!typeQualifiedReceiver) {
|
|
4791
4989
|
const knownDispatchType = call.receiverType || fieldHopType || fieldDispatchType;
|
|
4792
4990
|
if (knownDispatchType) {
|
|
4793
4991
|
// Known-but-unvalidated type (supertype of the
|
|
@@ -4801,7 +4999,7 @@ function findCallers(index, name, options = {}) {
|
|
|
4801
4999
|
dispatchCandidates: countDispatchCandidates(knownDispatchType),
|
|
4802
5000
|
});
|
|
4803
5001
|
} else {
|
|
4804
|
-
routeUnverified(filePath, fileEntry, call, 'method-ambiguous', calledAs, {
|
|
5002
|
+
routeUnverified(filePath, fileEntry, call, methodOwnerKeys().size === 1 ? 'single-owner' : 'method-ambiguous', calledAs, {
|
|
4805
5003
|
dispatchCandidates: methodOwnerKeys().size,
|
|
4806
5004
|
});
|
|
4807
5005
|
}
|
|
@@ -4876,6 +5074,17 @@ function findCallers(index, name, options = {}) {
|
|
|
4876
5074
|
}
|
|
4877
5075
|
}
|
|
4878
5076
|
|
|
5077
|
+
if (!collectAccount && call.isMethod && !call.isConstructor &&
|
|
5078
|
+
!resolvedBySameClass && !receiverTypeValidated && !nominalInferredMatch &&
|
|
5079
|
+
!resolvedByExtensionMethod && !call.moduleOwnedPath &&
|
|
5080
|
+
!call.receiverIsModule && !recvSubmoduleRel) {
|
|
5081
|
+
const qualified = _calleeTypeQualifiedReceiver(index,
|
|
5082
|
+
{ ...callerSymbol, file: filePath }, fileEntry, call, fileEntry.language);
|
|
5083
|
+
const exact = qualified?.match && targetDefs.some(target =>
|
|
5084
|
+
target.file === qualified.match.file && target.startLine === qualified.match.startLine);
|
|
5085
|
+
if (!exact) continue;
|
|
5086
|
+
}
|
|
5087
|
+
|
|
4879
5088
|
if (!pendingByFile.has(filePath)) pendingByFile.set(filePath, []);
|
|
4880
5089
|
pendingByFile.get(filePath).push({
|
|
4881
5090
|
call, fileEntry, callerSymbol,
|
|
@@ -4891,6 +5100,19 @@ function findCallers(index, name, options = {}) {
|
|
|
4891
5100
|
receiverType: call.receiverType,
|
|
4892
5101
|
calledAs,
|
|
4893
5102
|
_evidence: {
|
|
5103
|
+
facts: _confirmationFacts(index, filePath, call, targetDefs, {
|
|
5104
|
+
bindingId,
|
|
5105
|
+
...resolvedReceiverFacts,
|
|
5106
|
+
sameClass: !!resolvedBySameClass && !resolvedByTypedAttribute,
|
|
5107
|
+
...(fieldHopType && !call.receiverType && {
|
|
5108
|
+
receiverType: fieldHopType, receiverTypeSource: 'field',
|
|
5109
|
+
receiverOrigin: { source: 'field', root: call.receiverRoot,
|
|
5110
|
+
rootType: call.receiverRootType, field: call.receiverField },
|
|
5111
|
+
}),
|
|
5112
|
+
}),
|
|
5113
|
+
typeQualifiedReceiver: resolvedReceiverFacts.receiverTypeSource === 'type-qualified',
|
|
5114
|
+
moduleOwnedPath: !!call.moduleOwnedPath,
|
|
5115
|
+
extensionMethod: !!resolvedByExtensionMethod,
|
|
4894
5116
|
hasBindingId: !!bindingId,
|
|
4895
5117
|
resolvedBySameClass: !!resolvedBySameClass && !resolvedByTypedAttribute,
|
|
4896
5118
|
hasSamePackageEvidence,
|
|
@@ -4912,8 +5134,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4912
5134
|
// The dispatch gates above have already rejected
|
|
4913
5135
|
// external contracts, universal methods, wrong arity,
|
|
4914
5136
|
// unresolved producer flow, and multi-owner names.
|
|
4915
|
-
//
|
|
4916
|
-
//
|
|
5137
|
+
// Owner count describes unresolved candidates. It
|
|
5138
|
+
// never establishes the identity of a value receiver (#355).
|
|
4917
5139
|
hasSingleOwnerEvidence: !!(collectAccount && call.isMethod &&
|
|
4918
5140
|
!call.inMacroDefinition && !call.isMacro &&
|
|
4919
5141
|
!call.receiverType && !fieldHopType &&
|
|
@@ -4967,14 +5189,43 @@ function findCallers(index, name, options = {}) {
|
|
|
4967
5189
|
for (const { call, fileEntry, callerSymbol, isMethod, isFunctionReference,
|
|
4968
5190
|
isTypeReference, receiver, receiverType, calledAs, _evidence, _tier,
|
|
4969
5191
|
_reason, _meta } of pending) {
|
|
4970
|
-
const
|
|
5192
|
+
const evidence = {
|
|
5193
|
+
...(_evidence || {}),
|
|
5194
|
+
...(_reason && { reason: _reason }),
|
|
5195
|
+
facts: _evidence?.facts || _confirmationFacts(index, filePath,
|
|
5196
|
+
call, options.targetDefinitions || definitions, {
|
|
5197
|
+
sameClass: _evidence?.resolvedBySameClass,
|
|
5198
|
+
}),
|
|
5199
|
+
};
|
|
5200
|
+
let scored = scoreEdge(evidence);
|
|
5201
|
+
let routedTier = _tier;
|
|
5202
|
+
let routedReason = _reason;
|
|
5203
|
+
const migrated = isMethod && !call.isConstructor && !evidence.resolvedBySameClass &&
|
|
5204
|
+
!evidence.typeQualifiedReceiver && !evidence.moduleOwnedPath && !evidence.extensionMethod &&
|
|
5205
|
+
(evidence.hasReceiverType || evidence.resolvedByReceiverHint || evidence.hasSingleOwnerEvidence);
|
|
5206
|
+
if (!_tier && migrated && collectAccount) {
|
|
5207
|
+
const checked = validateConfirmation(scored.provenance, evidence.facts.targets);
|
|
5208
|
+
if (checked.verdict === 'establishes-other') {
|
|
5209
|
+
recordExcluded(filePath, call.line, 'receiver-target-different', scored.provenance);
|
|
5210
|
+
continue;
|
|
5211
|
+
}
|
|
5212
|
+
if (checked.verdict !== 'establishes-target' && checked.verdict !== 'unsupported') {
|
|
5213
|
+
if (!collectAccount) continue;
|
|
5214
|
+
routedTier = TIER.UNVERIFIED;
|
|
5215
|
+
routedReason = evidence.hasSingleOwnerEvidence && !evidence.hasReceiverType
|
|
5216
|
+
? 'single-owner' : 'provenance-incomplete';
|
|
5217
|
+
const ranking = scoreEdge({ isUncertain: true,
|
|
5218
|
+
hasSingleOwnerEvidence: routedReason === 'single-owner' });
|
|
5219
|
+
scored = { ...ranking, provenance: scored.provenance };
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
4971
5222
|
// Family B contract field (fix #221): a bind/call/apply site reaches
|
|
4972
5223
|
// the target through Function.prototype indirection, not direct call
|
|
4973
5224
|
// syntax — label the edge calledAs:'bound'. Rename aliases keep their
|
|
4974
5225
|
// surface name (they describe the same slot and are rarer). Label
|
|
4975
5226
|
// only, computed at edge construction: routing logic never sees it.
|
|
4976
5227
|
const edgeCalledAs = calledAs || (call.boundCall ? 'bound' : undefined);
|
|
4977
|
-
if (
|
|
5228
|
+
if (routedTier) {
|
|
4978
5229
|
// Routed unverified entry — never competes with the main
|
|
4979
5230
|
// answer for maxResults/enrichLimit slots.
|
|
4980
5231
|
const base = {
|
|
@@ -4986,8 +5237,9 @@ function findCallers(index, name, options = {}) {
|
|
|
4986
5237
|
evidenceScore: scored.evidenceScore,
|
|
4987
5238
|
scoreKind: scored.scoreKind,
|
|
4988
5239
|
resolution: scored.resolution,
|
|
4989
|
-
|
|
4990
|
-
|
|
5240
|
+
...(collectAccount && { provenance: scored.provenance }),
|
|
5241
|
+
tier: routedTier,
|
|
5242
|
+
reason: routedReason,
|
|
4991
5243
|
...(_meta || {}),
|
|
4992
5244
|
isMethod: call.isMethod || false,
|
|
4993
5245
|
...(isFunctionReference && { isFunctionReference: true }),
|
|
@@ -5036,6 +5288,7 @@ function findCallers(index, name, options = {}) {
|
|
|
5036
5288
|
evidenceScore: scored.evidenceScore,
|
|
5037
5289
|
scoreKind: scored.scoreKind,
|
|
5038
5290
|
resolution: scored.resolution,
|
|
5291
|
+
...(collectAccount && { provenance: scored.provenance }),
|
|
5039
5292
|
...(tier && { tier }),
|
|
5040
5293
|
isMethod: call.isMethod || false,
|
|
5041
5294
|
...(isFunctionReference && { isFunctionReference: true }),
|
|
@@ -5074,6 +5327,7 @@ function findCallers(index, name, options = {}) {
|
|
|
5074
5327
|
evidenceScore: scored.evidenceScore,
|
|
5075
5328
|
scoreKind: scored.scoreKind,
|
|
5076
5329
|
resolution: scored.resolution,
|
|
5330
|
+
...(collectAccount && { provenance: scored.provenance }),
|
|
5077
5331
|
...(tier && { tier }),
|
|
5078
5332
|
});
|
|
5079
5333
|
enrichedCount++;
|
|
@@ -5211,6 +5465,30 @@ function findCallees(index, definition, options = {}) {
|
|
|
5211
5465
|
: [];
|
|
5212
5466
|
|
|
5213
5467
|
const callees = new Map(); // key -> { name, bindingId, count }
|
|
5468
|
+
const siteEvidence = new Map();
|
|
5469
|
+
const provenanceForSite = (siteId, target, reason) => {
|
|
5470
|
+
const record = siteEvidence.get(siteId) || { call: calls[siteId], evidence: {} };
|
|
5471
|
+
const call = record.call;
|
|
5472
|
+
const evidence = { ...record.evidence, ...(reason && { reason }) };
|
|
5473
|
+
evidence.facts = _confirmationFacts(index, def.file, call,
|
|
5474
|
+
target ? [target] : (index.symbols.get(call.name) || []), record.options);
|
|
5475
|
+
// A bindingId synthesized by receiver resolution is not a
|
|
5476
|
+
// lexical binding of the method token. Rank the actual site facts.
|
|
5477
|
+
if (call.isMethod && !call.receiverIsModule && !call.moduleOwnedPath &&
|
|
5478
|
+
!evidence.typeQualifiedReceiver && !evidence.extensionMethod) evidence.hasBindingId = false;
|
|
5479
|
+
if (!call.isMethod && target && !evidence.hasBindingId) {
|
|
5480
|
+
evidence.hasImportEvidence = (index.symbols.get(call.name) || []).length === 1 ||
|
|
5481
|
+
target.file === def.file || index.importGraph.get(def.file)?.has(target.file);
|
|
5482
|
+
}
|
|
5483
|
+
const scored = scoreEdge(evidence);
|
|
5484
|
+
return {
|
|
5485
|
+
...occurrenceIdentity(fileEntry?.relativePath || def.file, call, siteId),
|
|
5486
|
+
...(target && { targetDef: declarationIdentity(target) }),
|
|
5487
|
+
confidence: scored.confidence, evidenceScore: scored.evidenceScore,
|
|
5488
|
+
resolution: scored.resolution, scoreKind: scored.scoreKind,
|
|
5489
|
+
provenance: scored.provenance,
|
|
5490
|
+
};
|
|
5491
|
+
};
|
|
5214
5492
|
let selfAttrCalls = null; // collected for Python self.attr.method() resolution
|
|
5215
5493
|
let selfMethodCalls = null; // collected for Python self.method() resolution
|
|
5216
5494
|
|
|
@@ -5259,7 +5537,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5259
5537
|
};
|
|
5260
5538
|
// Retain an uncertain/unresolved call as a visible unverified callee
|
|
5261
5539
|
// entry (aggregated by name+reason) and claim its site.
|
|
5262
|
-
const noteUnverified = (siteId, call, reason, meta = {}) => {
|
|
5540
|
+
const noteUnverified = (siteId, call, reason, meta = {}, siteProof) => {
|
|
5263
5541
|
if (!collectAccount || claimedSiteIds.has(siteId)) return;
|
|
5264
5542
|
noteSite(siteId, 'unverified', reason, call);
|
|
5265
5543
|
const key = `${call.name}|${reason}|${meta.dispatchVia || ''}`;
|
|
@@ -5272,6 +5550,13 @@ function findCallees(index, definition, options = {}) {
|
|
|
5272
5550
|
}
|
|
5273
5551
|
entry.callCount++;
|
|
5274
5552
|
entry.sites.push(call.line);
|
|
5553
|
+
if (!entry.siteProvenance) entry.siteProvenance = [];
|
|
5554
|
+
const unverifiedScore = scoreEdge({ isUncertain: true, reason });
|
|
5555
|
+
entry.siteProvenance.push({ ...(siteProof || provenanceForSite(siteId, null, reason)),
|
|
5556
|
+
tier: TIER.UNVERIFIED, reason,
|
|
5557
|
+
confidence: unverifiedScore.confidence, evidenceScore: unverifiedScore.evidenceScore,
|
|
5558
|
+
resolution: reason, scoreKind: unverifiedScore.scoreKind });
|
|
5559
|
+
entry.provenance = summarizeProvenance(entry.siteProvenance);
|
|
5275
5560
|
};
|
|
5276
5561
|
// A statically selected base implementation is not the only runtime
|
|
5277
5562
|
// target in languages with virtual/structural dispatch. If a project
|
|
@@ -5353,7 +5638,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5353
5638
|
// already carry stronger identity and must not trigger a whole-file
|
|
5354
5639
|
// flow build merely because their parser record lacks receiverType.
|
|
5355
5640
|
const mayNeedDirectReceiverFlow = call =>
|
|
5356
|
-
call.isMethod && call.receiver && !call.receiverType
|
|
5641
|
+
call.isMethod && call.receiver && (!call.receiverType ||
|
|
5642
|
+
call.receiverTypeGuessed || call.receiverTypeSource === 'guess') &&
|
|
5357
5643
|
!call.receiverPatternShadow &&
|
|
5358
5644
|
!_isReservedReceiver(language, call.receiver) &&
|
|
5359
5645
|
!call.isPathCall && !call.receiverIsModule &&
|
|
@@ -5378,6 +5664,14 @@ function findCallees(index, definition, options = {}) {
|
|
|
5378
5664
|
for (let call of calls) {
|
|
5379
5665
|
siteOrdinal++;
|
|
5380
5666
|
const siteId = siteOrdinal;
|
|
5667
|
+
siteEvidence.set(siteId, {
|
|
5668
|
+
call,
|
|
5669
|
+
evidence: {
|
|
5670
|
+
hasReceiverType: !!call.receiverType,
|
|
5671
|
+
resolvedBySameClass: !!call.receiver && _isReservedReceiver(language, call.receiver),
|
|
5672
|
+
},
|
|
5673
|
+
options: { sameClass: !!call.receiver && _isReservedReceiver(language, call.receiver) },
|
|
5674
|
+
});
|
|
5381
5675
|
if (language === 'csharp') {
|
|
5382
5676
|
// fix #353: `Beta.Helper.Widget()` — namespace-qualified type
|
|
5383
5677
|
// receiver (see the findCallers twin).
|
|
@@ -5391,6 +5685,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5391
5685
|
call = {
|
|
5392
5686
|
...call,
|
|
5393
5687
|
receiverType: indexedType.type,
|
|
5688
|
+
receiverTypeSource: 'flow',
|
|
5689
|
+
receiverTypeEvidence: { source: 'flow', ...indexedType },
|
|
5394
5690
|
...(indexedType.fromFile && {
|
|
5395
5691
|
receiverTypeFlowFile: indexedType.fromFile,
|
|
5396
5692
|
}),
|
|
@@ -5417,6 +5713,55 @@ function findCallees(index, definition, options = {}) {
|
|
|
5417
5713
|
if (!isDirectMatch && !isNestedCallback) continue;
|
|
5418
5714
|
if (calleeAccount) calleeAccount.totalSites++;
|
|
5419
5715
|
|
|
5716
|
+
if (language === 'python' && call.isMethod && !call.receiverType) {
|
|
5717
|
+
const fixture = _pythonFixtureReceiverType(index, def.file, call);
|
|
5718
|
+
if (fixture) call = { ...call, receiverType: fixture.type,
|
|
5719
|
+
receiverTypeSource: 'fixture', receiverTypeEvidence: { source: 'fixture', ...fixture },
|
|
5720
|
+
...(fixture.fromFile && { receiverTypeFlowFile: fixture.fromFile }) };
|
|
5721
|
+
const external = !fixture && _pythonExternalFieldFlow(index, def.file, call);
|
|
5722
|
+
if (external) {
|
|
5723
|
+
call = { ...call, receiverTypeSource: 'flow',
|
|
5724
|
+
receiverTypeEvidence: { source: 'flow', externalFactory: external.proof } };
|
|
5725
|
+
const targets = (index.symbols.get(call.name) || []).filter(d => !NON_CALLABLE_TYPES.has(d.type));
|
|
5726
|
+
const facts = _confirmationFacts(index, def.file, call, targets);
|
|
5727
|
+
const provenance = scoreEdge({ possibleDispatch: true, facts }).provenance;
|
|
5728
|
+
noteUnverified(siteId, call, 'possible-dispatch', { dispatchVia: external.via, externalContract: true },
|
|
5729
|
+
{ ...occurrenceIdentity(fileEntry.relativePath, call, siteId), provenance });
|
|
5730
|
+
continue;
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
|
|
5734
|
+
if (language === 'rust' && _rustStandardWrapperMethod(index, def.file, call, allCalls)) {
|
|
5735
|
+
noteSite(siteId, 'external', 'standard-wrapper-method', call);
|
|
5736
|
+
continue;
|
|
5737
|
+
}
|
|
5738
|
+
if (language === 'rust' && call.isMethod && !call.receiverType &&
|
|
5739
|
+
call.receiverPatternVariant && (collectAccount || call.receiverPatternSourceCallStart != null)) {
|
|
5740
|
+
const item = _rustPatternReceiverType(index, fileEntry, def.file, call, foldCtx());
|
|
5741
|
+
if (item?.type) call = { ...call, receiverType: item.type,
|
|
5742
|
+
receiverTypeSource: 'flow', receiverTypeEvidence: { source: 'flow', ...item },
|
|
5743
|
+
receiverTypeFlowFile: item.fromFile };
|
|
5744
|
+
}
|
|
5745
|
+
if (language === 'rust' && call.isMethod &&
|
|
5746
|
+
call.receiver && !call.receiverType &&
|
|
5747
|
+
(call.receiverIterationCall || call.receiverIterationVariable)) {
|
|
5748
|
+
const item = _rustIterationReceiverType(index, fileEntry, def.file, call, foldCtx());
|
|
5749
|
+
if (item) call = { ...call, receiverType: item.type,
|
|
5750
|
+
receiverTypeSource: 'flow', receiverTypeEvidence: { source: 'flow', ...item },
|
|
5751
|
+
receiverTypeFlowFile: item.fromFile };
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
if (language === 'c' && call.isMethod) {
|
|
5755
|
+
noteUnverified(siteId, call, 'callable-field');
|
|
5756
|
+
continue;
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
if (language === 'csharp' && call.receiverType === 'dynamic') {
|
|
5760
|
+
if (_calleeZeroCandidateName(index, call)) noteSite(siteId, 'external', null, call);
|
|
5761
|
+
else noteUnverified(siteId, call, 'possible-dispatch', { dispatchVia: 'dynamic' });
|
|
5762
|
+
continue;
|
|
5763
|
+
}
|
|
5764
|
+
|
|
5420
5765
|
if (call.macroParameter) {
|
|
5421
5766
|
noteSite(siteId, 'excluded', 'macro-parameter', call);
|
|
5422
5767
|
continue;
|
|
@@ -5460,6 +5805,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5460
5805
|
const selected = _calleeOverloadSelect(
|
|
5461
5806
|
index, call, extensions, language);
|
|
5462
5807
|
if (selected.match) {
|
|
5808
|
+
if (collectAccount) siteEvidence.get(siteId).evidence.extensionMethod = true;
|
|
5463
5809
|
const match = selected.match;
|
|
5464
5810
|
const key = match.bindingId ||
|
|
5465
5811
|
`${match.file}:${match.startLine}:${call.name}`;
|
|
@@ -5530,6 +5876,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5530
5876
|
call = {
|
|
5531
5877
|
...call,
|
|
5532
5878
|
receiverType: indexedType.type,
|
|
5879
|
+
receiverTypeSource: 'flow',
|
|
5880
|
+
receiverTypeEvidence: { source: 'flow', ...indexedType },
|
|
5533
5881
|
...(indexedType.fromFile && {
|
|
5534
5882
|
receiverTypeFlowFile: indexedType.fromFile,
|
|
5535
5883
|
}),
|
|
@@ -5648,6 +5996,22 @@ function findCallees(index, definition, options = {}) {
|
|
|
5648
5996
|
}
|
|
5649
5997
|
}
|
|
5650
5998
|
|
|
5999
|
+
if (collectAccount) {
|
|
6000
|
+
const record = siteEvidence.get(siteId);
|
|
6001
|
+
record.call = call;
|
|
6002
|
+
if (directReceiverFlow?.type || fieldHopType) {
|
|
6003
|
+
const source = directReceiverFlow?.type ? 'flow' : 'field';
|
|
6004
|
+
record.evidence.hasReceiverType = true;
|
|
6005
|
+
Object.assign(record.options, {
|
|
6006
|
+
receiverType: directReceiverFlow?.type || fieldHopType,
|
|
6007
|
+
receiverTypeSource: source,
|
|
6008
|
+
originFile: directReceiverFlow?.fromFile || fieldHopInfo?.fromFile,
|
|
6009
|
+
receiverOrigin: { source, ...(directReceiverFlow || fieldHopInfo || {}),
|
|
6010
|
+
field: call.receiverField, rootType: call.receiverRootType },
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
|
|
5651
6015
|
if (fieldDispatchType) {
|
|
5652
6016
|
noteUnverified(siteId, call, 'possible-dispatch', {
|
|
5653
6017
|
dispatchVia: fieldDispatchType,
|
|
@@ -5667,6 +6031,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5667
6031
|
const selected = _calleeOverloadSelect(
|
|
5668
6032
|
index, extensionCall, extensions, language);
|
|
5669
6033
|
if (selected.match) {
|
|
6034
|
+
if (collectAccount) siteEvidence.get(siteId).evidence.extensionMethod = true;
|
|
5670
6035
|
const match = selected.match;
|
|
5671
6036
|
const key = match.bindingId ||
|
|
5672
6037
|
`${match.file}:${match.startLine}:${call.name}`;
|
|
@@ -5810,6 +6175,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5810
6175
|
!_isReservedReceiver(language, call.receiver) &&
|
|
5811
6176
|
!(localTypes && localTypes.has(call.receiver))) {
|
|
5812
6177
|
typeQual = _calleeTypeQualifiedReceiver(index, def, fileEntry, call, language);
|
|
6178
|
+
if (typeQual) siteEvidence.get(siteId).evidence.typeQualifiedReceiver = true;
|
|
5813
6179
|
}
|
|
5814
6180
|
|
|
5815
6181
|
// Package-qualified NON-method records (fix #268, chi-measured):
|
|
@@ -5954,7 +6320,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5954
6320
|
}
|
|
5955
6321
|
continue;
|
|
5956
6322
|
}
|
|
5957
|
-
const typeName = call.receiverType ||
|
|
6323
|
+
const typeName = directReceiverFlow?.type || call.receiverType || fieldHopType;
|
|
5958
6324
|
const symbols = index.symbols.get(call.name);
|
|
5959
6325
|
const qualifiedType = language === 'go' && call.receiverType
|
|
5960
6326
|
? _goQualifiedReceiverType(index, fileEntry,
|
|
@@ -5980,8 +6346,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5980
6346
|
const isCallableRT = (s) => !NON_CALLABLE_TYPES.has(s.type) ||
|
|
5981
6347
|
(s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
|
|
5982
6348
|
// Same-class overload selection by static call shape (fix #268)
|
|
5983
|
-
const receiverOriginFile =
|
|
5984
|
-
|
|
6349
|
+
const receiverOriginFile = directReceiverFlow?.fromFile ||
|
|
6350
|
+
call.receiverTypeFlowFile ||
|
|
5985
6351
|
fieldHopInfo?.fromFile ||
|
|
5986
6352
|
(call.receiverType
|
|
5987
6353
|
? _resolveFlowTypeOrigin(
|
|
@@ -6103,6 +6469,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
6103
6469
|
chained = _nominalChainedReceiverType(index, call, fileEntry, def.file);
|
|
6104
6470
|
}
|
|
6105
6471
|
if (chained?.type) {
|
|
6472
|
+
const record = siteEvidence.get(siteId);
|
|
6473
|
+
record.evidence.hasReceiverType = true;
|
|
6474
|
+
Object.assign(record.options, {
|
|
6475
|
+
receiverType: chained.type, receiverTypeSource: 'flow',
|
|
6476
|
+
receiverOrigin: { source: 'flow', ...chained }, originFile: chained.fromFile,
|
|
6477
|
+
});
|
|
6106
6478
|
const symbols = index.symbols.get(call.name);
|
|
6107
6479
|
const isCallableCh = (s) => !NON_CALLABLE_TYPES.has(s.type) ||
|
|
6108
6480
|
(s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
|
|
@@ -6849,21 +7221,28 @@ function findCallees(index, definition, options = {}) {
|
|
|
6849
7221
|
if (bound && routeVirtualOverride(siteId, call, def.className, bound)) continue;
|
|
6850
7222
|
}
|
|
6851
7223
|
|
|
6852
|
-
//
|
|
6853
|
-
//
|
|
6854
|
-
//
|
|
6855
|
-
// owner's method — `k.run()` where only Kit defines run. Without
|
|
6856
|
-
// it, trace trees stopped expanding at statically-resolvable
|
|
6857
|
-
// calls the caller direction confirms.
|
|
7224
|
+
// Receiver evidence for the former single-owner fallback (#355).
|
|
7225
|
+
// A name with one owner still needs an independent typed lookup;
|
|
7226
|
+
// unresolved values remain visible in the contract.
|
|
6858
7227
|
if (isUncertain && call.isMethod && call.receiver && !bindingResolved) {
|
|
6859
7228
|
const fm = mayNeedDirectReceiverFlow(call) ? flowMap() : null;
|
|
6860
7229
|
const flowEntry = fm ? _lookupReturnTypeFlow(fm, call) : undefined;
|
|
6861
7230
|
const owner = _calleeSingleOwnerMatch(index, def, fileEntry, call, effectiveName, language, flowEntry);
|
|
6862
7231
|
if (owner) {
|
|
7232
|
+
siteEvidence.get(siteId).evidence.hasSingleOwnerEvidence = true;
|
|
6863
7233
|
isUncertain = false;
|
|
6864
7234
|
bindingResolved = owner.bindingId;
|
|
6865
7235
|
calleeKey = owner.bindingId ||
|
|
6866
7236
|
`${owner.className || (owner.receiver || '').replace(/^\*/, '')}.${effectiveName}`;
|
|
7237
|
+
} else if (!call.receiverType && !flowEntry?.type && !fieldHopType &&
|
|
7238
|
+
!call.receiverExternalFlow && !call.receiverQualifiedFlow) {
|
|
7239
|
+
const owners = new Set((index.symbols.get(effectiveName) || [])
|
|
7240
|
+
.filter(d => !NON_CALLABLE_TYPES.has(d.type) && (d.className || d.receiver))
|
|
7241
|
+
.map(d => `${d.file}:${d.className || d.receiver}`));
|
|
7242
|
+
if (owners.size === 1) {
|
|
7243
|
+
uncertainReason = 'single-owner';
|
|
7244
|
+
siteEvidence.get(siteId).evidence.hasSingleOwnerEvidence = true;
|
|
7245
|
+
}
|
|
6867
7246
|
}
|
|
6868
7247
|
}
|
|
6869
7248
|
|
|
@@ -6891,6 +7270,59 @@ function findCallees(index, definition, options = {}) {
|
|
|
6891
7270
|
}
|
|
6892
7271
|
}
|
|
6893
7272
|
|
|
7273
|
+
// Legacy callers receive no uncertainty band. Drop an untyped
|
|
7274
|
+
// value receiver even if a same-file or unique-name lookup picked
|
|
7275
|
+
// a method; only actual receiver/module/type evidence survives.
|
|
7276
|
+
if (!collectAccount && call.isMethod && !call.receiverType &&
|
|
7277
|
+
!directReceiverFlow?.type && !fieldHopType &&
|
|
7278
|
+
!localTypes?.has(call.receiver) &&
|
|
7279
|
+
!siteEvidence.get(siteId).evidence.resolvedBySameClass &&
|
|
7280
|
+
!siteEvidence.get(siteId).evidence.typeQualifiedReceiver &&
|
|
7281
|
+
!call.receiverIsModule && !call.receiverModuleSpecifier &&
|
|
7282
|
+
!call.receiverModuleComposition && !call.moduleOwnedPath &&
|
|
7283
|
+
!call.isConstructor) continue;
|
|
7284
|
+
|
|
7285
|
+
if (collectAccount) {
|
|
7286
|
+
const record = siteEvidence.get(siteId);
|
|
7287
|
+
record.call = call;
|
|
7288
|
+
if (bindingResolved && !record.evidence.hasSingleOwnerEvidence) {
|
|
7289
|
+
record.evidence.hasBindingId = true;
|
|
7290
|
+
record.options.bindingId = bindingResolved;
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
if (collectAccount) {
|
|
7294
|
+
const record = siteEvidence.get(siteId);
|
|
7295
|
+
const valueReceiver = call.isMethod && !call.isConstructor &&
|
|
7296
|
+
!call.receiverIsModule && !call.receiverModuleSpecifier &&
|
|
7297
|
+
!call.receiverModuleComposition && !call.moduleOwnedPath &&
|
|
7298
|
+
!record.evidence.typeQualifiedReceiver && !record.evidence.extensionMethod &&
|
|
7299
|
+
!record.evidence.resolvedBySameClass;
|
|
7300
|
+
if (valueReceiver) {
|
|
7301
|
+
// Resolve and classify THIS occurrence before its grouping
|
|
7302
|
+
// key is chosen. A sibling site cannot lend its receiver
|
|
7303
|
+
// evidence or overload identity to this one.
|
|
7304
|
+
const proof = provenanceForSite(siteId, null);
|
|
7305
|
+
const facts = proof.provenance.facts;
|
|
7306
|
+
const checked = validateConfirmation(proof.provenance, facts.targets);
|
|
7307
|
+
const selected = checked.verdict === 'establishes-target' &&
|
|
7308
|
+
(index.symbols.get(effectiveName) || []).find(candidate =>
|
|
7309
|
+
sameDeclaration(declarationIdentity(candidate), facts.lookup?.selected));
|
|
7310
|
+
const reportOnly = checked.verdict === 'unsupported' && record.evidence.hasReceiverType;
|
|
7311
|
+
if (!selected && !reportOnly) {
|
|
7312
|
+
const reason = !record.evidence.hasReceiverType && facts.ownerCount === 1
|
|
7313
|
+
? 'single-owner' : 'provenance-incomplete';
|
|
7314
|
+
if (reason === 'single-owner') proof.provenance = scoreEdge({
|
|
7315
|
+
hasSingleOwnerEvidence: true, facts,
|
|
7316
|
+
}).provenance;
|
|
7317
|
+
noteUnverified(siteId, call, reason, {}, proof);
|
|
7318
|
+
continue;
|
|
7319
|
+
}
|
|
7320
|
+
if (selected) {
|
|
7321
|
+
bindingResolved = selected.bindingId;
|
|
7322
|
+
calleeKey = selected.bindingId || `${selected.file}:${selected.startLine}:${selected.name}`;
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
}
|
|
6894
7326
|
const existing = callees.get(calleeKey);
|
|
6895
7327
|
if (existing) {
|
|
6896
7328
|
existing.count += 1;
|
|
@@ -7254,24 +7686,61 @@ function findCallees(index, definition, options = {}) {
|
|
|
7254
7686
|
}
|
|
7255
7687
|
}
|
|
7256
7688
|
|
|
7257
|
-
|
|
7689
|
+
let calleeScored = scoreEdge({
|
|
7258
7690
|
hasBindingId: !!bindingId,
|
|
7259
7691
|
hasImportEvidence: !!bindingId || resolutionSymbols.length === 1 ||
|
|
7260
7692
|
(callee.file === def.file) || callerImportSet.has(callee.file),
|
|
7261
7693
|
isUncertain: false, // uncertain callees already filtered above
|
|
7262
7694
|
});
|
|
7695
|
+
const siteProvenance = collectAccount ? siteIds.map(siteId => ({
|
|
7696
|
+
...provenanceForSite(siteId, callee), tier: TIER.CONFIRMED,
|
|
7697
|
+
})).sort((a, b) => a.siteId - b.siteId).filter(site => {
|
|
7698
|
+
const record = siteEvidence.get(site.siteId);
|
|
7699
|
+
const valueReceiver = record.call.isMethod &&
|
|
7700
|
+
!record.call.receiverIsModule && !record.call.receiverModuleSpecifier &&
|
|
7701
|
+
!record.call.receiverModuleComposition && !record.evidence.typeQualifiedReceiver;
|
|
7702
|
+
const untypedValue = valueReceiver && !record.evidence.hasReceiverType &&
|
|
7703
|
+
!record.evidence.resolvedBySameClass;
|
|
7704
|
+
if (untypedValue && site.provenance.facts.ownerCount === 1) {
|
|
7705
|
+
record.evidence.hasSingleOwnerEvidence = true;
|
|
7706
|
+
}
|
|
7707
|
+
const migrated = valueReceiver && !record.call.isConstructor &&
|
|
7708
|
+
!record.evidence.resolvedBySameClass && !record.evidence.extensionMethod &&
|
|
7709
|
+
(record.evidence.hasReceiverType || record.evidence.hasSingleOwnerEvidence || untypedValue);
|
|
7710
|
+
if (!migrated) return true;
|
|
7711
|
+
const checked = validateConfirmation(site.provenance, [declarationIdentity(callee)]);
|
|
7712
|
+
if (checked.verdict === 'establishes-target' ||
|
|
7713
|
+
(checked.verdict === 'unsupported' && record.evidence.hasReceiverType)) return true;
|
|
7714
|
+
if (checked.verdict === 'establishes-other') {
|
|
7715
|
+
noteSite(site.siteId, 'excluded', 'receiver-target-different', record.call);
|
|
7716
|
+
(calleeAccount.excluded.evidence ||= []).push(site);
|
|
7717
|
+
} else {
|
|
7718
|
+
const reason = record.evidence.hasSingleOwnerEvidence && !record.evidence.hasReceiverType
|
|
7719
|
+
? 'single-owner' : 'provenance-incomplete';
|
|
7720
|
+
if (reason === 'single-owner') site.provenance = scoreEdge({
|
|
7721
|
+
hasSingleOwnerEvidence: true, facts: site.provenance.facts,
|
|
7722
|
+
}).provenance;
|
|
7723
|
+
noteUnverified(site.siteId, record.call, reason, {}, site);
|
|
7724
|
+
}
|
|
7725
|
+
return false;
|
|
7726
|
+
}) : null;
|
|
7727
|
+
if (collectAccount && siteProvenance.length === 0) continue;
|
|
7728
|
+
if (collectAccount) calleeScored = siteProvenance.reduce((weakest, site) =>
|
|
7729
|
+
site.evidenceScore < weakest.evidenceScore ? site : weakest);
|
|
7263
7730
|
claimSites('confirmed', null);
|
|
7264
7731
|
result.push({
|
|
7265
7732
|
...callee,
|
|
7266
|
-
callCount: count,
|
|
7267
|
-
weight: index.calculateWeight(count),
|
|
7733
|
+
callCount: collectAccount ? siteProvenance.length : count,
|
|
7734
|
+
weight: index.calculateWeight(collectAccount ? siteProvenance.length : count),
|
|
7268
7735
|
confidence: calleeScored.confidence,
|
|
7269
7736
|
evidenceScore: calleeScored.evidenceScore,
|
|
7270
7737
|
scoreKind: calleeScored.scoreKind,
|
|
7271
7738
|
resolution: calleeScored.resolution,
|
|
7272
7739
|
...(collectAccount && {
|
|
7273
7740
|
tier: TIER.CONFIRMED,
|
|
7274
|
-
|
|
7741
|
+
provenance: summarizeProvenance(siteProvenance),
|
|
7742
|
+
siteProvenance,
|
|
7743
|
+
sites: siteProvenance.map(site => site.line).sort((a, b) => a - b),
|
|
7275
7744
|
...(isFunctionReference && { functionReference: true }),
|
|
7276
7745
|
}),
|
|
7277
7746
|
});
|
|
@@ -7369,6 +7838,28 @@ function getInstanceAttributeTypes(index, filePath, className) {
|
|
|
7369
7838
|
'python', { filePath, consumerAwaited: false });
|
|
7370
7839
|
return result?.fromFile ? result.type : null;
|
|
7371
7840
|
},
|
|
7841
|
+
resolveImportedCallType(name) {
|
|
7842
|
+
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
7843
|
+
(binding.alias || binding.name) === name);
|
|
7844
|
+
if (bindings.length !== 1 || (fileEntry.moduleAssignedNames || []).includes(name) ||
|
|
7845
|
+
(index.symbols.get(name) || []).some(d => d.file === filePath)) return null;
|
|
7846
|
+
const binding = bindings[0];
|
|
7847
|
+
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) return null;
|
|
7848
|
+
const type = langModule.getBuiltinCallReturnType?.(binding.module, binding.name);
|
|
7849
|
+
return type ? { type, binding: { ...binding }, externalModule: binding.module } : null;
|
|
7850
|
+
},
|
|
7851
|
+
resolveExternalFactory(parts) {
|
|
7852
|
+
const name = parts[0];
|
|
7853
|
+
const bindings = (fileEntry.importBindings || []).filter(binding => (binding.alias || binding.name) === name);
|
|
7854
|
+
if (bindings.length !== 1 || (fileEntry.moduleAssignedNames || []).includes(name) ||
|
|
7855
|
+
(index.symbols.get(name) || []).some(d => d.file === filePath)) return null;
|
|
7856
|
+
const binding = bindings[0];
|
|
7857
|
+
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) return null;
|
|
7858
|
+
const importedFunction = ['from', 'relative'].includes(binding.kind);
|
|
7859
|
+
if (parts.length !== (importedFunction ? 1 : 2)) return null;
|
|
7860
|
+
return { module: binding.module, producer: importedFunction ? binding.name : parts[1],
|
|
7861
|
+
binding: { ...binding }, projectDeclarations: [], projectBindings: [], resolvedModule: null };
|
|
7862
|
+
},
|
|
7372
7863
|
});
|
|
7373
7864
|
index._attrTypeCache.set(filePath, fileCache);
|
|
7374
7865
|
} catch {
|
|
@@ -7616,6 +8107,30 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7616
8107
|
candidate.callStart === call.receiverCallStart &&
|
|
7617
8108
|
candidate.callEnd === call.receiverCallEnd);
|
|
7618
8109
|
|
|
8110
|
+
// A separate unwrap/expect assignment consumes the payload of the
|
|
8111
|
+
// receiver's declared standard wrapper. Its spelling alone is not a
|
|
8112
|
+
// contract: project-defined Result/Option classes remain ordinary
|
|
8113
|
+
// method producers. Reassignments are checked by the same nearest-
|
|
8114
|
+
// assignment lookup used for every other flow receiver.
|
|
8115
|
+
if (language === 'rust' && call.isMethod && call.receiver &&
|
|
8116
|
+
!call.receiverPatternShadow && !call.receiverFlowInvalidated &&
|
|
8117
|
+
((call.name === 'unwrap' && call.argCount === 0) ||
|
|
8118
|
+
(call.name === 'expect' && call.argCount === 1))) {
|
|
8119
|
+
const receiverFlow = _lookupReturnTypeFlow(map, call);
|
|
8120
|
+
const contract = receiverFlow?.rustWrapper;
|
|
8121
|
+
if (contract?.type && validateRustWrapperContract(contract)) {
|
|
8122
|
+
const scope = call.enclosingFunction ? `${call.enclosingFunction.startLine}` : '';
|
|
8123
|
+
const key = `${scope}:${call.assignedTo}`;
|
|
8124
|
+
if (!map.has(key)) map.set(key, []);
|
|
8125
|
+
map.get(key).push({ line: call.line, start: call.callStart,
|
|
8126
|
+
type: contract.type, fromFile: contract.fromFile,
|
|
8127
|
+
wrapperUnwrap: { method: call.name,
|
|
8128
|
+
producer: { line: receiverFlow.line, start: receiverFlow.start,
|
|
8129
|
+
type: receiverFlow.type, fromFile: receiverFlow.fromFile }, contract } });
|
|
8130
|
+
continue;
|
|
8131
|
+
}
|
|
8132
|
+
}
|
|
8133
|
+
|
|
7619
8134
|
// Rust's `collect::<Vec<Item>>()` turbofish fixes the concrete result
|
|
7620
8135
|
// and its item type at the call site. Preserve the item identity even
|
|
7621
8136
|
// though the outer collection is a standard-library type with no
|
|
@@ -7692,7 +8207,7 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7692
8207
|
? resolved[0]
|
|
7693
8208
|
: null;
|
|
7694
8209
|
}
|
|
7695
|
-
if (folded?.type || folded?.externalVia) {
|
|
8210
|
+
if (folded?.type || folded?.externalVia || folded?.rustWrapper) {
|
|
7696
8211
|
const scope = call.enclosingFunction
|
|
7697
8212
|
? `${call.enclosingFunction.startLine}` : '';
|
|
7698
8213
|
const key = `${scope}:${call.assignedTo}`;
|
|
@@ -7701,12 +8216,15 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7701
8216
|
map.get(key).push({ line: call.line, start: call.callStart,
|
|
7702
8217
|
...(folded.type && { type: folded.type }),
|
|
7703
8218
|
...(folded.fromFile && { fromFile: folded.fromFile }),
|
|
8219
|
+
...(folded.rustWrapper && { rustWrapper: folded.rustWrapper }),
|
|
8220
|
+
...(folded.rustReturnDeclaration && { rustReturnDeclaration: folded.rustReturnDeclaration }),
|
|
8221
|
+
...(folded.moduleProducer && { moduleProducer: folded.moduleProducer }),
|
|
7704
8222
|
...(folded.externalVia && { externalVia: folded.externalVia }),
|
|
7705
8223
|
...(folded.externalConcrete && { externalConcrete: true }) });
|
|
7706
8224
|
continue;
|
|
7707
8225
|
}
|
|
7708
8226
|
}
|
|
7709
|
-
let returnType, fromFile, selfClass, returnedFunctionResult, returnDefinition;
|
|
8227
|
+
let returnType, fromFile, selfClass, returnedFunctionResult, returnDefinition, moduleProducer;
|
|
7710
8228
|
const builtinCallReturn = !nominal && language === 'python'
|
|
7711
8229
|
? _pythonBuiltinCallReturnType(index, fileEntry, call) : null;
|
|
7712
8230
|
if (call.localValueCall && call.returnTypeHint) {
|
|
@@ -7732,10 +8250,14 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7732
8250
|
returnType = callableFlow.returnedFunctionResult;
|
|
7733
8251
|
fromFile = callableFlow.fromFile;
|
|
7734
8252
|
} else if (call.isMethod && call.receiverType &&
|
|
7735
|
-
!call.receiverTypeGuessed) {
|
|
8253
|
+
!call.receiverTypeGuessed && call.receiverTypeSource !== 'guess') {
|
|
7736
8254
|
const defs = index.symbols.get(call.name) || [];
|
|
7737
8255
|
if (nominal) {
|
|
7738
|
-
const
|
|
8256
|
+
const rustOwner = language === 'rust'
|
|
8257
|
+
? _rustFlowReceiverOrigin(index, filePath, call.receiverType, call.receiverTypeQualifier)
|
|
8258
|
+
: null;
|
|
8259
|
+
const matches = defs.filter(d => d.className === call.receiverType && d.returnType &&
|
|
8260
|
+
(language !== 'rust' || rustOwner?.fromFile));
|
|
7739
8261
|
if (matches.length > 0 && new Set(matches.map(d => d.returnType)).size === 1) {
|
|
7740
8262
|
returnType = matches[0].returnType;
|
|
7741
8263
|
fromFile = matches[0].file;
|
|
@@ -7846,6 +8368,15 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7846
8368
|
cls = next;
|
|
7847
8369
|
}
|
|
7848
8370
|
} else if (nominal && call.isMethod && call.isPathCall && call.receiver) {
|
|
8371
|
+
if (language === 'rust') {
|
|
8372
|
+
const producer = _rustModuleProducer(index, fileEntry, filePath, call);
|
|
8373
|
+
if (producer) {
|
|
8374
|
+
returnDefinition = producer.definition;
|
|
8375
|
+
returnType = returnDefinition.returnType;
|
|
8376
|
+
fromFile = returnDefinition.file;
|
|
8377
|
+
moduleProducer = producer.facts;
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
7849
8380
|
if (language === 'cpp') {
|
|
7850
8381
|
// C++ value-initialization through a qualified type name:
|
|
7851
8382
|
// `auto p = fmt::pipe()`. The portable tree-sitter AST uses
|
|
@@ -8327,6 +8858,18 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
8327
8858
|
}
|
|
8328
8859
|
const origin = _resolveFlowTypeOrigin(index, fromFile || filePath, parsed.name, parsed.qualifier);
|
|
8329
8860
|
if (!origin) {
|
|
8861
|
+
const wrapper = language === 'rust' && !call.assignedUnwrap &&
|
|
8862
|
+
_rustReturnWrapper(index, returnType, fromFile || filePath, selfClass,
|
|
8863
|
+
returnDefinition, [], true);
|
|
8864
|
+
if (wrapper && validateRustWrapperContract(wrapper, true)) {
|
|
8865
|
+
const scope = call.enclosingFunction ? `${call.enclosingFunction.startLine}` : '';
|
|
8866
|
+
const key = `${scope}:${call.assignedTo}`;
|
|
8867
|
+
if (!map) map = new Map();
|
|
8868
|
+
if (!map.has(key)) map.set(key, []);
|
|
8869
|
+
map.get(key).push({ line: call.line, start: call.callStart,
|
|
8870
|
+
rustWrapper: wrapper, rustReturnDeclaration: _rustReturnDeclaration(returnDefinition) });
|
|
8871
|
+
continue;
|
|
8872
|
+
}
|
|
8330
8873
|
// A project producer can return a package-qualified external
|
|
8331
8874
|
// Go type (`DefaultLogger(...) http.Handler`). That still
|
|
8332
8875
|
// gives compiler-grade provenance: the assigned receiver is
|
|
@@ -8373,6 +8916,12 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
8373
8916
|
if (!map.has(key)) map.set(key, []);
|
|
8374
8917
|
map.get(key).push({ line: call.line, start: call.callStart, type: typeName,
|
|
8375
8918
|
...(entryFromFile && { fromFile: entryFromFile }),
|
|
8919
|
+
...(moduleProducer && { moduleProducer }),
|
|
8920
|
+
...(language === 'rust' && returnDefinition && { rustReturnDeclaration: _rustReturnDeclaration(returnDefinition) }),
|
|
8921
|
+
...(language === 'rust' && !call.assignedUnwrap && {
|
|
8922
|
+
rustWrapper: _rustReturnWrapper(index, returnType, fromFile || filePath,
|
|
8923
|
+
selfClass, returnDefinition),
|
|
8924
|
+
}),
|
|
8376
8925
|
...(iteratorItemType && {
|
|
8377
8926
|
iteratorItemType,
|
|
8378
8927
|
iteratorItemFromFile,
|
|
@@ -8480,7 +9029,15 @@ function _lookupReturnTypeFlow(map, call) {
|
|
|
8480
9029
|
for (const e of entries) {
|
|
8481
9030
|
if (precedesCall(e) && laterThan(e, best)) best = e;
|
|
8482
9031
|
}
|
|
8483
|
-
if (best)
|
|
9032
|
+
if (best) {
|
|
9033
|
+
const binding = call.receiverTypeEvidence?.aliasBinding;
|
|
9034
|
+
// A non-call copy/rebinding is absent from the producer map.
|
|
9035
|
+
// Its newer AST declaration must still invalidate an older
|
|
9036
|
+
// factory payload with the same local variable name.
|
|
9037
|
+
if (binding?.target === call.receiver && Number.isInteger(binding.assignment?.start) &&
|
|
9038
|
+
(best.start == null || binding.assignment.start > best.start)) return undefined;
|
|
9039
|
+
return best.invalidated ? undefined : best;
|
|
9040
|
+
}
|
|
8484
9041
|
}
|
|
8485
9042
|
return undefined;
|
|
8486
9043
|
}
|
|
@@ -8555,6 +9112,59 @@ function _splitTopLevelGenericArgs(s) {
|
|
|
8555
9112
|
return out;
|
|
8556
9113
|
}
|
|
8557
9114
|
|
|
9115
|
+
function _rustReturnWrapper(index, text, file, selfClass, producer, projection = [], allowUnknownPayload = false) {
|
|
9116
|
+
if (!text || !file || !producer) return null;
|
|
9117
|
+
const contract = rustWrapperContract(index, text, file, {
|
|
9118
|
+
projection,
|
|
9119
|
+
allowUnknownPayload,
|
|
9120
|
+
parseType: value => _returnTypeNameNominal(value, 'rust', { selfClass }),
|
|
9121
|
+
resolveType: (context, name, qualifier) => _resolveFlowTypeOrigin(index, context, name, qualifier),
|
|
9122
|
+
});
|
|
9123
|
+
return contract && { ...contract, producer: { ...declarationIdentity(producer), returnType: producer.returnType } };
|
|
9124
|
+
}
|
|
9125
|
+
|
|
9126
|
+
function _rustReturnDeclaration(definition) {
|
|
9127
|
+
return { ...declarationIdentity(definition), file: definition.file,
|
|
9128
|
+
relativePath: definition.relativePath, returnType: definition.returnType };
|
|
9129
|
+
}
|
|
9130
|
+
|
|
9131
|
+
function _rustStandardWrapperMethod(index, file, call, calls) {
|
|
9132
|
+
if (!call.isMethod || !call.receiver || call.isPathCall ||
|
|
9133
|
+
call.receiverPatternShadow || call.receiverFlowInvalidated ||
|
|
9134
|
+
!['unwrap', 'expect'].includes(call.name)) return null;
|
|
9135
|
+
const flow = _lookupReturnTypeFlow(_buildReturnTypeFlowMap(index, file, calls), call);
|
|
9136
|
+
const contract = flow?.rustWrapper;
|
|
9137
|
+
if (contract && validateRustWrapperContract(contract, true)) {
|
|
9138
|
+
return { method: call.name, receiver: call.receiver, callKind: 'method',
|
|
9139
|
+
producer: { line: flow.line, start: flow.start }, contract };
|
|
9140
|
+
}
|
|
9141
|
+
// Ownership of a standard wrapper does not require knowing its payload:
|
|
9142
|
+
// `value: Result<T, E>` still uses Result's inherent unwrap. This negative
|
|
9143
|
+
// proof never types T or attributes its later methods to a project class.
|
|
9144
|
+
const type = call.receiverType;
|
|
9145
|
+
if (!['Result', 'Option'].includes(type) || call.receiverTypeSource !== 'annotation' ||
|
|
9146
|
+
call.receiverTypeEvidence?.source !== 'annotation' ||
|
|
9147
|
+
_isGenericParamReceiverType(index, file, call.line, type)) return null;
|
|
9148
|
+
const entry = index.files.get(file);
|
|
9149
|
+
const module = type === 'Result' ? 'result' : 'option';
|
|
9150
|
+
const standardPaths = [`std::${module}::${type}`, `core::${module}::${type}`];
|
|
9151
|
+
const qualifier = call.receiverTypeQualifier;
|
|
9152
|
+
const local = (index.symbols.get(type) || []).filter(d => d.file === file &&
|
|
9153
|
+
(IDENTITY_TYPE_KINDS.has(d.type) || d.type === 'type'));
|
|
9154
|
+
const bindings = (entry.importBindings || []).filter(b => (b.alias || b.name) === type);
|
|
9155
|
+
if (qualifier ? !standardPaths.includes(`${qualifier}::${type}`)
|
|
9156
|
+
: local.length || bindings.some(b => !standardPaths.includes(b.module))) return null;
|
|
9157
|
+
if ((entry.importBindings || []).some(b => b.name === '*' || b.module?.endsWith('::*'))) return null;
|
|
9158
|
+
const root = qualifier?.split('::')[0] || bindings[0]?.module.split('::')[0];
|
|
9159
|
+
if (root && ((index.symbols.get(root) || []).some(d => d.file === file) ||
|
|
9160
|
+
(entry.importBindings || []).some(b => (b.alias || b.name) === root))) return null;
|
|
9161
|
+
return { method: call.name, receiver: call.receiver, callKind: 'method',
|
|
9162
|
+
annotationReceiver: { type, origin: call.receiverTypeEvidence,
|
|
9163
|
+
qualifier: qualifier || null, localDeclarations: local.map(declarationIdentity),
|
|
9164
|
+
bindings, wildcardImports: [], rootDeclarations: [], rootBindings: [],
|
|
9165
|
+
genericParameter: false } };
|
|
9166
|
+
}
|
|
9167
|
+
|
|
8558
9168
|
function _splitTopLevelDelimiter(s, delimiter) {
|
|
8559
9169
|
const out = [];
|
|
8560
9170
|
let angle = 0, paren = 0, bracket = 0, brace = 0, cur = '';
|
|
@@ -8659,10 +9269,11 @@ function _returnTypeNameNominal(text, language, opts = {}) {
|
|
|
8659
9269
|
if (language === 'go') {
|
|
8660
9270
|
const qm = t.replace(/^\*+/, '').match(/^([A-Za-z_]\w*)\.([A-Za-z_]\w*)$/);
|
|
8661
9271
|
if (qm) qualifier = qm[1];
|
|
8662
|
-
} else if (language === 'cpp') {
|
|
8663
|
-
const stripped =
|
|
8664
|
-
.replace(
|
|
8665
|
-
.replace(/
|
|
9272
|
+
} else if (language === 'cpp' || language === 'rust') {
|
|
9273
|
+
const stripped = language === 'rust'
|
|
9274
|
+
? t.replace(/^&(?:\s*'\w+)?\s*/, '').replace(/^mut\s+/, '').trim()
|
|
9275
|
+
: t.replace(/\b(const|volatile|class|struct|typename)\b/g, '')
|
|
9276
|
+
.replace(/[*&]+/g, '').trim();
|
|
8666
9277
|
const genericStart = stripped.indexOf('<');
|
|
8667
9278
|
const head = genericStart >= 0
|
|
8668
9279
|
? stripped.slice(0, genericStart).trim() : stripped;
|
|
@@ -8807,6 +9418,35 @@ function _rustImportedTypeIdentity(index, filePath, localName) {
|
|
|
8807
9418
|
* trusted (a use/import of an external type can shadow it invisibly)
|
|
8808
9419
|
* - no project type def at all: external name — safe, can't conflate
|
|
8809
9420
|
*/
|
|
9421
|
+
function _rustFlowReceiverOrigin(index, file, name, qualifier) {
|
|
9422
|
+
const entry = index.files.get(file);
|
|
9423
|
+
// An annotation names a type in this module's scope. A same-directory
|
|
9424
|
+
// project Result/Option declaration does not shadow the standard prelude
|
|
9425
|
+
// of another module, nor does an unrelated imported file bind its names.
|
|
9426
|
+
const local = (index.symbols.get(name) || []).some(d => d.file === file &&
|
|
9427
|
+
(IDENTITY_TYPE_KINDS.has(d.type) || (d.type === 'type' && d.aliasOf)));
|
|
9428
|
+
const bound = (entry?.importBindings || []).some(b => (b.alias || b.name) === name);
|
|
9429
|
+
if (!qualifier && !local && !bound) return null;
|
|
9430
|
+
return _resolveFlowTypeOrigin(index, file, name, qualifier);
|
|
9431
|
+
}
|
|
9432
|
+
|
|
9433
|
+
function _rustSameNameAliasOrigin(index, definition, seen = new Set()) {
|
|
9434
|
+
if (!definition || seen.has(definition) || seen.size >= 8) return null;
|
|
9435
|
+
seen.add(definition);
|
|
9436
|
+
if (definition.type !== 'type') return { fromFile: definition.file };
|
|
9437
|
+
if (!definition.aliasTypeText) return null;
|
|
9438
|
+
const parsed = _returnTypeNameNominal(definition.aliasTypeText, 'rust');
|
|
9439
|
+
if (parsed?.name !== definition.name || !parsed.qualifier) return null;
|
|
9440
|
+
const relative = index.files.get(definition.file)?.moduleResolved?.[parsed.qualifier];
|
|
9441
|
+
const resolved = relative ? path.resolve(index.root, relative)
|
|
9442
|
+
: resolveRustImport(`${parsed.qualifier}::${parsed.name}`, definition.file, index.root) ||
|
|
9443
|
+
resolveRustImport(parsed.qualifier, definition.file, index.root);
|
|
9444
|
+
if (!resolved) return null;
|
|
9445
|
+
const targets = (index.symbols.get(parsed.name) || []).filter(d =>
|
|
9446
|
+
IDENTITY_TYPE_KINDS.has(d.type) && d.file === resolved);
|
|
9447
|
+
return targets.length === 1 ? _rustSameNameAliasOrigin(index, targets[0], seen) : null;
|
|
9448
|
+
}
|
|
9449
|
+
|
|
8810
9450
|
function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier = undefined) {
|
|
8811
9451
|
const opCache = index._opFlowTypeOriginCache;
|
|
8812
9452
|
const cacheKey = `${producerFile}\x00${typeName}\x00${qualifier || ''}`;
|
|
@@ -8863,6 +9503,13 @@ function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier = undef
|
|
|
8863
9503
|
const resolved = resolveRustImport(
|
|
8864
9504
|
`${qualifier}::${typeName}`, producerFile, index.root);
|
|
8865
9505
|
if (resolved) {
|
|
9506
|
+
const local = typeDefs.filter(d => d.file === resolved);
|
|
9507
|
+
if (local.length === 1) {
|
|
9508
|
+
const direct = local[0].type === 'type' && local[0].aliasOf === typeName
|
|
9509
|
+
? _rustSameNameAliasOrigin(index, local[0])
|
|
9510
|
+
: { fromFile: local[0].file };
|
|
9511
|
+
if (direct) return finish(direct);
|
|
9512
|
+
}
|
|
8866
9513
|
const reachable = typeDefs.filter(d =>
|
|
8867
9514
|
d.file === resolved ||
|
|
8868
9515
|
_importReaches(index, resolved, new Set([d.file])));
|
|
@@ -9052,22 +9699,7 @@ const JS_GLOBAL_RECEIVERS = new Set([
|
|
|
9052
9699
|
'crypto', 'performance', 'history', 'location', 'screen',
|
|
9053
9700
|
]);
|
|
9054
9701
|
|
|
9055
|
-
|
|
9056
|
-
'dict', 'list', 'set', 'tuple', 'str', 'int', 'float', 'bool', 'bytes', 'frozenset',
|
|
9057
|
-
'Mapping', 'MutableMapping', 'Sequence', 'MutableSequence',
|
|
9058
|
-
'Collection', 'Iterable', 'Iterator', 'KeysView', 'ValuesView', 'ItemsView',
|
|
9059
|
-
'IO', 'TextIO', 'BinaryIO', 'StringIO', 'BytesIO',
|
|
9060
|
-
'ZlibCompress', 'ZlibDecompress',
|
|
9061
|
-
'AsyncEvent',
|
|
9062
|
-
'Generator', 'AsyncGenerator', 'ContextManager', 'AsyncContextManager',
|
|
9063
|
-
'Array', 'String', 'Object', 'RegExp', 'Number', 'Boolean', 'Map', 'Set', 'Promise',
|
|
9064
|
-
'WeakMap', 'WeakSet',
|
|
9065
|
-
'string', 'number', 'boolean', 'bigint', 'symbol',
|
|
9066
|
-
'object', 'dynamic', 'decimal', 'byte', 'sbyte', 'char',
|
|
9067
|
-
'short', 'ushort', 'uint', 'long', 'ulong', 'double',
|
|
9068
|
-
'List', 'Dictionary', 'HashSet', 'Queue', 'Stack',
|
|
9069
|
-
'Task', 'ValueTask', 'IEnumerable', 'ICollection', 'IList',
|
|
9070
|
-
]);
|
|
9702
|
+
|
|
9071
9703
|
|
|
9072
9704
|
// Universal-contract method names (fix #265, hono-measured: 183 untyped
|
|
9073
9705
|
// `x.toString()` calls confirmed against JSXNode.toString via the single-
|
|
@@ -10959,6 +11591,60 @@ function _pythonBuiltinContractAllowed(index, fileEntry, moduleName) {
|
|
|
10959
11591
|
return !_unresolvedModuleIsGap(index, module);
|
|
10960
11592
|
}
|
|
10961
11593
|
|
|
11594
|
+
function _pythonExternalFieldFlow(index, file, call) {
|
|
11595
|
+
const field = call.receiverRoot === 'self' && (!call.receiverFields || call.receiverFields.length === 1)
|
|
11596
|
+
? call.receiverField : call.receiver?.startsWith('self.') && call.receiver.split('.').length === 2
|
|
11597
|
+
? call.receiver.slice(5) : null;
|
|
11598
|
+
if (!field || call.receiverFlowInvalidated) return null;
|
|
11599
|
+
const enclosing = index.findEnclosingFunction(file, call.line, true);
|
|
11600
|
+
if (!enclosing?.className) return null;
|
|
11601
|
+
const owner = (index.symbols.get(enclosing.className) || []).filter(d => d.file === file && d.type === 'class');
|
|
11602
|
+
if (owner.length !== 1) return null;
|
|
11603
|
+
const flow = getInstanceAttributeTypes(index, file, enclosing.className)?.externalFlows?.get(field);
|
|
11604
|
+
if (!flow) return null;
|
|
11605
|
+
const producer = flow.assignments[0];
|
|
11606
|
+
return { via: `${producer.module}.${producer.producer}${'()'.repeat(producer.depth)}`,
|
|
11607
|
+
proof: { ...flow, owner: declarationIdentity(owner[0]), enclosing: declarationIdentity(enclosing) } };
|
|
11608
|
+
}
|
|
11609
|
+
|
|
11610
|
+
function _pythonFixtureReceiverType(index, file, call) {
|
|
11611
|
+
const resolveType = (context, name, qualifier) => {
|
|
11612
|
+
const root = qualifier || name, entry = index.files.get(context);
|
|
11613
|
+
if ((entry.moduleAssignedNames || []).includes(root) ||
|
|
11614
|
+
(entry.importBindings || []).filter(b => (b.alias || b.name) === root).length > 1 ||
|
|
11615
|
+
(qualifier && (index.symbols.get(root) || []).some(d => d.file === context))) return null;
|
|
11616
|
+
return pythonFixtureType(index, context, qualifier ? `${qualifier}.${name}` : name,
|
|
11617
|
+
d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
11618
|
+
};
|
|
11619
|
+
return pythonFixtureReceiver(index, file, call, {
|
|
11620
|
+
resolveType,
|
|
11621
|
+
externalModule: (context, module) => _pythonBuiltinContractAllowed(index, index.files.get(context), module),
|
|
11622
|
+
field(type, context, field) {
|
|
11623
|
+
const owner = resolveType(context, type)?.declaration;
|
|
11624
|
+
if (!owner) return null;
|
|
11625
|
+
const members = (index.symbols.get(field) || []).filter(d => d.className === type && d.file === owner.file &&
|
|
11626
|
+
['property', 'getter', 'field'].includes(d.type));
|
|
11627
|
+
if (members.length !== 1) return null;
|
|
11628
|
+
const member = members[0], text = member.returnType || member.fieldType;
|
|
11629
|
+
const result = _structuralTypeExpression(text);
|
|
11630
|
+
if (!result || result.args.length) return null;
|
|
11631
|
+
const qualifier = result.qualifiedHead.includes('.')
|
|
11632
|
+
? result.qualifiedHead.slice(0, result.qualifiedHead.lastIndexOf('.')) : undefined;
|
|
11633
|
+
const next = resolveType(member.file, result.head, qualifier);
|
|
11634
|
+
const builtin = !qualifier && !next && isProvenanceBuiltinReceiver(result.head, 'python') &&
|
|
11635
|
+
!(index.files.get(member.file).importBindings || []).some(b => (b.alias || b.name) === result.head) &&
|
|
11636
|
+
!(index.files.get(member.file).moduleAssignedNames || []).includes(result.head) &&
|
|
11637
|
+
!(index.symbols.get(result.head) || []).some(d => d.file === member.file);
|
|
11638
|
+
if (!next && !builtin) return null;
|
|
11639
|
+
return { type: result.head, fromFile: next?.declaration.file,
|
|
11640
|
+
fact: { owner: declarationIdentity(owner), member: declarationIdentity(member), annotation: text,
|
|
11641
|
+
result: next ? declarationIdentity(next.declaration) : { builtin: result.head, language: 'python' },
|
|
11642
|
+
...(builtin && { builtinShadowDeclarations: [], builtinShadowBindings: [] }),
|
|
11643
|
+
importChain: next?.chain || [] } };
|
|
11644
|
+
},
|
|
11645
|
+
});
|
|
11646
|
+
}
|
|
11647
|
+
|
|
10962
11648
|
function _structuralImportedReceiverType(index, fileEntry, receiver) {
|
|
10963
11649
|
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
10964
11650
|
binding.name === receiver || binding.alias === receiver);
|
|
@@ -10981,7 +11667,7 @@ function _structuralImportedReceiverType(index, fileEntry, receiver) {
|
|
|
10981
11667
|
return { type: receiver, fromFile: definition.file };
|
|
10982
11668
|
}
|
|
10983
11669
|
|
|
10984
|
-
function _pythonBuiltinCallReturnType(index, fileEntry, call) {
|
|
11670
|
+
function _pythonBuiltinCallReturnType(index, fileEntry, call, evidence = null) {
|
|
10985
11671
|
if (fileEntry?.language !== 'python') return null;
|
|
10986
11672
|
const adapter = getLanguageAdapter('python');
|
|
10987
11673
|
if (typeof adapter?.getBuiltinCallReturnType !== 'function') return null;
|
|
@@ -10998,7 +11684,10 @@ function _pythonBuiltinCallReturnType(index, fileEntry, call) {
|
|
|
10998
11684
|
for (const binding of bindings) {
|
|
10999
11685
|
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) continue;
|
|
11000
11686
|
const type = adapter.getBuiltinCallReturnType(binding.module, call.name);
|
|
11001
|
-
if (type)
|
|
11687
|
+
if (type) {
|
|
11688
|
+
types.add(type);
|
|
11689
|
+
if (evidence) (evidence.bindings ||= []).push({ ...binding, returnType: type });
|
|
11690
|
+
}
|
|
11002
11691
|
}
|
|
11003
11692
|
return types.size === 1 ? [...types][0] : null;
|
|
11004
11693
|
}
|
|
@@ -11010,21 +11699,30 @@ function _pythonBuiltinFieldPathType(index, fileEntry, root, fields) {
|
|
|
11010
11699
|
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
11011
11700
|
binding.name === root || binding.alias === root);
|
|
11012
11701
|
const types = new Set();
|
|
11702
|
+
const contracts = [];
|
|
11013
11703
|
for (const binding of bindings) {
|
|
11014
11704
|
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) continue;
|
|
11015
11705
|
const type = adapter.getBuiltinFieldType(binding.module, fields[0]);
|
|
11016
|
-
if (type)
|
|
11706
|
+
if (type) {
|
|
11707
|
+
types.add(type);
|
|
11708
|
+
contracts.push({ ...binding, field: fields[0], type });
|
|
11709
|
+
}
|
|
11017
11710
|
}
|
|
11018
|
-
return types.size === 1 ? [...types][0] : null;
|
|
11711
|
+
return types.size === 1 ? { type: [...types][0], root, fields, bindings: contracts } : null;
|
|
11019
11712
|
}
|
|
11020
11713
|
|
|
11021
11714
|
function _pythonBuiltinChainedReceiverType(index, fileEntry, call, foldCtx) {
|
|
11022
11715
|
if (fileEntry?.language !== 'python') return null;
|
|
11023
11716
|
const producers = _chainedProducerRecords(foldCtx, call);
|
|
11024
11717
|
if (producers.length === 0) return null;
|
|
11025
|
-
const
|
|
11026
|
-
|
|
11027
|
-
|
|
11718
|
+
const witnesses = producers.map(producer => {
|
|
11719
|
+
const evidence = { name: producer.name, line: producer.line, site: producer.callSite };
|
|
11720
|
+
const type = _pythonBuiltinCallReturnType(index, fileEntry, producer, evidence);
|
|
11721
|
+
return { type, ...evidence };
|
|
11722
|
+
});
|
|
11723
|
+
const types = witnesses.map(witness => witness.type);
|
|
11724
|
+
return types.every(Boolean) && new Set(types).size === 1
|
|
11725
|
+
? { type: types[0], producers: witnesses } : null;
|
|
11028
11726
|
}
|
|
11029
11727
|
|
|
11030
11728
|
/**
|
|
@@ -11062,12 +11760,22 @@ function _calleeStructuralModuleRoute(index, fileEntry, call, language) {
|
|
|
11062
11760
|
}
|
|
11063
11761
|
|
|
11064
11762
|
function _calleeStructuralImportedNameRoute(index, fileEntry, call, language) {
|
|
11065
|
-
|
|
11763
|
+
let lookupName = call.resolvedName || call.name;
|
|
11066
11764
|
let bindings = (fileEntry?.importBindings || []).filter(b => b.name === lookupName);
|
|
11067
11765
|
if (call.resolvedName && bindings.some(b => b.alias)) {
|
|
11068
11766
|
const paired = bindings.filter(b => b.alias === call.name);
|
|
11069
11767
|
if (paired.length > 0) bindings = paired;
|
|
11070
11768
|
}
|
|
11769
|
+
// fix #357: a call spelled by a rename's LOCAL alias (Rust `use m::a as
|
|
11770
|
+
// b; b()`) pairs with exactly the binding carrying that alias, and the
|
|
11771
|
+
// exported item is the binding's ORIGINAL name.
|
|
11772
|
+
if (bindings.length === 0) {
|
|
11773
|
+
const aliased = (fileEntry?.importBindings || []).filter(b => b.alias === call.name);
|
|
11774
|
+
if (aliased.length > 0 && new Set(aliased.map(b => b.name)).size === 1) {
|
|
11775
|
+
bindings = aliased;
|
|
11776
|
+
lookupName = aliased[0].name;
|
|
11777
|
+
}
|
|
11778
|
+
}
|
|
11071
11779
|
if (bindings.length === 0) return null;
|
|
11072
11780
|
return _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, lookupName, true);
|
|
11073
11781
|
}
|
|
@@ -11127,6 +11835,7 @@ function _calleeExportDefinitions(index, startAbs, exposedName, language, call,
|
|
|
11127
11835
|
let unknown = false;
|
|
11128
11836
|
let frontier = [[startAbs, exposedName]];
|
|
11129
11837
|
const shapeMatches = d =>
|
|
11838
|
+
(language !== 'rust' || !(d.className || d.receiver)) &&
|
|
11130
11839
|
(!NON_CALLABLE_TYPES.has(d.type) ||
|
|
11131
11840
|
(call.isConstructor && d.type === 'class') ||
|
|
11132
11841
|
(langTraits(language)?.classesCallableWithoutNew && d.type === 'class')) &&
|
|
@@ -11264,6 +11973,19 @@ function _calleeGoPackageMatch(index, call, importModule) {
|
|
|
11264
11973
|
return bestMatch;
|
|
11265
11974
|
}
|
|
11266
11975
|
|
|
11976
|
+
function selectProvenanceOverload(index, call, candidates, language) {
|
|
11977
|
+
const remaining = new Set(candidates);
|
|
11978
|
+
const slots = [];
|
|
11979
|
+
for (const definition of candidates) {
|
|
11980
|
+
if (!remaining.has(definition)) continue;
|
|
11981
|
+
const group = _closeCallableIdentityGroup(index, [definition], candidates);
|
|
11982
|
+
const representative = group.find(d => !d.isSignature && !d.isDeclaration) || definition;
|
|
11983
|
+
slots.push(representative);
|
|
11984
|
+
for (const member of group) remaining.delete(member);
|
|
11985
|
+
}
|
|
11986
|
+
return _calleeOverloadSelect(index, call, slots, language);
|
|
11987
|
+
}
|
|
11988
|
+
|
|
11267
11989
|
function _calleeOverloadSelect(index, call, matches, language) {
|
|
11268
11990
|
if (matches.length === 0) return { match: null };
|
|
11269
11991
|
if (call.argCount == null || call.argSpread) {
|
|
@@ -11580,6 +12302,7 @@ function _declaredFieldType(
|
|
|
11580
12302
|
className: rootType,
|
|
11581
12303
|
fieldType,
|
|
11582
12304
|
file: owner.file,
|
|
12305
|
+
...(attrs.origins?.get(fieldName) && { fieldOrigin: attrs.origins.get(fieldName) }),
|
|
11583
12306
|
});
|
|
11584
12307
|
}
|
|
11585
12308
|
if (complete && inferred.length > 0) {
|
|
@@ -11642,6 +12365,7 @@ function _declaredFieldType(
|
|
|
11642
12365
|
const aliasBase = _pureAliasBase(index, typeName);
|
|
11643
12366
|
if (aliasBase) typeName = aliasBase;
|
|
11644
12367
|
if (info) {
|
|
12368
|
+
if (onType.length === 1 && onType[0].fieldOrigin) Object.assign(info, onType[0].fieldOrigin);
|
|
11645
12369
|
const origins = new Set();
|
|
11646
12370
|
const namespaces = new Set();
|
|
11647
12371
|
let complete = true;
|
|
@@ -12309,7 +13033,14 @@ function _calleeSingleOwnerMatch(index, def, fileEntry, call, name, language, fl
|
|
|
12309
13033
|
if (matchFe && callerFe &&
|
|
12310
13034
|
(isTestFile(matchFe.relativePath, matchFe.language) || isTestPath(matchFe.relativePath)) &&
|
|
12311
13035
|
!(isTestFile(callerFe.relativePath, callerFe.language) || isTestPath(callerFe.relativePath))) return null;
|
|
12312
|
-
|
|
13036
|
+
const facts = _confirmationFacts(index, def.file, call, [match], {
|
|
13037
|
+
...(flowEntry?.type && !call.receiverType && {
|
|
13038
|
+
receiverType: flowEntry.type, receiverTypeSource: 'flow',
|
|
13039
|
+
receiverOrigin: { source: 'flow', ...flowEntry }, originFile: flowEntry.fromFile,
|
|
13040
|
+
}),
|
|
13041
|
+
});
|
|
13042
|
+
return validateConfirmation({ facts }, facts.targets).verdict === 'establishes-target'
|
|
13043
|
+
? match : null;
|
|
12313
13044
|
}
|
|
12314
13045
|
|
|
12315
13046
|
const _CSHARP_TYPE_ALIASES = new Map([
|
|
@@ -15111,8 +15842,18 @@ function _methodReturnOnType(index, typeName, fromFile, methodName, language, op
|
|
|
15111
15842
|
const parsed = _returnTypeNameNominal(def.returnType, language, { selfClass: selfType });
|
|
15112
15843
|
if (!parsed) return null;
|
|
15113
15844
|
const origin = _resolveFlowTypeOrigin(index, def.file || opts.filePath, parsed.name, parsed.qualifier);
|
|
15114
|
-
|
|
15115
|
-
|
|
15845
|
+
const wrappers = language === 'rust' ? owned.map(candidate =>
|
|
15846
|
+
_rustReturnWrapper(index, candidate.returnType, candidate.file || opts.filePath,
|
|
15847
|
+
selfType, candidate, [], true)) : [];
|
|
15848
|
+
// Identical annotation text in different impl files can bind different
|
|
15849
|
+
// aliases. Every possible producer must agree on the payload identity.
|
|
15850
|
+
const rustWrapper = wrappers.length && wrappers.every(wrapper => wrapper &&
|
|
15851
|
+
wrapper.kind === wrappers[0].kind && wrapper.type === wrappers[0].type &&
|
|
15852
|
+
wrapper.fromFile === wrappers[0].fromFile) ? wrappers[0] : null;
|
|
15853
|
+
if (!origin && !rustWrapper) return null;
|
|
15854
|
+
return { ...(origin && { type: parsed.name }), ...(origin?.fromFile && { fromFile: origin.fromFile }),
|
|
15855
|
+
...(language === 'rust' && owned.length === 1 && { rustReturnDeclaration: _rustReturnDeclaration(def) }),
|
|
15856
|
+
...(rustWrapper && { rustWrapper }) };
|
|
15116
15857
|
}
|
|
15117
15858
|
// Structural: heads must agree; `this`/`Self` are the receiver's type
|
|
15118
15859
|
// (checked BEFORE the reject set — with a known owner they ARE identity);
|
|
@@ -15266,7 +16007,72 @@ function _rustPathIsKnownExternal(index, fileEntry, filePath, receiver, name) {
|
|
|
15266
16007
|
`${segments.join('::')}::${name}`, filePath, index.root);
|
|
15267
16008
|
}
|
|
15268
16009
|
|
|
15269
|
-
function
|
|
16010
|
+
/** A qualified free function belongs to its exact module, never an impl
|
|
16011
|
+
* whose type or method happens to share either path component. Re-exports
|
|
16012
|
+
* and inline modules abstain until their complete item path is available.
|
|
16013
|
+
*/
|
|
16014
|
+
function _rustModuleProducer(index, fileEntry, filePath, call) {
|
|
16015
|
+
if (!call.isPathCall || !call.receiver || call.receiverType || call.localShadow) return null;
|
|
16016
|
+
const parts = call.receiver.split('::');
|
|
16017
|
+
const root = parts[0];
|
|
16018
|
+
const bindings = (fileEntry.importBindings || []).filter(b => (b.alias || b.name) === root);
|
|
16019
|
+
const locals = (index.symbols.get(root) || []).filter(d => d.file === filePath);
|
|
16020
|
+
if (locals.some(d => d.type !== 'module') || bindings.length > 1) return null;
|
|
16021
|
+
const binding = bindings[0];
|
|
16022
|
+
const specifier = binding ? [binding.module, ...parts.slice(1)].join('::') : call.receiver;
|
|
16023
|
+
const terminal = specifier.split('::').at(-1);
|
|
16024
|
+
if (!terminal || ['Self', 'self', 'super', 'crate'].includes(terminal)) return null;
|
|
16025
|
+
const relative = fileEntry.moduleResolved?.[specifier];
|
|
16026
|
+
const destination = relative ? path.resolve(index.root, relative)
|
|
16027
|
+
: resolveRustImport(specifier, filePath, index.root);
|
|
16028
|
+
if (!destination || !index.files.has(destination) || destination === filePath) return null;
|
|
16029
|
+
const base = path.basename(destination, '.rs');
|
|
16030
|
+
if ((base === 'mod' ? path.basename(path.dirname(destination)) : base) !== terminal) return null;
|
|
16031
|
+
const candidates = (index.symbols.get(call.name) || []).filter(d => d.file === destination &&
|
|
16032
|
+
!NON_CALLABLE_TYPES.has(d.type) && !d.className && !d.receiver && !d.namespace);
|
|
16033
|
+
if (candidates.length !== 1 || !candidates[0].returnType) return null;
|
|
16034
|
+
const definition = candidates[0];
|
|
16035
|
+
if ((index.files.get(destination).symbols || []).some(d => d.type === 'module' &&
|
|
16036
|
+
d.startLine < definition.startLine && d.endLine >= definition.endLine)) return null;
|
|
16037
|
+
return { definition, facts: {
|
|
16038
|
+
call: { file: fileEntry.relativePath, line: call.line, start: call.callStart,
|
|
16039
|
+
end: call.callEnd, receiver: call.receiver, name: call.name },
|
|
16040
|
+
binding: binding || null, module: { specifier, file: path.relative(index.root, destination) },
|
|
16041
|
+
declaration: { ...declarationIdentity(definition), returnType: definition.returnType },
|
|
16042
|
+
} };
|
|
16043
|
+
}
|
|
16044
|
+
|
|
16045
|
+
function _rustPatternReceiverType(index, fileEntry, filePath, record, ctx) {
|
|
16046
|
+
if (['Some', 'Ok'].includes(record.receiverPatternVariant) && !record.receiverPatternOwner &&
|
|
16047
|
+
(record.receiverPatternIndex || 0) === 0 &&
|
|
16048
|
+
(record.receiverPatternSourceCallStart != null || record.receiverPatternSourceVariable)) {
|
|
16049
|
+
if (!ctx) {
|
|
16050
|
+
const records = index.getCachedCalls(filePath);
|
|
16051
|
+
ctx = { records, memo: new Map(), visiting: new Set(),
|
|
16052
|
+
getFlowMap: () => _buildReturnTypeFlowMap(index, filePath, records) };
|
|
16053
|
+
}
|
|
16054
|
+
const shadow = (index.symbols.get(record.receiverPatternVariant) || []).some(d => d.file === filePath) ||
|
|
16055
|
+
(fileEntry.importBindings || []).some(b => (b.alias || b.name) === record.receiverPatternVariant || b.name === '*');
|
|
16056
|
+
const sources = record.receiverPatternSourceVariable
|
|
16057
|
+
? [_lookupReturnTypeFlow(ctx.getFlowMap(), { ...record, receiver: record.receiverPatternSourceVariable,
|
|
16058
|
+
receiverPatternShadow: false })]
|
|
16059
|
+
: ctx.records.filter(source => source.callStart === record.receiverPatternSourceCallStart &&
|
|
16060
|
+
source.callEnd === record.receiverPatternSourceCallEnd).map(source =>
|
|
16061
|
+
_typeOfCallResultFold(index, fileEntry, filePath, source, ctx));
|
|
16062
|
+
const producer = sources.length === 1 && sources[0]?.rustReturnDeclaration;
|
|
16063
|
+
const contract = !shadow && producer && _rustReturnWrapper(index, producer.returnType, producer.file,
|
|
16064
|
+
producer.className, producer, record.receiverPatternProjection || []);
|
|
16065
|
+
if (contract && validateRustWrapperContract(contract) &&
|
|
16066
|
+
contract.kind === (record.receiverPatternVariant === 'Some' ? 'Option' : 'Result')) {
|
|
16067
|
+
return { type: contract.type, fromFile: contract.fromFile,
|
|
16068
|
+
wrapperPattern: { variant: record.receiverPatternVariant, contract,
|
|
16069
|
+
source: { start: record.receiverPatternSourceCallStart, end: record.receiverPatternSourceCallEnd,
|
|
16070
|
+
variable: record.receiverPatternSourceVariable }, shadowDeclarations: [], shadowBindings: [] } };
|
|
16071
|
+
}
|
|
16072
|
+
// A tuple payload cannot be treated as its outer type by the older
|
|
16073
|
+
// whole-payload path below.
|
|
16074
|
+
if (record.receiverPatternProjection?.length) return null;
|
|
16075
|
+
}
|
|
15270
16076
|
let variants = (index.symbols.get(record.receiverPatternVariant) || [])
|
|
15271
16077
|
.filter(definition => definition.type === 'variant');
|
|
15272
16078
|
if (record.receiverPatternOwner) {
|
|
@@ -15356,7 +16162,15 @@ const _RUST_ITERATOR_ITEM_CALLBACKS = new Set([
|
|
|
15356
16162
|
]);
|
|
15357
16163
|
|
|
15358
16164
|
function _rustRecordReceiverType(index, fileEntry, filePath, record, ctx) {
|
|
15359
|
-
if (record.
|
|
16165
|
+
if (record.receiver === 'self') {
|
|
16166
|
+
const enclosing = index.findEnclosingFunction(filePath, record.line, true);
|
|
16167
|
+
if (enclosing?.className) {
|
|
16168
|
+
const origin = _resolveFlowTypeOrigin(index, filePath, enclosing.className);
|
|
16169
|
+
if (origin?.fromFile) return { type: enclosing.className, fromFile: origin.fromFile };
|
|
16170
|
+
}
|
|
16171
|
+
}
|
|
16172
|
+
if (record.receiverType && !record.receiverIsChainRoot &&
|
|
16173
|
+
!record.receiverTypeGuessed && record.receiverTypeSource !== 'guess') {
|
|
15360
16174
|
const origin = _resolveFlowTypeOrigin(
|
|
15361
16175
|
index, filePath, record.receiverType, record.receiverTypeQualifier);
|
|
15362
16176
|
if (origin?.fromFile) {
|
|
@@ -15383,9 +16197,13 @@ function _rustRecordReceiverType(index, fileEntry, filePath, record, ctx) {
|
|
|
15383
16197
|
}
|
|
15384
16198
|
if (record.receiverPatternVariant) {
|
|
15385
16199
|
const pattern = _rustPatternReceiverType(
|
|
15386
|
-
index, fileEntry, filePath, record);
|
|
16200
|
+
index, fileEntry, filePath, record, ctx);
|
|
15387
16201
|
if (pattern) return pattern;
|
|
15388
16202
|
}
|
|
16203
|
+
if (record.enclosingFunction?.closureParameterNames?.includes(record.receiver)) {
|
|
16204
|
+
const parameter = _rustClosureReceiverType(index, fileEntry, filePath, record, ctx);
|
|
16205
|
+
if (parameter) return parameter;
|
|
16206
|
+
}
|
|
15389
16207
|
if (record.receiver && !record.receiverIsChainRoot &&
|
|
15390
16208
|
!record.receiverPatternShadow) {
|
|
15391
16209
|
const flow = _lookupReturnTypeFlow(ctx.getFlowMap(), record);
|
|
@@ -15485,6 +16303,35 @@ const _RUST_ITERATOR_ITEM_PRESERVING = new Set([
|
|
|
15485
16303
|
'skip', 'skip_while', 'step_by', 'take', 'take_while',
|
|
15486
16304
|
]);
|
|
15487
16305
|
|
|
16306
|
+
function _rustIterationReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
16307
|
+
let items = [];
|
|
16308
|
+
let producers = [];
|
|
16309
|
+
if (call.receiverIterationVariable) {
|
|
16310
|
+
const flow = _lookupReturnTypeFlow(ctx.getFlowMap(), {
|
|
16311
|
+
...call, receiver: call.receiverIterationVariable,
|
|
16312
|
+
});
|
|
16313
|
+
if (flow?.iteratorItemType) {
|
|
16314
|
+
items = [{ type: flow.iteratorItemType, fromFile: flow.iteratorItemFromFile }];
|
|
16315
|
+
producers = [{ ...flow }];
|
|
16316
|
+
}
|
|
16317
|
+
} else {
|
|
16318
|
+
const sources = _chainedProducerRecords(ctx, {
|
|
16319
|
+
receiverCall: call.receiverIterationCall,
|
|
16320
|
+
receiverCallIsMethod: call.receiverIterationCallIsMethod,
|
|
16321
|
+
receiverCallLine: call.receiverIterationCallLine,
|
|
16322
|
+
receiverCallStart: call.receiverIterationCallStart,
|
|
16323
|
+
receiverCallEnd: call.receiverIterationCallEnd,
|
|
16324
|
+
});
|
|
16325
|
+
items = sources.map(source => _rustIteratorOutputItemType(index, fileEntry, filePath, source, ctx));
|
|
16326
|
+
producers = sources.map(source => ({ name: source.name,
|
|
16327
|
+
site: occurrenceIdentity(fileEntry.relativePath, source) }));
|
|
16328
|
+
}
|
|
16329
|
+
if (!items.length || items.some(item => !item?.fromFile) ||
|
|
16330
|
+
new Set(items.map(item => item.type)).size !== 1 ||
|
|
16331
|
+
new Set(items.map(item => item.fromFile)).size !== 1) return null;
|
|
16332
|
+
return { ...items[0], alternatives: items, producers };
|
|
16333
|
+
}
|
|
16334
|
+
|
|
15488
16335
|
function _rustIteratorOutputItemType(index, fileEntry, filePath, source, ctx, visiting = new Set()) {
|
|
15489
16336
|
if (!source || visiting.has(source)) return null;
|
|
15490
16337
|
visiting.add(source);
|
|
@@ -15731,6 +16578,16 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
15731
16578
|
externalConcrete: true,
|
|
15732
16579
|
};
|
|
15733
16580
|
}
|
|
16581
|
+
const module = language === 'rust' ? _rustModuleProducer(index, fileEntry, filePath, record) : null;
|
|
16582
|
+
if (module) {
|
|
16583
|
+
const producer = module.definition;
|
|
16584
|
+
const parsed = _returnTypeNameNominal(producer.returnType, language);
|
|
16585
|
+
const origin = parsed && _resolveFlowTypeOrigin(index, producer.file, parsed.name, parsed.qualifier);
|
|
16586
|
+
if (!origin) return null;
|
|
16587
|
+
return { type: parsed.name, fromFile: origin.fromFile, moduleProducer: module.facts,
|
|
16588
|
+
rustReturnDeclaration: _rustReturnDeclaration(producer),
|
|
16589
|
+
rustWrapper: _rustReturnWrapper(index, producer.returnType, producer.file, null, producer) };
|
|
16590
|
+
}
|
|
15734
16591
|
const segs = String(record.receiver).split('::');
|
|
15735
16592
|
let seg = segs.pop();
|
|
15736
16593
|
if (seg === 'Self') {
|
|
@@ -15872,11 +16729,15 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
15872
16729
|
if (record.isMethod) {
|
|
15873
16730
|
let rt = _goBuiltinChainedReceiverType(
|
|
15874
16731
|
index, fileEntry, filePath, record);
|
|
15875
|
-
if (record.receiverType && !record.receiverIsChainRoot
|
|
15876
|
-
|
|
16732
|
+
if (record.receiverType && !record.receiverIsChainRoot &&
|
|
16733
|
+
!record.receiverTypeGuessed && record.receiverTypeSource !== 'guess') {
|
|
16734
|
+
const origin = language === 'rust'
|
|
16735
|
+
? _rustFlowReceiverOrigin(index, filePath, record.receiverType, record.receiverTypeQualifier)
|
|
16736
|
+
: nominal
|
|
15877
16737
|
? _resolveFlowTypeOrigin(index, filePath, record.receiverType,
|
|
15878
16738
|
record.receiverTypeQualifier)
|
|
15879
16739
|
: null;
|
|
16740
|
+
if (language === 'rust' && !origin?.fromFile) return null;
|
|
15880
16741
|
rt = {
|
|
15881
16742
|
type: record.receiverType,
|
|
15882
16743
|
...(origin?.fromFile && { fromFile: origin.fromFile }),
|
|
@@ -16073,7 +16934,8 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
16073
16934
|
if (!parsed) return null;
|
|
16074
16935
|
const origin = _resolveFlowTypeOrigin(index, chosen.file || filePath, parsed.name, parsed.qualifier);
|
|
16075
16936
|
if (!origin) return null;
|
|
16076
|
-
return { type: parsed.name, ...(origin.fromFile && { fromFile: origin.fromFile })
|
|
16937
|
+
return { type: parsed.name, ...(origin.fromFile && { fromFile: origin.fromFile }),
|
|
16938
|
+
...(language === 'rust' && chosen.file === filePath && { rustReturnDeclaration: _rustReturnDeclaration(chosen) }) };
|
|
16077
16939
|
}
|
|
16078
16940
|
if (language === 'python' && !consumerAwaited && chosen.isAsync) return null;
|
|
16079
16941
|
let head = _structuralTypeHead(chosen.returnType, {
|
|
@@ -16167,6 +17029,9 @@ function _foldChainedReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
16167
17029
|
const typeTexts = new Set(results.map(r => r.typeText));
|
|
16168
17030
|
let result = {
|
|
16169
17031
|
type: results[0].type,
|
|
17032
|
+
...(results.length === 1 && results[0].moduleProducer && {
|
|
17033
|
+
moduleProducer: results[0].moduleProducer,
|
|
17034
|
+
}),
|
|
16170
17035
|
...(typeTexts.size === 1 && results[0].typeText && {
|
|
16171
17036
|
typeText: results[0].typeText,
|
|
16172
17037
|
}),
|
|
@@ -16317,4 +17182,4 @@ function findCallbackUsages(index, name) {
|
|
|
16317
17182
|
return usages;
|
|
16318
17183
|
}
|
|
16319
17184
|
|
|
16320
|
-
module.exports = { _unresolvedModuleIsGap, _importReaches, _sameNominalPackageDir, getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|
|
17185
|
+
module.exports = { isProvenanceBuiltinReceiver, provenanceParameterIdentity: _overloadTypeIdentity, selectProvenanceOverload, _unresolvedModuleIsGap, _importReaches, _sameNominalPackageDir, getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|