ucn 5.3.4 → 5.3.5
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 +4 -1
- package/core/callers.js +939 -122
- package/core/confidence.js +26 -14
- package/core/imports.js +18 -4
- package/core/index-ir.js +1 -1
- 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 +173 -73
- 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
|
}
|
|
@@ -3036,6 +3147,13 @@ function findCallers(index, name, options = {}) {
|
|
|
3036
3147
|
continue;
|
|
3037
3148
|
}
|
|
3038
3149
|
const targetHasClass = targetDefs.some(d => d.className);
|
|
3150
|
+
if (call.isMethod && !targetHasClass && fileEntry.language === 'c') {
|
|
3151
|
+
// C member calls invoke function-pointer fields. The
|
|
3152
|
+
// pointer can hold the pinned free function; member
|
|
3153
|
+
// syntax alone is neither binding nor exclusion proof.
|
|
3154
|
+
routeUnverified(filePath, fileEntry, call, 'callable-field', calledAs);
|
|
3155
|
+
continue;
|
|
3156
|
+
}
|
|
3039
3157
|
if (call.isMethod && !targetHasClass) {
|
|
3040
3158
|
// Method call but target is a standalone function — skip
|
|
3041
3159
|
recordExcluded(filePath, call.line, 'method-kind-mismatch');
|
|
@@ -3227,7 +3345,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3227
3345
|
isUncertain = true;
|
|
3228
3346
|
typeMismatch = true;
|
|
3229
3347
|
if (collectAccount) {
|
|
3230
|
-
|
|
3348
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
3231
3349
|
continue;
|
|
3232
3350
|
}
|
|
3233
3351
|
if (!options.includeUncertain) {
|
|
@@ -3303,7 +3421,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3303
3421
|
isUncertain = true;
|
|
3304
3422
|
typeMismatch = true;
|
|
3305
3423
|
if (collectAccount) {
|
|
3306
|
-
|
|
3424
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
3307
3425
|
continue;
|
|
3308
3426
|
}
|
|
3309
3427
|
if (!options.includeUncertain) {
|
|
@@ -3312,6 +3430,14 @@ function findCallers(index, name, options = {}) {
|
|
|
3312
3430
|
}
|
|
3313
3431
|
}
|
|
3314
3432
|
|
|
3433
|
+
// C# dynamic is an unresolved runtime receiver, never a
|
|
3434
|
+
// concrete foreign type that can disprove a project edge.
|
|
3435
|
+
if (fileEntry.language === 'csharp' && call.receiverType === 'dynamic') {
|
|
3436
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs,
|
|
3437
|
+
{ dispatchVia: 'dynamic' });
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3315
3441
|
// Receiver-class disambiguation:
|
|
3316
3442
|
// When the target definition has a class/receiver type, filter callers
|
|
3317
3443
|
// whose receiverType is known to be a different type.
|
|
@@ -3571,7 +3697,8 @@ function findCallers(index, name, options = {}) {
|
|
|
3571
3697
|
// Not evidence against — visible possible-dispatch.
|
|
3572
3698
|
// Go struct embedding binds statically and stays
|
|
3573
3699
|
// excluded.
|
|
3574
|
-
if (_dispatchCapableSupertype(index, fileEntry.language, knownType, targetDefs, definitions)
|
|
3700
|
+
if (_dispatchCapableSupertype(index, fileEntry.language, knownType, targetDefs, definitions) ||
|
|
3701
|
+
(!knownTypeHasProjectIdentity && externalContractTarget()?.via === knownType)) {
|
|
3575
3702
|
const externalContract = externalContractTarget();
|
|
3576
3703
|
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
3577
3704
|
dispatchVia: knownType,
|
|
@@ -3582,7 +3709,11 @@ function findCallers(index, name, options = {}) {
|
|
|
3582
3709
|
});
|
|
3583
3710
|
continue;
|
|
3584
3711
|
}
|
|
3585
|
-
|
|
3712
|
+
excludeReceiver(filePath, fileEntry, call, calledAs, fieldHopType && !call.receiverType ? {
|
|
3713
|
+
receiverType: fieldHopType, receiverTypeSource: 'field',
|
|
3714
|
+
receiverOrigin: { source: 'field', root: call.receiverRoot,
|
|
3715
|
+
rootType: call.receiverRootType, field: call.receiverField },
|
|
3716
|
+
} : {});
|
|
3586
3717
|
continue;
|
|
3587
3718
|
}
|
|
3588
3719
|
if (!options.includeUncertain) {
|
|
@@ -3695,7 +3826,7 @@ function findCallers(index, name, options = {}) {
|
|
|
3695
3826
|
isUncertain = true;
|
|
3696
3827
|
typeMismatch = true;
|
|
3697
3828
|
if (collectAccount) {
|
|
3698
|
-
|
|
3829
|
+
excludeReceiver(filePath, fileEntry, call, calledAs);
|
|
3699
3830
|
continue;
|
|
3700
3831
|
}
|
|
3701
3832
|
if (!options.includeUncertain) {
|
|
@@ -3736,6 +3867,10 @@ function findCallers(index, name, options = {}) {
|
|
|
3736
3867
|
}
|
|
3737
3868
|
inferredMatch = true;
|
|
3738
3869
|
nominalInferredMatch = true;
|
|
3870
|
+
resolvedReceiverFacts = {
|
|
3871
|
+
receiverType: typeReceiver, receiverTypeSource: 'type-qualified',
|
|
3872
|
+
receiverOrigin: { source: 'type-qualified', site: occurrenceIdentity(fileEntry.relativePath, call) },
|
|
3873
|
+
};
|
|
3739
3874
|
}
|
|
3740
3875
|
// Still no type — fall back to receiver name matching when
|
|
3741
3876
|
// multiple defs exist. A field-declared interface/trait type
|
|
@@ -4154,6 +4289,13 @@ function findCallers(index, name, options = {}) {
|
|
|
4154
4289
|
continue;
|
|
4155
4290
|
}
|
|
4156
4291
|
}
|
|
4292
|
+
if (typeQualifiedReceiver) {
|
|
4293
|
+
resolvedReceiverFacts = {
|
|
4294
|
+
receiverType: receiverName, receiverTypeSource: 'type-qualified',
|
|
4295
|
+
receiverOrigin: { source: 'type-qualified', site: occurrenceIdentity(fileEntry.relativePath, call) },
|
|
4296
|
+
...(aliasResolvedFile && { originFile: aliasResolvedFile }),
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4157
4299
|
if (!typeQualifiedReceiver) {
|
|
4158
4300
|
// External-producer receiver (fix #220): the variable
|
|
4159
4301
|
// was assigned from a call into an external package
|
|
@@ -4344,6 +4486,15 @@ function findCallers(index, name, options = {}) {
|
|
|
4344
4486
|
}
|
|
4345
4487
|
}
|
|
4346
4488
|
}
|
|
4489
|
+
// Module ownership is evidence for an exported item,
|
|
4490
|
+
// not for a method nested inside that module's type.
|
|
4491
|
+
// A same-named free function and method can share the
|
|
4492
|
+
// target file (itertools::peek_nth vs PeekNth::peek_nth).
|
|
4493
|
+
if (call.moduleOwnedPath && fileEntry.language === 'rust' &&
|
|
4494
|
+
targetDefs2.every(d => d.className || d.receiver)) {
|
|
4495
|
+
routeUnverified(filePath, fileEntry, call, 'provenance-incomplete', calledAs);
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4347
4498
|
const knownDispatchType = call.receiverType || fieldHopType || fieldDispatchType;
|
|
4348
4499
|
// Module-owned qualified calls (fix #260b) are resolved
|
|
4349
4500
|
// by OWNERSHIP — the dispatch-ambiguity routing below is
|
|
@@ -4373,8 +4524,11 @@ function findCallers(index, name, options = {}) {
|
|
|
4373
4524
|
});
|
|
4374
4525
|
continue;
|
|
4375
4526
|
}
|
|
4376
|
-
if (!call.moduleOwnedPath && !knownDispatchType
|
|
4377
|
-
|
|
4527
|
+
if (!call.moduleOwnedPath && !knownDispatchType) {
|
|
4528
|
+
const contract = externalContractTarget();
|
|
4529
|
+
routeUnverified(filePath, fileEntry, call,
|
|
4530
|
+
contract ? 'possible-dispatch' : methodOwnerKeys().size === 1 ? 'single-owner' : 'method-ambiguous', calledAs, {
|
|
4531
|
+
...(contract && { dispatchVia: contract.via, externalContract: true }),
|
|
4378
4532
|
dispatchCandidates: methodOwnerKeys().size,
|
|
4379
4533
|
});
|
|
4380
4534
|
continue;
|
|
@@ -4566,8 +4720,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4566
4720
|
// in a file that imports _decoders.py is bytes.decode, not
|
|
4567
4721
|
// ContentDecoder.decode. An untyped-receiver method call
|
|
4568
4722
|
// confirms only via binding, same-class, a validated receiver
|
|
4569
|
-
// type
|
|
4570
|
-
//
|
|
4723
|
+
// type or a type-qualified receiver (Class.method static style).
|
|
4724
|
+
// Single-owner and multi-owner name matches
|
|
4571
4725
|
// route VISIBLE method-ambiguous — never dropped. Same for a
|
|
4572
4726
|
// bare call against pure method targets (a bare name cannot
|
|
4573
4727
|
// denote a method in JS/TS/Python — only a rebound alias can,
|
|
@@ -4583,7 +4737,11 @@ function findCallers(index, name, options = {}) {
|
|
|
4583
4737
|
if (call.isMethod && !call.receiverIsModule &&
|
|
4584
4738
|
!recvSubmoduleRel && !call.moduleOwnedPath) {
|
|
4585
4739
|
const tTypes = dispatchTargetTypes(targetDefs2);
|
|
4586
|
-
|
|
4740
|
+
let typeQualifiedReceiver = !!(call.receiver && tTypes.has(call.receiver));
|
|
4741
|
+
if (typeQualifiedReceiver) resolvedReceiverFacts = {
|
|
4742
|
+
receiverType: call.receiver, receiverTypeSource: 'type-qualified',
|
|
4743
|
+
receiverOrigin: { source: 'type-qualified', site: occurrenceIdentity(fileEntry.relativePath, call) },
|
|
4744
|
+
};
|
|
4587
4745
|
const knownDispatchType = call.receiverType ||
|
|
4588
4746
|
fieldHopType || fieldDispatchType;
|
|
4589
4747
|
// A compiler/parser-known receiver type that is not a
|
|
@@ -4758,6 +4916,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4758
4916
|
});
|
|
4759
4917
|
continue;
|
|
4760
4918
|
}
|
|
4919
|
+
typeQualifiedReceiver = true;
|
|
4920
|
+
call = { ...call, moduleOwnedPath: true };
|
|
4761
4921
|
}
|
|
4762
4922
|
// External-contract single owner (fix #210): same
|
|
4763
4923
|
// physics as the nominal gate above — an override
|
|
@@ -4787,7 +4947,7 @@ function findCallers(index, name, options = {}) {
|
|
|
4787
4947
|
});
|
|
4788
4948
|
continue;
|
|
4789
4949
|
}
|
|
4790
|
-
if (!typeQualifiedReceiver
|
|
4950
|
+
if (!typeQualifiedReceiver) {
|
|
4791
4951
|
const knownDispatchType = call.receiverType || fieldHopType || fieldDispatchType;
|
|
4792
4952
|
if (knownDispatchType) {
|
|
4793
4953
|
// Known-but-unvalidated type (supertype of the
|
|
@@ -4801,7 +4961,7 @@ function findCallers(index, name, options = {}) {
|
|
|
4801
4961
|
dispatchCandidates: countDispatchCandidates(knownDispatchType),
|
|
4802
4962
|
});
|
|
4803
4963
|
} else {
|
|
4804
|
-
routeUnverified(filePath, fileEntry, call, 'method-ambiguous', calledAs, {
|
|
4964
|
+
routeUnverified(filePath, fileEntry, call, methodOwnerKeys().size === 1 ? 'single-owner' : 'method-ambiguous', calledAs, {
|
|
4805
4965
|
dispatchCandidates: methodOwnerKeys().size,
|
|
4806
4966
|
});
|
|
4807
4967
|
}
|
|
@@ -4876,6 +5036,17 @@ function findCallers(index, name, options = {}) {
|
|
|
4876
5036
|
}
|
|
4877
5037
|
}
|
|
4878
5038
|
|
|
5039
|
+
if (!collectAccount && call.isMethod && !call.isConstructor &&
|
|
5040
|
+
!resolvedBySameClass && !receiverTypeValidated && !nominalInferredMatch &&
|
|
5041
|
+
!resolvedByExtensionMethod && !call.moduleOwnedPath &&
|
|
5042
|
+
!call.receiverIsModule && !recvSubmoduleRel) {
|
|
5043
|
+
const qualified = _calleeTypeQualifiedReceiver(index,
|
|
5044
|
+
{ ...callerSymbol, file: filePath }, fileEntry, call, fileEntry.language);
|
|
5045
|
+
const exact = qualified?.match && targetDefs.some(target =>
|
|
5046
|
+
target.file === qualified.match.file && target.startLine === qualified.match.startLine);
|
|
5047
|
+
if (!exact) continue;
|
|
5048
|
+
}
|
|
5049
|
+
|
|
4879
5050
|
if (!pendingByFile.has(filePath)) pendingByFile.set(filePath, []);
|
|
4880
5051
|
pendingByFile.get(filePath).push({
|
|
4881
5052
|
call, fileEntry, callerSymbol,
|
|
@@ -4891,6 +5062,19 @@ function findCallers(index, name, options = {}) {
|
|
|
4891
5062
|
receiverType: call.receiverType,
|
|
4892
5063
|
calledAs,
|
|
4893
5064
|
_evidence: {
|
|
5065
|
+
facts: _confirmationFacts(index, filePath, call, targetDefs, {
|
|
5066
|
+
bindingId,
|
|
5067
|
+
...resolvedReceiverFacts,
|
|
5068
|
+
sameClass: !!resolvedBySameClass && !resolvedByTypedAttribute,
|
|
5069
|
+
...(fieldHopType && !call.receiverType && {
|
|
5070
|
+
receiverType: fieldHopType, receiverTypeSource: 'field',
|
|
5071
|
+
receiverOrigin: { source: 'field', root: call.receiverRoot,
|
|
5072
|
+
rootType: call.receiverRootType, field: call.receiverField },
|
|
5073
|
+
}),
|
|
5074
|
+
}),
|
|
5075
|
+
typeQualifiedReceiver: resolvedReceiverFacts.receiverTypeSource === 'type-qualified',
|
|
5076
|
+
moduleOwnedPath: !!call.moduleOwnedPath,
|
|
5077
|
+
extensionMethod: !!resolvedByExtensionMethod,
|
|
4894
5078
|
hasBindingId: !!bindingId,
|
|
4895
5079
|
resolvedBySameClass: !!resolvedBySameClass && !resolvedByTypedAttribute,
|
|
4896
5080
|
hasSamePackageEvidence,
|
|
@@ -4912,8 +5096,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4912
5096
|
// The dispatch gates above have already rejected
|
|
4913
5097
|
// external contracts, universal methods, wrong arity,
|
|
4914
5098
|
// unresolved producer flow, and multi-owner names.
|
|
4915
|
-
//
|
|
4916
|
-
//
|
|
5099
|
+
// Owner count describes unresolved candidates. It
|
|
5100
|
+
// never establishes the identity of a value receiver (#355).
|
|
4917
5101
|
hasSingleOwnerEvidence: !!(collectAccount && call.isMethod &&
|
|
4918
5102
|
!call.inMacroDefinition && !call.isMacro &&
|
|
4919
5103
|
!call.receiverType && !fieldHopType &&
|
|
@@ -4967,14 +5151,43 @@ function findCallers(index, name, options = {}) {
|
|
|
4967
5151
|
for (const { call, fileEntry, callerSymbol, isMethod, isFunctionReference,
|
|
4968
5152
|
isTypeReference, receiver, receiverType, calledAs, _evidence, _tier,
|
|
4969
5153
|
_reason, _meta } of pending) {
|
|
4970
|
-
const
|
|
5154
|
+
const evidence = {
|
|
5155
|
+
...(_evidence || {}),
|
|
5156
|
+
...(_reason && { reason: _reason }),
|
|
5157
|
+
facts: _evidence?.facts || _confirmationFacts(index, filePath,
|
|
5158
|
+
call, options.targetDefinitions || definitions, {
|
|
5159
|
+
sameClass: _evidence?.resolvedBySameClass,
|
|
5160
|
+
}),
|
|
5161
|
+
};
|
|
5162
|
+
let scored = scoreEdge(evidence);
|
|
5163
|
+
let routedTier = _tier;
|
|
5164
|
+
let routedReason = _reason;
|
|
5165
|
+
const migrated = isMethod && !call.isConstructor && !evidence.resolvedBySameClass &&
|
|
5166
|
+
!evidence.typeQualifiedReceiver && !evidence.moduleOwnedPath && !evidence.extensionMethod &&
|
|
5167
|
+
(evidence.hasReceiverType || evidence.resolvedByReceiverHint || evidence.hasSingleOwnerEvidence);
|
|
5168
|
+
if (!_tier && migrated && collectAccount) {
|
|
5169
|
+
const checked = validateConfirmation(scored.provenance, evidence.facts.targets);
|
|
5170
|
+
if (checked.verdict === 'establishes-other') {
|
|
5171
|
+
recordExcluded(filePath, call.line, 'receiver-target-different', scored.provenance);
|
|
5172
|
+
continue;
|
|
5173
|
+
}
|
|
5174
|
+
if (checked.verdict !== 'establishes-target' && checked.verdict !== 'unsupported') {
|
|
5175
|
+
if (!collectAccount) continue;
|
|
5176
|
+
routedTier = TIER.UNVERIFIED;
|
|
5177
|
+
routedReason = evidence.hasSingleOwnerEvidence && !evidence.hasReceiverType
|
|
5178
|
+
? 'single-owner' : 'provenance-incomplete';
|
|
5179
|
+
const ranking = scoreEdge({ isUncertain: true,
|
|
5180
|
+
hasSingleOwnerEvidence: routedReason === 'single-owner' });
|
|
5181
|
+
scored = { ...ranking, provenance: scored.provenance };
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
4971
5184
|
// Family B contract field (fix #221): a bind/call/apply site reaches
|
|
4972
5185
|
// the target through Function.prototype indirection, not direct call
|
|
4973
5186
|
// syntax — label the edge calledAs:'bound'. Rename aliases keep their
|
|
4974
5187
|
// surface name (they describe the same slot and are rarer). Label
|
|
4975
5188
|
// only, computed at edge construction: routing logic never sees it.
|
|
4976
5189
|
const edgeCalledAs = calledAs || (call.boundCall ? 'bound' : undefined);
|
|
4977
|
-
if (
|
|
5190
|
+
if (routedTier) {
|
|
4978
5191
|
// Routed unverified entry — never competes with the main
|
|
4979
5192
|
// answer for maxResults/enrichLimit slots.
|
|
4980
5193
|
const base = {
|
|
@@ -4986,8 +5199,9 @@ function findCallers(index, name, options = {}) {
|
|
|
4986
5199
|
evidenceScore: scored.evidenceScore,
|
|
4987
5200
|
scoreKind: scored.scoreKind,
|
|
4988
5201
|
resolution: scored.resolution,
|
|
4989
|
-
|
|
4990
|
-
|
|
5202
|
+
...(collectAccount && { provenance: scored.provenance }),
|
|
5203
|
+
tier: routedTier,
|
|
5204
|
+
reason: routedReason,
|
|
4991
5205
|
...(_meta || {}),
|
|
4992
5206
|
isMethod: call.isMethod || false,
|
|
4993
5207
|
...(isFunctionReference && { isFunctionReference: true }),
|
|
@@ -5036,6 +5250,7 @@ function findCallers(index, name, options = {}) {
|
|
|
5036
5250
|
evidenceScore: scored.evidenceScore,
|
|
5037
5251
|
scoreKind: scored.scoreKind,
|
|
5038
5252
|
resolution: scored.resolution,
|
|
5253
|
+
...(collectAccount && { provenance: scored.provenance }),
|
|
5039
5254
|
...(tier && { tier }),
|
|
5040
5255
|
isMethod: call.isMethod || false,
|
|
5041
5256
|
...(isFunctionReference && { isFunctionReference: true }),
|
|
@@ -5074,6 +5289,7 @@ function findCallers(index, name, options = {}) {
|
|
|
5074
5289
|
evidenceScore: scored.evidenceScore,
|
|
5075
5290
|
scoreKind: scored.scoreKind,
|
|
5076
5291
|
resolution: scored.resolution,
|
|
5292
|
+
...(collectAccount && { provenance: scored.provenance }),
|
|
5077
5293
|
...(tier && { tier }),
|
|
5078
5294
|
});
|
|
5079
5295
|
enrichedCount++;
|
|
@@ -5211,6 +5427,30 @@ function findCallees(index, definition, options = {}) {
|
|
|
5211
5427
|
: [];
|
|
5212
5428
|
|
|
5213
5429
|
const callees = new Map(); // key -> { name, bindingId, count }
|
|
5430
|
+
const siteEvidence = new Map();
|
|
5431
|
+
const provenanceForSite = (siteId, target, reason) => {
|
|
5432
|
+
const record = siteEvidence.get(siteId) || { call: calls[siteId], evidence: {} };
|
|
5433
|
+
const call = record.call;
|
|
5434
|
+
const evidence = { ...record.evidence, ...(reason && { reason }) };
|
|
5435
|
+
evidence.facts = _confirmationFacts(index, def.file, call,
|
|
5436
|
+
target ? [target] : (index.symbols.get(call.name) || []), record.options);
|
|
5437
|
+
// A bindingId synthesized by receiver resolution is not a
|
|
5438
|
+
// lexical binding of the method token. Rank the actual site facts.
|
|
5439
|
+
if (call.isMethod && !call.receiverIsModule && !call.moduleOwnedPath &&
|
|
5440
|
+
!evidence.typeQualifiedReceiver && !evidence.extensionMethod) evidence.hasBindingId = false;
|
|
5441
|
+
if (!call.isMethod && target && !evidence.hasBindingId) {
|
|
5442
|
+
evidence.hasImportEvidence = (index.symbols.get(call.name) || []).length === 1 ||
|
|
5443
|
+
target.file === def.file || index.importGraph.get(def.file)?.has(target.file);
|
|
5444
|
+
}
|
|
5445
|
+
const scored = scoreEdge(evidence);
|
|
5446
|
+
return {
|
|
5447
|
+
...occurrenceIdentity(fileEntry?.relativePath || def.file, call, siteId),
|
|
5448
|
+
...(target && { targetDef: declarationIdentity(target) }),
|
|
5449
|
+
confidence: scored.confidence, evidenceScore: scored.evidenceScore,
|
|
5450
|
+
resolution: scored.resolution, scoreKind: scored.scoreKind,
|
|
5451
|
+
provenance: scored.provenance,
|
|
5452
|
+
};
|
|
5453
|
+
};
|
|
5214
5454
|
let selfAttrCalls = null; // collected for Python self.attr.method() resolution
|
|
5215
5455
|
let selfMethodCalls = null; // collected for Python self.method() resolution
|
|
5216
5456
|
|
|
@@ -5259,7 +5499,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5259
5499
|
};
|
|
5260
5500
|
// Retain an uncertain/unresolved call as a visible unverified callee
|
|
5261
5501
|
// entry (aggregated by name+reason) and claim its site.
|
|
5262
|
-
const noteUnverified = (siteId, call, reason, meta = {}) => {
|
|
5502
|
+
const noteUnverified = (siteId, call, reason, meta = {}, siteProof) => {
|
|
5263
5503
|
if (!collectAccount || claimedSiteIds.has(siteId)) return;
|
|
5264
5504
|
noteSite(siteId, 'unverified', reason, call);
|
|
5265
5505
|
const key = `${call.name}|${reason}|${meta.dispatchVia || ''}`;
|
|
@@ -5272,6 +5512,13 @@ function findCallees(index, definition, options = {}) {
|
|
|
5272
5512
|
}
|
|
5273
5513
|
entry.callCount++;
|
|
5274
5514
|
entry.sites.push(call.line);
|
|
5515
|
+
if (!entry.siteProvenance) entry.siteProvenance = [];
|
|
5516
|
+
const unverifiedScore = scoreEdge({ isUncertain: true, reason });
|
|
5517
|
+
entry.siteProvenance.push({ ...(siteProof || provenanceForSite(siteId, null, reason)),
|
|
5518
|
+
tier: TIER.UNVERIFIED, reason,
|
|
5519
|
+
confidence: unverifiedScore.confidence, evidenceScore: unverifiedScore.evidenceScore,
|
|
5520
|
+
resolution: reason, scoreKind: unverifiedScore.scoreKind });
|
|
5521
|
+
entry.provenance = summarizeProvenance(entry.siteProvenance);
|
|
5275
5522
|
};
|
|
5276
5523
|
// A statically selected base implementation is not the only runtime
|
|
5277
5524
|
// target in languages with virtual/structural dispatch. If a project
|
|
@@ -5353,7 +5600,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5353
5600
|
// already carry stronger identity and must not trigger a whole-file
|
|
5354
5601
|
// flow build merely because their parser record lacks receiverType.
|
|
5355
5602
|
const mayNeedDirectReceiverFlow = call =>
|
|
5356
|
-
call.isMethod && call.receiver && !call.receiverType
|
|
5603
|
+
call.isMethod && call.receiver && (!call.receiverType ||
|
|
5604
|
+
call.receiverTypeGuessed || call.receiverTypeSource === 'guess') &&
|
|
5357
5605
|
!call.receiverPatternShadow &&
|
|
5358
5606
|
!_isReservedReceiver(language, call.receiver) &&
|
|
5359
5607
|
!call.isPathCall && !call.receiverIsModule &&
|
|
@@ -5378,6 +5626,14 @@ function findCallees(index, definition, options = {}) {
|
|
|
5378
5626
|
for (let call of calls) {
|
|
5379
5627
|
siteOrdinal++;
|
|
5380
5628
|
const siteId = siteOrdinal;
|
|
5629
|
+
siteEvidence.set(siteId, {
|
|
5630
|
+
call,
|
|
5631
|
+
evidence: {
|
|
5632
|
+
hasReceiverType: !!call.receiverType,
|
|
5633
|
+
resolvedBySameClass: !!call.receiver && _isReservedReceiver(language, call.receiver),
|
|
5634
|
+
},
|
|
5635
|
+
options: { sameClass: !!call.receiver && _isReservedReceiver(language, call.receiver) },
|
|
5636
|
+
});
|
|
5381
5637
|
if (language === 'csharp') {
|
|
5382
5638
|
// fix #353: `Beta.Helper.Widget()` — namespace-qualified type
|
|
5383
5639
|
// receiver (see the findCallers twin).
|
|
@@ -5391,6 +5647,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5391
5647
|
call = {
|
|
5392
5648
|
...call,
|
|
5393
5649
|
receiverType: indexedType.type,
|
|
5650
|
+
receiverTypeSource: 'flow',
|
|
5651
|
+
receiverTypeEvidence: { source: 'flow', ...indexedType },
|
|
5394
5652
|
...(indexedType.fromFile && {
|
|
5395
5653
|
receiverTypeFlowFile: indexedType.fromFile,
|
|
5396
5654
|
}),
|
|
@@ -5417,6 +5675,55 @@ function findCallees(index, definition, options = {}) {
|
|
|
5417
5675
|
if (!isDirectMatch && !isNestedCallback) continue;
|
|
5418
5676
|
if (calleeAccount) calleeAccount.totalSites++;
|
|
5419
5677
|
|
|
5678
|
+
if (language === 'python' && call.isMethod && !call.receiverType) {
|
|
5679
|
+
const fixture = _pythonFixtureReceiverType(index, def.file, call);
|
|
5680
|
+
if (fixture) call = { ...call, receiverType: fixture.type,
|
|
5681
|
+
receiverTypeSource: 'fixture', receiverTypeEvidence: { source: 'fixture', ...fixture },
|
|
5682
|
+
...(fixture.fromFile && { receiverTypeFlowFile: fixture.fromFile }) };
|
|
5683
|
+
const external = !fixture && _pythonExternalFieldFlow(index, def.file, call);
|
|
5684
|
+
if (external) {
|
|
5685
|
+
call = { ...call, receiverTypeSource: 'flow',
|
|
5686
|
+
receiverTypeEvidence: { source: 'flow', externalFactory: external.proof } };
|
|
5687
|
+
const targets = (index.symbols.get(call.name) || []).filter(d => !NON_CALLABLE_TYPES.has(d.type));
|
|
5688
|
+
const facts = _confirmationFacts(index, def.file, call, targets);
|
|
5689
|
+
const provenance = scoreEdge({ possibleDispatch: true, facts }).provenance;
|
|
5690
|
+
noteUnverified(siteId, call, 'possible-dispatch', { dispatchVia: external.via, externalContract: true },
|
|
5691
|
+
{ ...occurrenceIdentity(fileEntry.relativePath, call, siteId), provenance });
|
|
5692
|
+
continue;
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
|
|
5696
|
+
if (language === 'rust' && _rustStandardWrapperMethod(index, def.file, call, allCalls)) {
|
|
5697
|
+
noteSite(siteId, 'external', 'standard-wrapper-method', call);
|
|
5698
|
+
continue;
|
|
5699
|
+
}
|
|
5700
|
+
if (language === 'rust' && call.isMethod && !call.receiverType &&
|
|
5701
|
+
call.receiverPatternVariant && (collectAccount || call.receiverPatternSourceCallStart != null)) {
|
|
5702
|
+
const item = _rustPatternReceiverType(index, fileEntry, def.file, call, foldCtx());
|
|
5703
|
+
if (item?.type) call = { ...call, receiverType: item.type,
|
|
5704
|
+
receiverTypeSource: 'flow', receiverTypeEvidence: { source: 'flow', ...item },
|
|
5705
|
+
receiverTypeFlowFile: item.fromFile };
|
|
5706
|
+
}
|
|
5707
|
+
if (language === 'rust' && call.isMethod &&
|
|
5708
|
+
call.receiver && !call.receiverType &&
|
|
5709
|
+
(call.receiverIterationCall || call.receiverIterationVariable)) {
|
|
5710
|
+
const item = _rustIterationReceiverType(index, fileEntry, def.file, call, foldCtx());
|
|
5711
|
+
if (item) call = { ...call, receiverType: item.type,
|
|
5712
|
+
receiverTypeSource: 'flow', receiverTypeEvidence: { source: 'flow', ...item },
|
|
5713
|
+
receiverTypeFlowFile: item.fromFile };
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
if (language === 'c' && call.isMethod) {
|
|
5717
|
+
noteUnverified(siteId, call, 'callable-field');
|
|
5718
|
+
continue;
|
|
5719
|
+
}
|
|
5720
|
+
|
|
5721
|
+
if (language === 'csharp' && call.receiverType === 'dynamic') {
|
|
5722
|
+
if (_calleeZeroCandidateName(index, call)) noteSite(siteId, 'external', null, call);
|
|
5723
|
+
else noteUnverified(siteId, call, 'possible-dispatch', { dispatchVia: 'dynamic' });
|
|
5724
|
+
continue;
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5420
5727
|
if (call.macroParameter) {
|
|
5421
5728
|
noteSite(siteId, 'excluded', 'macro-parameter', call);
|
|
5422
5729
|
continue;
|
|
@@ -5460,6 +5767,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5460
5767
|
const selected = _calleeOverloadSelect(
|
|
5461
5768
|
index, call, extensions, language);
|
|
5462
5769
|
if (selected.match) {
|
|
5770
|
+
if (collectAccount) siteEvidence.get(siteId).evidence.extensionMethod = true;
|
|
5463
5771
|
const match = selected.match;
|
|
5464
5772
|
const key = match.bindingId ||
|
|
5465
5773
|
`${match.file}:${match.startLine}:${call.name}`;
|
|
@@ -5530,6 +5838,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5530
5838
|
call = {
|
|
5531
5839
|
...call,
|
|
5532
5840
|
receiverType: indexedType.type,
|
|
5841
|
+
receiverTypeSource: 'flow',
|
|
5842
|
+
receiverTypeEvidence: { source: 'flow', ...indexedType },
|
|
5533
5843
|
...(indexedType.fromFile && {
|
|
5534
5844
|
receiverTypeFlowFile: indexedType.fromFile,
|
|
5535
5845
|
}),
|
|
@@ -5648,6 +5958,22 @@ function findCallees(index, definition, options = {}) {
|
|
|
5648
5958
|
}
|
|
5649
5959
|
}
|
|
5650
5960
|
|
|
5961
|
+
if (collectAccount) {
|
|
5962
|
+
const record = siteEvidence.get(siteId);
|
|
5963
|
+
record.call = call;
|
|
5964
|
+
if (directReceiverFlow?.type || fieldHopType) {
|
|
5965
|
+
const source = directReceiverFlow?.type ? 'flow' : 'field';
|
|
5966
|
+
record.evidence.hasReceiverType = true;
|
|
5967
|
+
Object.assign(record.options, {
|
|
5968
|
+
receiverType: directReceiverFlow?.type || fieldHopType,
|
|
5969
|
+
receiverTypeSource: source,
|
|
5970
|
+
originFile: directReceiverFlow?.fromFile || fieldHopInfo?.fromFile,
|
|
5971
|
+
receiverOrigin: { source, ...(directReceiverFlow || fieldHopInfo || {}),
|
|
5972
|
+
field: call.receiverField, rootType: call.receiverRootType },
|
|
5973
|
+
});
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
|
|
5651
5977
|
if (fieldDispatchType) {
|
|
5652
5978
|
noteUnverified(siteId, call, 'possible-dispatch', {
|
|
5653
5979
|
dispatchVia: fieldDispatchType,
|
|
@@ -5667,6 +5993,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5667
5993
|
const selected = _calleeOverloadSelect(
|
|
5668
5994
|
index, extensionCall, extensions, language);
|
|
5669
5995
|
if (selected.match) {
|
|
5996
|
+
if (collectAccount) siteEvidence.get(siteId).evidence.extensionMethod = true;
|
|
5670
5997
|
const match = selected.match;
|
|
5671
5998
|
const key = match.bindingId ||
|
|
5672
5999
|
`${match.file}:${match.startLine}:${call.name}`;
|
|
@@ -5810,6 +6137,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5810
6137
|
!_isReservedReceiver(language, call.receiver) &&
|
|
5811
6138
|
!(localTypes && localTypes.has(call.receiver))) {
|
|
5812
6139
|
typeQual = _calleeTypeQualifiedReceiver(index, def, fileEntry, call, language);
|
|
6140
|
+
if (typeQual) siteEvidence.get(siteId).evidence.typeQualifiedReceiver = true;
|
|
5813
6141
|
}
|
|
5814
6142
|
|
|
5815
6143
|
// Package-qualified NON-method records (fix #268, chi-measured):
|
|
@@ -5954,7 +6282,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5954
6282
|
}
|
|
5955
6283
|
continue;
|
|
5956
6284
|
}
|
|
5957
|
-
const typeName = call.receiverType ||
|
|
6285
|
+
const typeName = directReceiverFlow?.type || call.receiverType || fieldHopType;
|
|
5958
6286
|
const symbols = index.symbols.get(call.name);
|
|
5959
6287
|
const qualifiedType = language === 'go' && call.receiverType
|
|
5960
6288
|
? _goQualifiedReceiverType(index, fileEntry,
|
|
@@ -5980,8 +6308,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5980
6308
|
const isCallableRT = (s) => !NON_CALLABLE_TYPES.has(s.type) ||
|
|
5981
6309
|
(s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
|
|
5982
6310
|
// Same-class overload selection by static call shape (fix #268)
|
|
5983
|
-
const receiverOriginFile =
|
|
5984
|
-
|
|
6311
|
+
const receiverOriginFile = directReceiverFlow?.fromFile ||
|
|
6312
|
+
call.receiverTypeFlowFile ||
|
|
5985
6313
|
fieldHopInfo?.fromFile ||
|
|
5986
6314
|
(call.receiverType
|
|
5987
6315
|
? _resolveFlowTypeOrigin(
|
|
@@ -6103,6 +6431,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
6103
6431
|
chained = _nominalChainedReceiverType(index, call, fileEntry, def.file);
|
|
6104
6432
|
}
|
|
6105
6433
|
if (chained?.type) {
|
|
6434
|
+
const record = siteEvidence.get(siteId);
|
|
6435
|
+
record.evidence.hasReceiverType = true;
|
|
6436
|
+
Object.assign(record.options, {
|
|
6437
|
+
receiverType: chained.type, receiverTypeSource: 'flow',
|
|
6438
|
+
receiverOrigin: { source: 'flow', ...chained }, originFile: chained.fromFile,
|
|
6439
|
+
});
|
|
6106
6440
|
const symbols = index.symbols.get(call.name);
|
|
6107
6441
|
const isCallableCh = (s) => !NON_CALLABLE_TYPES.has(s.type) ||
|
|
6108
6442
|
(s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
|
|
@@ -6849,21 +7183,28 @@ function findCallees(index, definition, options = {}) {
|
|
|
6849
7183
|
if (bound && routeVirtualOverride(siteId, call, def.className, bound)) continue;
|
|
6850
7184
|
}
|
|
6851
7185
|
|
|
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.
|
|
7186
|
+
// Receiver evidence for the former single-owner fallback (#355).
|
|
7187
|
+
// A name with one owner still needs an independent typed lookup;
|
|
7188
|
+
// unresolved values remain visible in the contract.
|
|
6858
7189
|
if (isUncertain && call.isMethod && call.receiver && !bindingResolved) {
|
|
6859
7190
|
const fm = mayNeedDirectReceiverFlow(call) ? flowMap() : null;
|
|
6860
7191
|
const flowEntry = fm ? _lookupReturnTypeFlow(fm, call) : undefined;
|
|
6861
7192
|
const owner = _calleeSingleOwnerMatch(index, def, fileEntry, call, effectiveName, language, flowEntry);
|
|
6862
7193
|
if (owner) {
|
|
7194
|
+
siteEvidence.get(siteId).evidence.hasSingleOwnerEvidence = true;
|
|
6863
7195
|
isUncertain = false;
|
|
6864
7196
|
bindingResolved = owner.bindingId;
|
|
6865
7197
|
calleeKey = owner.bindingId ||
|
|
6866
7198
|
`${owner.className || (owner.receiver || '').replace(/^\*/, '')}.${effectiveName}`;
|
|
7199
|
+
} else if (!call.receiverType && !flowEntry?.type && !fieldHopType &&
|
|
7200
|
+
!call.receiverExternalFlow && !call.receiverQualifiedFlow) {
|
|
7201
|
+
const owners = new Set((index.symbols.get(effectiveName) || [])
|
|
7202
|
+
.filter(d => !NON_CALLABLE_TYPES.has(d.type) && (d.className || d.receiver))
|
|
7203
|
+
.map(d => `${d.file}:${d.className || d.receiver}`));
|
|
7204
|
+
if (owners.size === 1) {
|
|
7205
|
+
uncertainReason = 'single-owner';
|
|
7206
|
+
siteEvidence.get(siteId).evidence.hasSingleOwnerEvidence = true;
|
|
7207
|
+
}
|
|
6867
7208
|
}
|
|
6868
7209
|
}
|
|
6869
7210
|
|
|
@@ -6891,6 +7232,59 @@ function findCallees(index, definition, options = {}) {
|
|
|
6891
7232
|
}
|
|
6892
7233
|
}
|
|
6893
7234
|
|
|
7235
|
+
// Legacy callers receive no uncertainty band. Drop an untyped
|
|
7236
|
+
// value receiver even if a same-file or unique-name lookup picked
|
|
7237
|
+
// a method; only actual receiver/module/type evidence survives.
|
|
7238
|
+
if (!collectAccount && call.isMethod && !call.receiverType &&
|
|
7239
|
+
!directReceiverFlow?.type && !fieldHopType &&
|
|
7240
|
+
!localTypes?.has(call.receiver) &&
|
|
7241
|
+
!siteEvidence.get(siteId).evidence.resolvedBySameClass &&
|
|
7242
|
+
!siteEvidence.get(siteId).evidence.typeQualifiedReceiver &&
|
|
7243
|
+
!call.receiverIsModule && !call.receiverModuleSpecifier &&
|
|
7244
|
+
!call.receiverModuleComposition && !call.moduleOwnedPath &&
|
|
7245
|
+
!call.isConstructor) continue;
|
|
7246
|
+
|
|
7247
|
+
if (collectAccount) {
|
|
7248
|
+
const record = siteEvidence.get(siteId);
|
|
7249
|
+
record.call = call;
|
|
7250
|
+
if (bindingResolved && !record.evidence.hasSingleOwnerEvidence) {
|
|
7251
|
+
record.evidence.hasBindingId = true;
|
|
7252
|
+
record.options.bindingId = bindingResolved;
|
|
7253
|
+
}
|
|
7254
|
+
}
|
|
7255
|
+
if (collectAccount) {
|
|
7256
|
+
const record = siteEvidence.get(siteId);
|
|
7257
|
+
const valueReceiver = call.isMethod && !call.isConstructor &&
|
|
7258
|
+
!call.receiverIsModule && !call.receiverModuleSpecifier &&
|
|
7259
|
+
!call.receiverModuleComposition && !call.moduleOwnedPath &&
|
|
7260
|
+
!record.evidence.typeQualifiedReceiver && !record.evidence.extensionMethod &&
|
|
7261
|
+
!record.evidence.resolvedBySameClass;
|
|
7262
|
+
if (valueReceiver) {
|
|
7263
|
+
// Resolve and classify THIS occurrence before its grouping
|
|
7264
|
+
// key is chosen. A sibling site cannot lend its receiver
|
|
7265
|
+
// evidence or overload identity to this one.
|
|
7266
|
+
const proof = provenanceForSite(siteId, null);
|
|
7267
|
+
const facts = proof.provenance.facts;
|
|
7268
|
+
const checked = validateConfirmation(proof.provenance, facts.targets);
|
|
7269
|
+
const selected = checked.verdict === 'establishes-target' &&
|
|
7270
|
+
(index.symbols.get(effectiveName) || []).find(candidate =>
|
|
7271
|
+
sameDeclaration(declarationIdentity(candidate), facts.lookup?.selected));
|
|
7272
|
+
const reportOnly = checked.verdict === 'unsupported' && record.evidence.hasReceiverType;
|
|
7273
|
+
if (!selected && !reportOnly) {
|
|
7274
|
+
const reason = !record.evidence.hasReceiverType && facts.ownerCount === 1
|
|
7275
|
+
? 'single-owner' : 'provenance-incomplete';
|
|
7276
|
+
if (reason === 'single-owner') proof.provenance = scoreEdge({
|
|
7277
|
+
hasSingleOwnerEvidence: true, facts,
|
|
7278
|
+
}).provenance;
|
|
7279
|
+
noteUnverified(siteId, call, reason, {}, proof);
|
|
7280
|
+
continue;
|
|
7281
|
+
}
|
|
7282
|
+
if (selected) {
|
|
7283
|
+
bindingResolved = selected.bindingId;
|
|
7284
|
+
calleeKey = selected.bindingId || `${selected.file}:${selected.startLine}:${selected.name}`;
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
6894
7288
|
const existing = callees.get(calleeKey);
|
|
6895
7289
|
if (existing) {
|
|
6896
7290
|
existing.count += 1;
|
|
@@ -7254,24 +7648,61 @@ function findCallees(index, definition, options = {}) {
|
|
|
7254
7648
|
}
|
|
7255
7649
|
}
|
|
7256
7650
|
|
|
7257
|
-
|
|
7651
|
+
let calleeScored = scoreEdge({
|
|
7258
7652
|
hasBindingId: !!bindingId,
|
|
7259
7653
|
hasImportEvidence: !!bindingId || resolutionSymbols.length === 1 ||
|
|
7260
7654
|
(callee.file === def.file) || callerImportSet.has(callee.file),
|
|
7261
7655
|
isUncertain: false, // uncertain callees already filtered above
|
|
7262
7656
|
});
|
|
7657
|
+
const siteProvenance = collectAccount ? siteIds.map(siteId => ({
|
|
7658
|
+
...provenanceForSite(siteId, callee), tier: TIER.CONFIRMED,
|
|
7659
|
+
})).sort((a, b) => a.siteId - b.siteId).filter(site => {
|
|
7660
|
+
const record = siteEvidence.get(site.siteId);
|
|
7661
|
+
const valueReceiver = record.call.isMethod &&
|
|
7662
|
+
!record.call.receiverIsModule && !record.call.receiverModuleSpecifier &&
|
|
7663
|
+
!record.call.receiverModuleComposition && !record.evidence.typeQualifiedReceiver;
|
|
7664
|
+
const untypedValue = valueReceiver && !record.evidence.hasReceiverType &&
|
|
7665
|
+
!record.evidence.resolvedBySameClass;
|
|
7666
|
+
if (untypedValue && site.provenance.facts.ownerCount === 1) {
|
|
7667
|
+
record.evidence.hasSingleOwnerEvidence = true;
|
|
7668
|
+
}
|
|
7669
|
+
const migrated = valueReceiver && !record.call.isConstructor &&
|
|
7670
|
+
!record.evidence.resolvedBySameClass && !record.evidence.extensionMethod &&
|
|
7671
|
+
(record.evidence.hasReceiverType || record.evidence.hasSingleOwnerEvidence || untypedValue);
|
|
7672
|
+
if (!migrated) return true;
|
|
7673
|
+
const checked = validateConfirmation(site.provenance, [declarationIdentity(callee)]);
|
|
7674
|
+
if (checked.verdict === 'establishes-target' ||
|
|
7675
|
+
(checked.verdict === 'unsupported' && record.evidence.hasReceiverType)) return true;
|
|
7676
|
+
if (checked.verdict === 'establishes-other') {
|
|
7677
|
+
noteSite(site.siteId, 'excluded', 'receiver-target-different', record.call);
|
|
7678
|
+
(calleeAccount.excluded.evidence ||= []).push(site);
|
|
7679
|
+
} else {
|
|
7680
|
+
const reason = record.evidence.hasSingleOwnerEvidence && !record.evidence.hasReceiverType
|
|
7681
|
+
? 'single-owner' : 'provenance-incomplete';
|
|
7682
|
+
if (reason === 'single-owner') site.provenance = scoreEdge({
|
|
7683
|
+
hasSingleOwnerEvidence: true, facts: site.provenance.facts,
|
|
7684
|
+
}).provenance;
|
|
7685
|
+
noteUnverified(site.siteId, record.call, reason, {}, site);
|
|
7686
|
+
}
|
|
7687
|
+
return false;
|
|
7688
|
+
}) : null;
|
|
7689
|
+
if (collectAccount && siteProvenance.length === 0) continue;
|
|
7690
|
+
if (collectAccount) calleeScored = siteProvenance.reduce((weakest, site) =>
|
|
7691
|
+
site.evidenceScore < weakest.evidenceScore ? site : weakest);
|
|
7263
7692
|
claimSites('confirmed', null);
|
|
7264
7693
|
result.push({
|
|
7265
7694
|
...callee,
|
|
7266
|
-
callCount: count,
|
|
7267
|
-
weight: index.calculateWeight(count),
|
|
7695
|
+
callCount: collectAccount ? siteProvenance.length : count,
|
|
7696
|
+
weight: index.calculateWeight(collectAccount ? siteProvenance.length : count),
|
|
7268
7697
|
confidence: calleeScored.confidence,
|
|
7269
7698
|
evidenceScore: calleeScored.evidenceScore,
|
|
7270
7699
|
scoreKind: calleeScored.scoreKind,
|
|
7271
7700
|
resolution: calleeScored.resolution,
|
|
7272
7701
|
...(collectAccount && {
|
|
7273
7702
|
tier: TIER.CONFIRMED,
|
|
7274
|
-
|
|
7703
|
+
provenance: summarizeProvenance(siteProvenance),
|
|
7704
|
+
siteProvenance,
|
|
7705
|
+
sites: siteProvenance.map(site => site.line).sort((a, b) => a - b),
|
|
7275
7706
|
...(isFunctionReference && { functionReference: true }),
|
|
7276
7707
|
}),
|
|
7277
7708
|
});
|
|
@@ -7369,6 +7800,28 @@ function getInstanceAttributeTypes(index, filePath, className) {
|
|
|
7369
7800
|
'python', { filePath, consumerAwaited: false });
|
|
7370
7801
|
return result?.fromFile ? result.type : null;
|
|
7371
7802
|
},
|
|
7803
|
+
resolveImportedCallType(name) {
|
|
7804
|
+
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
7805
|
+
(binding.alias || binding.name) === name);
|
|
7806
|
+
if (bindings.length !== 1 || (fileEntry.moduleAssignedNames || []).includes(name) ||
|
|
7807
|
+
(index.symbols.get(name) || []).some(d => d.file === filePath)) return null;
|
|
7808
|
+
const binding = bindings[0];
|
|
7809
|
+
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) return null;
|
|
7810
|
+
const type = langModule.getBuiltinCallReturnType?.(binding.module, binding.name);
|
|
7811
|
+
return type ? { type, binding: { ...binding }, externalModule: binding.module } : null;
|
|
7812
|
+
},
|
|
7813
|
+
resolveExternalFactory(parts) {
|
|
7814
|
+
const name = parts[0];
|
|
7815
|
+
const bindings = (fileEntry.importBindings || []).filter(binding => (binding.alias || binding.name) === name);
|
|
7816
|
+
if (bindings.length !== 1 || (fileEntry.moduleAssignedNames || []).includes(name) ||
|
|
7817
|
+
(index.symbols.get(name) || []).some(d => d.file === filePath)) return null;
|
|
7818
|
+
const binding = bindings[0];
|
|
7819
|
+
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) return null;
|
|
7820
|
+
const importedFunction = ['from', 'relative'].includes(binding.kind);
|
|
7821
|
+
if (parts.length !== (importedFunction ? 1 : 2)) return null;
|
|
7822
|
+
return { module: binding.module, producer: importedFunction ? binding.name : parts[1],
|
|
7823
|
+
binding: { ...binding }, projectDeclarations: [], projectBindings: [], resolvedModule: null };
|
|
7824
|
+
},
|
|
7372
7825
|
});
|
|
7373
7826
|
index._attrTypeCache.set(filePath, fileCache);
|
|
7374
7827
|
} catch {
|
|
@@ -7616,6 +8069,30 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7616
8069
|
candidate.callStart === call.receiverCallStart &&
|
|
7617
8070
|
candidate.callEnd === call.receiverCallEnd);
|
|
7618
8071
|
|
|
8072
|
+
// A separate unwrap/expect assignment consumes the payload of the
|
|
8073
|
+
// receiver's declared standard wrapper. Its spelling alone is not a
|
|
8074
|
+
// contract: project-defined Result/Option classes remain ordinary
|
|
8075
|
+
// method producers. Reassignments are checked by the same nearest-
|
|
8076
|
+
// assignment lookup used for every other flow receiver.
|
|
8077
|
+
if (language === 'rust' && call.isMethod && call.receiver &&
|
|
8078
|
+
!call.receiverPatternShadow && !call.receiverFlowInvalidated &&
|
|
8079
|
+
((call.name === 'unwrap' && call.argCount === 0) ||
|
|
8080
|
+
(call.name === 'expect' && call.argCount === 1))) {
|
|
8081
|
+
const receiverFlow = _lookupReturnTypeFlow(map, call);
|
|
8082
|
+
const contract = receiverFlow?.rustWrapper;
|
|
8083
|
+
if (contract?.type && validateRustWrapperContract(contract)) {
|
|
8084
|
+
const scope = call.enclosingFunction ? `${call.enclosingFunction.startLine}` : '';
|
|
8085
|
+
const key = `${scope}:${call.assignedTo}`;
|
|
8086
|
+
if (!map.has(key)) map.set(key, []);
|
|
8087
|
+
map.get(key).push({ line: call.line, start: call.callStart,
|
|
8088
|
+
type: contract.type, fromFile: contract.fromFile,
|
|
8089
|
+
wrapperUnwrap: { method: call.name,
|
|
8090
|
+
producer: { line: receiverFlow.line, start: receiverFlow.start,
|
|
8091
|
+
type: receiverFlow.type, fromFile: receiverFlow.fromFile }, contract } });
|
|
8092
|
+
continue;
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
|
|
7619
8096
|
// Rust's `collect::<Vec<Item>>()` turbofish fixes the concrete result
|
|
7620
8097
|
// and its item type at the call site. Preserve the item identity even
|
|
7621
8098
|
// though the outer collection is a standard-library type with no
|
|
@@ -7692,7 +8169,7 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7692
8169
|
? resolved[0]
|
|
7693
8170
|
: null;
|
|
7694
8171
|
}
|
|
7695
|
-
if (folded?.type || folded?.externalVia) {
|
|
8172
|
+
if (folded?.type || folded?.externalVia || folded?.rustWrapper) {
|
|
7696
8173
|
const scope = call.enclosingFunction
|
|
7697
8174
|
? `${call.enclosingFunction.startLine}` : '';
|
|
7698
8175
|
const key = `${scope}:${call.assignedTo}`;
|
|
@@ -7701,12 +8178,15 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7701
8178
|
map.get(key).push({ line: call.line, start: call.callStart,
|
|
7702
8179
|
...(folded.type && { type: folded.type }),
|
|
7703
8180
|
...(folded.fromFile && { fromFile: folded.fromFile }),
|
|
8181
|
+
...(folded.rustWrapper && { rustWrapper: folded.rustWrapper }),
|
|
8182
|
+
...(folded.rustReturnDeclaration && { rustReturnDeclaration: folded.rustReturnDeclaration }),
|
|
8183
|
+
...(folded.moduleProducer && { moduleProducer: folded.moduleProducer }),
|
|
7704
8184
|
...(folded.externalVia && { externalVia: folded.externalVia }),
|
|
7705
8185
|
...(folded.externalConcrete && { externalConcrete: true }) });
|
|
7706
8186
|
continue;
|
|
7707
8187
|
}
|
|
7708
8188
|
}
|
|
7709
|
-
let returnType, fromFile, selfClass, returnedFunctionResult, returnDefinition;
|
|
8189
|
+
let returnType, fromFile, selfClass, returnedFunctionResult, returnDefinition, moduleProducer;
|
|
7710
8190
|
const builtinCallReturn = !nominal && language === 'python'
|
|
7711
8191
|
? _pythonBuiltinCallReturnType(index, fileEntry, call) : null;
|
|
7712
8192
|
if (call.localValueCall && call.returnTypeHint) {
|
|
@@ -7732,10 +8212,14 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7732
8212
|
returnType = callableFlow.returnedFunctionResult;
|
|
7733
8213
|
fromFile = callableFlow.fromFile;
|
|
7734
8214
|
} else if (call.isMethod && call.receiverType &&
|
|
7735
|
-
!call.receiverTypeGuessed) {
|
|
8215
|
+
!call.receiverTypeGuessed && call.receiverTypeSource !== 'guess') {
|
|
7736
8216
|
const defs = index.symbols.get(call.name) || [];
|
|
7737
8217
|
if (nominal) {
|
|
7738
|
-
const
|
|
8218
|
+
const rustOwner = language === 'rust'
|
|
8219
|
+
? _rustFlowReceiverOrigin(index, filePath, call.receiverType, call.receiverTypeQualifier)
|
|
8220
|
+
: null;
|
|
8221
|
+
const matches = defs.filter(d => d.className === call.receiverType && d.returnType &&
|
|
8222
|
+
(language !== 'rust' || rustOwner?.fromFile));
|
|
7739
8223
|
if (matches.length > 0 && new Set(matches.map(d => d.returnType)).size === 1) {
|
|
7740
8224
|
returnType = matches[0].returnType;
|
|
7741
8225
|
fromFile = matches[0].file;
|
|
@@ -7846,6 +8330,15 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7846
8330
|
cls = next;
|
|
7847
8331
|
}
|
|
7848
8332
|
} else if (nominal && call.isMethod && call.isPathCall && call.receiver) {
|
|
8333
|
+
if (language === 'rust') {
|
|
8334
|
+
const producer = _rustModuleProducer(index, fileEntry, filePath, call);
|
|
8335
|
+
if (producer) {
|
|
8336
|
+
returnDefinition = producer.definition;
|
|
8337
|
+
returnType = returnDefinition.returnType;
|
|
8338
|
+
fromFile = returnDefinition.file;
|
|
8339
|
+
moduleProducer = producer.facts;
|
|
8340
|
+
}
|
|
8341
|
+
}
|
|
7849
8342
|
if (language === 'cpp') {
|
|
7850
8343
|
// C++ value-initialization through a qualified type name:
|
|
7851
8344
|
// `auto p = fmt::pipe()`. The portable tree-sitter AST uses
|
|
@@ -8327,6 +8820,18 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
8327
8820
|
}
|
|
8328
8821
|
const origin = _resolveFlowTypeOrigin(index, fromFile || filePath, parsed.name, parsed.qualifier);
|
|
8329
8822
|
if (!origin) {
|
|
8823
|
+
const wrapper = language === 'rust' && !call.assignedUnwrap &&
|
|
8824
|
+
_rustReturnWrapper(index, returnType, fromFile || filePath, selfClass,
|
|
8825
|
+
returnDefinition, [], true);
|
|
8826
|
+
if (wrapper && validateRustWrapperContract(wrapper, true)) {
|
|
8827
|
+
const scope = call.enclosingFunction ? `${call.enclosingFunction.startLine}` : '';
|
|
8828
|
+
const key = `${scope}:${call.assignedTo}`;
|
|
8829
|
+
if (!map) map = new Map();
|
|
8830
|
+
if (!map.has(key)) map.set(key, []);
|
|
8831
|
+
map.get(key).push({ line: call.line, start: call.callStart,
|
|
8832
|
+
rustWrapper: wrapper, rustReturnDeclaration: _rustReturnDeclaration(returnDefinition) });
|
|
8833
|
+
continue;
|
|
8834
|
+
}
|
|
8330
8835
|
// A project producer can return a package-qualified external
|
|
8331
8836
|
// Go type (`DefaultLogger(...) http.Handler`). That still
|
|
8332
8837
|
// gives compiler-grade provenance: the assigned receiver is
|
|
@@ -8373,6 +8878,12 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
8373
8878
|
if (!map.has(key)) map.set(key, []);
|
|
8374
8879
|
map.get(key).push({ line: call.line, start: call.callStart, type: typeName,
|
|
8375
8880
|
...(entryFromFile && { fromFile: entryFromFile }),
|
|
8881
|
+
...(moduleProducer && { moduleProducer }),
|
|
8882
|
+
...(language === 'rust' && returnDefinition && { rustReturnDeclaration: _rustReturnDeclaration(returnDefinition) }),
|
|
8883
|
+
...(language === 'rust' && !call.assignedUnwrap && {
|
|
8884
|
+
rustWrapper: _rustReturnWrapper(index, returnType, fromFile || filePath,
|
|
8885
|
+
selfClass, returnDefinition),
|
|
8886
|
+
}),
|
|
8376
8887
|
...(iteratorItemType && {
|
|
8377
8888
|
iteratorItemType,
|
|
8378
8889
|
iteratorItemFromFile,
|
|
@@ -8480,7 +8991,15 @@ function _lookupReturnTypeFlow(map, call) {
|
|
|
8480
8991
|
for (const e of entries) {
|
|
8481
8992
|
if (precedesCall(e) && laterThan(e, best)) best = e;
|
|
8482
8993
|
}
|
|
8483
|
-
if (best)
|
|
8994
|
+
if (best) {
|
|
8995
|
+
const binding = call.receiverTypeEvidence?.aliasBinding;
|
|
8996
|
+
// A non-call copy/rebinding is absent from the producer map.
|
|
8997
|
+
// Its newer AST declaration must still invalidate an older
|
|
8998
|
+
// factory payload with the same local variable name.
|
|
8999
|
+
if (binding?.target === call.receiver && Number.isInteger(binding.assignment?.start) &&
|
|
9000
|
+
(best.start == null || binding.assignment.start > best.start)) return undefined;
|
|
9001
|
+
return best.invalidated ? undefined : best;
|
|
9002
|
+
}
|
|
8484
9003
|
}
|
|
8485
9004
|
return undefined;
|
|
8486
9005
|
}
|
|
@@ -8555,6 +9074,59 @@ function _splitTopLevelGenericArgs(s) {
|
|
|
8555
9074
|
return out;
|
|
8556
9075
|
}
|
|
8557
9076
|
|
|
9077
|
+
function _rustReturnWrapper(index, text, file, selfClass, producer, projection = [], allowUnknownPayload = false) {
|
|
9078
|
+
if (!text || !file || !producer) return null;
|
|
9079
|
+
const contract = rustWrapperContract(index, text, file, {
|
|
9080
|
+
projection,
|
|
9081
|
+
allowUnknownPayload,
|
|
9082
|
+
parseType: value => _returnTypeNameNominal(value, 'rust', { selfClass }),
|
|
9083
|
+
resolveType: (context, name, qualifier) => _resolveFlowTypeOrigin(index, context, name, qualifier),
|
|
9084
|
+
});
|
|
9085
|
+
return contract && { ...contract, producer: { ...declarationIdentity(producer), returnType: producer.returnType } };
|
|
9086
|
+
}
|
|
9087
|
+
|
|
9088
|
+
function _rustReturnDeclaration(definition) {
|
|
9089
|
+
return { ...declarationIdentity(definition), file: definition.file,
|
|
9090
|
+
relativePath: definition.relativePath, returnType: definition.returnType };
|
|
9091
|
+
}
|
|
9092
|
+
|
|
9093
|
+
function _rustStandardWrapperMethod(index, file, call, calls) {
|
|
9094
|
+
if (!call.isMethod || !call.receiver || call.isPathCall ||
|
|
9095
|
+
call.receiverPatternShadow || call.receiverFlowInvalidated ||
|
|
9096
|
+
!['unwrap', 'expect'].includes(call.name)) return null;
|
|
9097
|
+
const flow = _lookupReturnTypeFlow(_buildReturnTypeFlowMap(index, file, calls), call);
|
|
9098
|
+
const contract = flow?.rustWrapper;
|
|
9099
|
+
if (contract && validateRustWrapperContract(contract, true)) {
|
|
9100
|
+
return { method: call.name, receiver: call.receiver, callKind: 'method',
|
|
9101
|
+
producer: { line: flow.line, start: flow.start }, contract };
|
|
9102
|
+
}
|
|
9103
|
+
// Ownership of a standard wrapper does not require knowing its payload:
|
|
9104
|
+
// `value: Result<T, E>` still uses Result's inherent unwrap. This negative
|
|
9105
|
+
// proof never types T or attributes its later methods to a project class.
|
|
9106
|
+
const type = call.receiverType;
|
|
9107
|
+
if (!['Result', 'Option'].includes(type) || call.receiverTypeSource !== 'annotation' ||
|
|
9108
|
+
call.receiverTypeEvidence?.source !== 'annotation' ||
|
|
9109
|
+
_isGenericParamReceiverType(index, file, call.line, type)) return null;
|
|
9110
|
+
const entry = index.files.get(file);
|
|
9111
|
+
const module = type === 'Result' ? 'result' : 'option';
|
|
9112
|
+
const standardPaths = [`std::${module}::${type}`, `core::${module}::${type}`];
|
|
9113
|
+
const qualifier = call.receiverTypeQualifier;
|
|
9114
|
+
const local = (index.symbols.get(type) || []).filter(d => d.file === file &&
|
|
9115
|
+
(IDENTITY_TYPE_KINDS.has(d.type) || d.type === 'type'));
|
|
9116
|
+
const bindings = (entry.importBindings || []).filter(b => (b.alias || b.name) === type);
|
|
9117
|
+
if (qualifier ? !standardPaths.includes(`${qualifier}::${type}`)
|
|
9118
|
+
: local.length || bindings.some(b => !standardPaths.includes(b.module))) return null;
|
|
9119
|
+
if ((entry.importBindings || []).some(b => b.name === '*' || b.module?.endsWith('::*'))) return null;
|
|
9120
|
+
const root = qualifier?.split('::')[0] || bindings[0]?.module.split('::')[0];
|
|
9121
|
+
if (root && ((index.symbols.get(root) || []).some(d => d.file === file) ||
|
|
9122
|
+
(entry.importBindings || []).some(b => (b.alias || b.name) === root))) return null;
|
|
9123
|
+
return { method: call.name, receiver: call.receiver, callKind: 'method',
|
|
9124
|
+
annotationReceiver: { type, origin: call.receiverTypeEvidence,
|
|
9125
|
+
qualifier: qualifier || null, localDeclarations: local.map(declarationIdentity),
|
|
9126
|
+
bindings, wildcardImports: [], rootDeclarations: [], rootBindings: [],
|
|
9127
|
+
genericParameter: false } };
|
|
9128
|
+
}
|
|
9129
|
+
|
|
8558
9130
|
function _splitTopLevelDelimiter(s, delimiter) {
|
|
8559
9131
|
const out = [];
|
|
8560
9132
|
let angle = 0, paren = 0, bracket = 0, brace = 0, cur = '';
|
|
@@ -8659,10 +9231,11 @@ function _returnTypeNameNominal(text, language, opts = {}) {
|
|
|
8659
9231
|
if (language === 'go') {
|
|
8660
9232
|
const qm = t.replace(/^\*+/, '').match(/^([A-Za-z_]\w*)\.([A-Za-z_]\w*)$/);
|
|
8661
9233
|
if (qm) qualifier = qm[1];
|
|
8662
|
-
} else if (language === 'cpp') {
|
|
8663
|
-
const stripped =
|
|
8664
|
-
.replace(
|
|
8665
|
-
.replace(/
|
|
9234
|
+
} else if (language === 'cpp' || language === 'rust') {
|
|
9235
|
+
const stripped = language === 'rust'
|
|
9236
|
+
? t.replace(/^&(?:\s*'\w+)?\s*/, '').replace(/^mut\s+/, '').trim()
|
|
9237
|
+
: t.replace(/\b(const|volatile|class|struct|typename)\b/g, '')
|
|
9238
|
+
.replace(/[*&]+/g, '').trim();
|
|
8666
9239
|
const genericStart = stripped.indexOf('<');
|
|
8667
9240
|
const head = genericStart >= 0
|
|
8668
9241
|
? stripped.slice(0, genericStart).trim() : stripped;
|
|
@@ -8807,6 +9380,35 @@ function _rustImportedTypeIdentity(index, filePath, localName) {
|
|
|
8807
9380
|
* trusted (a use/import of an external type can shadow it invisibly)
|
|
8808
9381
|
* - no project type def at all: external name — safe, can't conflate
|
|
8809
9382
|
*/
|
|
9383
|
+
function _rustFlowReceiverOrigin(index, file, name, qualifier) {
|
|
9384
|
+
const entry = index.files.get(file);
|
|
9385
|
+
// An annotation names a type in this module's scope. A same-directory
|
|
9386
|
+
// project Result/Option declaration does not shadow the standard prelude
|
|
9387
|
+
// of another module, nor does an unrelated imported file bind its names.
|
|
9388
|
+
const local = (index.symbols.get(name) || []).some(d => d.file === file &&
|
|
9389
|
+
(IDENTITY_TYPE_KINDS.has(d.type) || (d.type === 'type' && d.aliasOf)));
|
|
9390
|
+
const bound = (entry?.importBindings || []).some(b => (b.alias || b.name) === name);
|
|
9391
|
+
if (!qualifier && !local && !bound) return null;
|
|
9392
|
+
return _resolveFlowTypeOrigin(index, file, name, qualifier);
|
|
9393
|
+
}
|
|
9394
|
+
|
|
9395
|
+
function _rustSameNameAliasOrigin(index, definition, seen = new Set()) {
|
|
9396
|
+
if (!definition || seen.has(definition) || seen.size >= 8) return null;
|
|
9397
|
+
seen.add(definition);
|
|
9398
|
+
if (definition.type !== 'type') return { fromFile: definition.file };
|
|
9399
|
+
if (!definition.aliasTypeText) return null;
|
|
9400
|
+
const parsed = _returnTypeNameNominal(definition.aliasTypeText, 'rust');
|
|
9401
|
+
if (parsed?.name !== definition.name || !parsed.qualifier) return null;
|
|
9402
|
+
const relative = index.files.get(definition.file)?.moduleResolved?.[parsed.qualifier];
|
|
9403
|
+
const resolved = relative ? path.resolve(index.root, relative)
|
|
9404
|
+
: resolveRustImport(`${parsed.qualifier}::${parsed.name}`, definition.file, index.root) ||
|
|
9405
|
+
resolveRustImport(parsed.qualifier, definition.file, index.root);
|
|
9406
|
+
if (!resolved) return null;
|
|
9407
|
+
const targets = (index.symbols.get(parsed.name) || []).filter(d =>
|
|
9408
|
+
IDENTITY_TYPE_KINDS.has(d.type) && d.file === resolved);
|
|
9409
|
+
return targets.length === 1 ? _rustSameNameAliasOrigin(index, targets[0], seen) : null;
|
|
9410
|
+
}
|
|
9411
|
+
|
|
8810
9412
|
function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier = undefined) {
|
|
8811
9413
|
const opCache = index._opFlowTypeOriginCache;
|
|
8812
9414
|
const cacheKey = `${producerFile}\x00${typeName}\x00${qualifier || ''}`;
|
|
@@ -8863,6 +9465,13 @@ function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier = undef
|
|
|
8863
9465
|
const resolved = resolveRustImport(
|
|
8864
9466
|
`${qualifier}::${typeName}`, producerFile, index.root);
|
|
8865
9467
|
if (resolved) {
|
|
9468
|
+
const local = typeDefs.filter(d => d.file === resolved);
|
|
9469
|
+
if (local.length === 1) {
|
|
9470
|
+
const direct = local[0].type === 'type' && local[0].aliasOf === typeName
|
|
9471
|
+
? _rustSameNameAliasOrigin(index, local[0])
|
|
9472
|
+
: { fromFile: local[0].file };
|
|
9473
|
+
if (direct) return finish(direct);
|
|
9474
|
+
}
|
|
8866
9475
|
const reachable = typeDefs.filter(d =>
|
|
8867
9476
|
d.file === resolved ||
|
|
8868
9477
|
_importReaches(index, resolved, new Set([d.file])));
|
|
@@ -9052,22 +9661,7 @@ const JS_GLOBAL_RECEIVERS = new Set([
|
|
|
9052
9661
|
'crypto', 'performance', 'history', 'location', 'screen',
|
|
9053
9662
|
]);
|
|
9054
9663
|
|
|
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
|
-
]);
|
|
9664
|
+
|
|
9071
9665
|
|
|
9072
9666
|
// Universal-contract method names (fix #265, hono-measured: 183 untyped
|
|
9073
9667
|
// `x.toString()` calls confirmed against JSXNode.toString via the single-
|
|
@@ -10959,6 +11553,60 @@ function _pythonBuiltinContractAllowed(index, fileEntry, moduleName) {
|
|
|
10959
11553
|
return !_unresolvedModuleIsGap(index, module);
|
|
10960
11554
|
}
|
|
10961
11555
|
|
|
11556
|
+
function _pythonExternalFieldFlow(index, file, call) {
|
|
11557
|
+
const field = call.receiverRoot === 'self' && (!call.receiverFields || call.receiverFields.length === 1)
|
|
11558
|
+
? call.receiverField : call.receiver?.startsWith('self.') && call.receiver.split('.').length === 2
|
|
11559
|
+
? call.receiver.slice(5) : null;
|
|
11560
|
+
if (!field || call.receiverFlowInvalidated) return null;
|
|
11561
|
+
const enclosing = index.findEnclosingFunction(file, call.line, true);
|
|
11562
|
+
if (!enclosing?.className) return null;
|
|
11563
|
+
const owner = (index.symbols.get(enclosing.className) || []).filter(d => d.file === file && d.type === 'class');
|
|
11564
|
+
if (owner.length !== 1) return null;
|
|
11565
|
+
const flow = getInstanceAttributeTypes(index, file, enclosing.className)?.externalFlows?.get(field);
|
|
11566
|
+
if (!flow) return null;
|
|
11567
|
+
const producer = flow.assignments[0];
|
|
11568
|
+
return { via: `${producer.module}.${producer.producer}${'()'.repeat(producer.depth)}`,
|
|
11569
|
+
proof: { ...flow, owner: declarationIdentity(owner[0]), enclosing: declarationIdentity(enclosing) } };
|
|
11570
|
+
}
|
|
11571
|
+
|
|
11572
|
+
function _pythonFixtureReceiverType(index, file, call) {
|
|
11573
|
+
const resolveType = (context, name, qualifier) => {
|
|
11574
|
+
const root = qualifier || name, entry = index.files.get(context);
|
|
11575
|
+
if ((entry.moduleAssignedNames || []).includes(root) ||
|
|
11576
|
+
(entry.importBindings || []).filter(b => (b.alias || b.name) === root).length > 1 ||
|
|
11577
|
+
(qualifier && (index.symbols.get(root) || []).some(d => d.file === context))) return null;
|
|
11578
|
+
return pythonFixtureType(index, context, qualifier ? `${qualifier}.${name}` : name,
|
|
11579
|
+
d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
11580
|
+
};
|
|
11581
|
+
return pythonFixtureReceiver(index, file, call, {
|
|
11582
|
+
resolveType,
|
|
11583
|
+
externalModule: (context, module) => _pythonBuiltinContractAllowed(index, index.files.get(context), module),
|
|
11584
|
+
field(type, context, field) {
|
|
11585
|
+
const owner = resolveType(context, type)?.declaration;
|
|
11586
|
+
if (!owner) return null;
|
|
11587
|
+
const members = (index.symbols.get(field) || []).filter(d => d.className === type && d.file === owner.file &&
|
|
11588
|
+
['property', 'getter', 'field'].includes(d.type));
|
|
11589
|
+
if (members.length !== 1) return null;
|
|
11590
|
+
const member = members[0], text = member.returnType || member.fieldType;
|
|
11591
|
+
const result = _structuralTypeExpression(text);
|
|
11592
|
+
if (!result || result.args.length) return null;
|
|
11593
|
+
const qualifier = result.qualifiedHead.includes('.')
|
|
11594
|
+
? result.qualifiedHead.slice(0, result.qualifiedHead.lastIndexOf('.')) : undefined;
|
|
11595
|
+
const next = resolveType(member.file, result.head, qualifier);
|
|
11596
|
+
const builtin = !qualifier && !next && isProvenanceBuiltinReceiver(result.head, 'python') &&
|
|
11597
|
+
!(index.files.get(member.file).importBindings || []).some(b => (b.alias || b.name) === result.head) &&
|
|
11598
|
+
!(index.files.get(member.file).moduleAssignedNames || []).includes(result.head) &&
|
|
11599
|
+
!(index.symbols.get(result.head) || []).some(d => d.file === member.file);
|
|
11600
|
+
if (!next && !builtin) return null;
|
|
11601
|
+
return { type: result.head, fromFile: next?.declaration.file,
|
|
11602
|
+
fact: { owner: declarationIdentity(owner), member: declarationIdentity(member), annotation: text,
|
|
11603
|
+
result: next ? declarationIdentity(next.declaration) : { builtin: result.head, language: 'python' },
|
|
11604
|
+
...(builtin && { builtinShadowDeclarations: [], builtinShadowBindings: [] }),
|
|
11605
|
+
importChain: next?.chain || [] } };
|
|
11606
|
+
},
|
|
11607
|
+
});
|
|
11608
|
+
}
|
|
11609
|
+
|
|
10962
11610
|
function _structuralImportedReceiverType(index, fileEntry, receiver) {
|
|
10963
11611
|
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
10964
11612
|
binding.name === receiver || binding.alias === receiver);
|
|
@@ -10981,7 +11629,7 @@ function _structuralImportedReceiverType(index, fileEntry, receiver) {
|
|
|
10981
11629
|
return { type: receiver, fromFile: definition.file };
|
|
10982
11630
|
}
|
|
10983
11631
|
|
|
10984
|
-
function _pythonBuiltinCallReturnType(index, fileEntry, call) {
|
|
11632
|
+
function _pythonBuiltinCallReturnType(index, fileEntry, call, evidence = null) {
|
|
10985
11633
|
if (fileEntry?.language !== 'python') return null;
|
|
10986
11634
|
const adapter = getLanguageAdapter('python');
|
|
10987
11635
|
if (typeof adapter?.getBuiltinCallReturnType !== 'function') return null;
|
|
@@ -10998,7 +11646,10 @@ function _pythonBuiltinCallReturnType(index, fileEntry, call) {
|
|
|
10998
11646
|
for (const binding of bindings) {
|
|
10999
11647
|
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) continue;
|
|
11000
11648
|
const type = adapter.getBuiltinCallReturnType(binding.module, call.name);
|
|
11001
|
-
if (type)
|
|
11649
|
+
if (type) {
|
|
11650
|
+
types.add(type);
|
|
11651
|
+
if (evidence) (evidence.bindings ||= []).push({ ...binding, returnType: type });
|
|
11652
|
+
}
|
|
11002
11653
|
}
|
|
11003
11654
|
return types.size === 1 ? [...types][0] : null;
|
|
11004
11655
|
}
|
|
@@ -11010,21 +11661,30 @@ function _pythonBuiltinFieldPathType(index, fileEntry, root, fields) {
|
|
|
11010
11661
|
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
11011
11662
|
binding.name === root || binding.alias === root);
|
|
11012
11663
|
const types = new Set();
|
|
11664
|
+
const contracts = [];
|
|
11013
11665
|
for (const binding of bindings) {
|
|
11014
11666
|
if (!_pythonBuiltinContractAllowed(index, fileEntry, binding.module)) continue;
|
|
11015
11667
|
const type = adapter.getBuiltinFieldType(binding.module, fields[0]);
|
|
11016
|
-
if (type)
|
|
11668
|
+
if (type) {
|
|
11669
|
+
types.add(type);
|
|
11670
|
+
contracts.push({ ...binding, field: fields[0], type });
|
|
11671
|
+
}
|
|
11017
11672
|
}
|
|
11018
|
-
return types.size === 1 ? [...types][0] : null;
|
|
11673
|
+
return types.size === 1 ? { type: [...types][0], root, fields, bindings: contracts } : null;
|
|
11019
11674
|
}
|
|
11020
11675
|
|
|
11021
11676
|
function _pythonBuiltinChainedReceiverType(index, fileEntry, call, foldCtx) {
|
|
11022
11677
|
if (fileEntry?.language !== 'python') return null;
|
|
11023
11678
|
const producers = _chainedProducerRecords(foldCtx, call);
|
|
11024
11679
|
if (producers.length === 0) return null;
|
|
11025
|
-
const
|
|
11026
|
-
|
|
11027
|
-
|
|
11680
|
+
const witnesses = producers.map(producer => {
|
|
11681
|
+
const evidence = { name: producer.name, line: producer.line, site: producer.callSite };
|
|
11682
|
+
const type = _pythonBuiltinCallReturnType(index, fileEntry, producer, evidence);
|
|
11683
|
+
return { type, ...evidence };
|
|
11684
|
+
});
|
|
11685
|
+
const types = witnesses.map(witness => witness.type);
|
|
11686
|
+
return types.every(Boolean) && new Set(types).size === 1
|
|
11687
|
+
? { type: types[0], producers: witnesses } : null;
|
|
11028
11688
|
}
|
|
11029
11689
|
|
|
11030
11690
|
/**
|
|
@@ -11127,6 +11787,7 @@ function _calleeExportDefinitions(index, startAbs, exposedName, language, call,
|
|
|
11127
11787
|
let unknown = false;
|
|
11128
11788
|
let frontier = [[startAbs, exposedName]];
|
|
11129
11789
|
const shapeMatches = d =>
|
|
11790
|
+
(language !== 'rust' || !(d.className || d.receiver)) &&
|
|
11130
11791
|
(!NON_CALLABLE_TYPES.has(d.type) ||
|
|
11131
11792
|
(call.isConstructor && d.type === 'class') ||
|
|
11132
11793
|
(langTraits(language)?.classesCallableWithoutNew && d.type === 'class')) &&
|
|
@@ -11264,6 +11925,19 @@ function _calleeGoPackageMatch(index, call, importModule) {
|
|
|
11264
11925
|
return bestMatch;
|
|
11265
11926
|
}
|
|
11266
11927
|
|
|
11928
|
+
function selectProvenanceOverload(index, call, candidates, language) {
|
|
11929
|
+
const remaining = new Set(candidates);
|
|
11930
|
+
const slots = [];
|
|
11931
|
+
for (const definition of candidates) {
|
|
11932
|
+
if (!remaining.has(definition)) continue;
|
|
11933
|
+
const group = _closeCallableIdentityGroup(index, [definition], candidates);
|
|
11934
|
+
const representative = group.find(d => !d.isSignature && !d.isDeclaration) || definition;
|
|
11935
|
+
slots.push(representative);
|
|
11936
|
+
for (const member of group) remaining.delete(member);
|
|
11937
|
+
}
|
|
11938
|
+
return _calleeOverloadSelect(index, call, slots, language);
|
|
11939
|
+
}
|
|
11940
|
+
|
|
11267
11941
|
function _calleeOverloadSelect(index, call, matches, language) {
|
|
11268
11942
|
if (matches.length === 0) return { match: null };
|
|
11269
11943
|
if (call.argCount == null || call.argSpread) {
|
|
@@ -11580,6 +12254,7 @@ function _declaredFieldType(
|
|
|
11580
12254
|
className: rootType,
|
|
11581
12255
|
fieldType,
|
|
11582
12256
|
file: owner.file,
|
|
12257
|
+
...(attrs.origins?.get(fieldName) && { fieldOrigin: attrs.origins.get(fieldName) }),
|
|
11583
12258
|
});
|
|
11584
12259
|
}
|
|
11585
12260
|
if (complete && inferred.length > 0) {
|
|
@@ -11642,6 +12317,7 @@ function _declaredFieldType(
|
|
|
11642
12317
|
const aliasBase = _pureAliasBase(index, typeName);
|
|
11643
12318
|
if (aliasBase) typeName = aliasBase;
|
|
11644
12319
|
if (info) {
|
|
12320
|
+
if (onType.length === 1 && onType[0].fieldOrigin) Object.assign(info, onType[0].fieldOrigin);
|
|
11645
12321
|
const origins = new Set();
|
|
11646
12322
|
const namespaces = new Set();
|
|
11647
12323
|
let complete = true;
|
|
@@ -12309,7 +12985,14 @@ function _calleeSingleOwnerMatch(index, def, fileEntry, call, name, language, fl
|
|
|
12309
12985
|
if (matchFe && callerFe &&
|
|
12310
12986
|
(isTestFile(matchFe.relativePath, matchFe.language) || isTestPath(matchFe.relativePath)) &&
|
|
12311
12987
|
!(isTestFile(callerFe.relativePath, callerFe.language) || isTestPath(callerFe.relativePath))) return null;
|
|
12312
|
-
|
|
12988
|
+
const facts = _confirmationFacts(index, def.file, call, [match], {
|
|
12989
|
+
...(flowEntry?.type && !call.receiverType && {
|
|
12990
|
+
receiverType: flowEntry.type, receiverTypeSource: 'flow',
|
|
12991
|
+
receiverOrigin: { source: 'flow', ...flowEntry }, originFile: flowEntry.fromFile,
|
|
12992
|
+
}),
|
|
12993
|
+
});
|
|
12994
|
+
return validateConfirmation({ facts }, facts.targets).verdict === 'establishes-target'
|
|
12995
|
+
? match : null;
|
|
12313
12996
|
}
|
|
12314
12997
|
|
|
12315
12998
|
const _CSHARP_TYPE_ALIASES = new Map([
|
|
@@ -15111,8 +15794,18 @@ function _methodReturnOnType(index, typeName, fromFile, methodName, language, op
|
|
|
15111
15794
|
const parsed = _returnTypeNameNominal(def.returnType, language, { selfClass: selfType });
|
|
15112
15795
|
if (!parsed) return null;
|
|
15113
15796
|
const origin = _resolveFlowTypeOrigin(index, def.file || opts.filePath, parsed.name, parsed.qualifier);
|
|
15114
|
-
|
|
15115
|
-
|
|
15797
|
+
const wrappers = language === 'rust' ? owned.map(candidate =>
|
|
15798
|
+
_rustReturnWrapper(index, candidate.returnType, candidate.file || opts.filePath,
|
|
15799
|
+
selfType, candidate, [], true)) : [];
|
|
15800
|
+
// Identical annotation text in different impl files can bind different
|
|
15801
|
+
// aliases. Every possible producer must agree on the payload identity.
|
|
15802
|
+
const rustWrapper = wrappers.length && wrappers.every(wrapper => wrapper &&
|
|
15803
|
+
wrapper.kind === wrappers[0].kind && wrapper.type === wrappers[0].type &&
|
|
15804
|
+
wrapper.fromFile === wrappers[0].fromFile) ? wrappers[0] : null;
|
|
15805
|
+
if (!origin && !rustWrapper) return null;
|
|
15806
|
+
return { ...(origin && { type: parsed.name }), ...(origin?.fromFile && { fromFile: origin.fromFile }),
|
|
15807
|
+
...(language === 'rust' && owned.length === 1 && { rustReturnDeclaration: _rustReturnDeclaration(def) }),
|
|
15808
|
+
...(rustWrapper && { rustWrapper }) };
|
|
15116
15809
|
}
|
|
15117
15810
|
// Structural: heads must agree; `this`/`Self` are the receiver's type
|
|
15118
15811
|
// (checked BEFORE the reject set — with a known owner they ARE identity);
|
|
@@ -15266,7 +15959,72 @@ function _rustPathIsKnownExternal(index, fileEntry, filePath, receiver, name) {
|
|
|
15266
15959
|
`${segments.join('::')}::${name}`, filePath, index.root);
|
|
15267
15960
|
}
|
|
15268
15961
|
|
|
15269
|
-
function
|
|
15962
|
+
/** A qualified free function belongs to its exact module, never an impl
|
|
15963
|
+
* whose type or method happens to share either path component. Re-exports
|
|
15964
|
+
* and inline modules abstain until their complete item path is available.
|
|
15965
|
+
*/
|
|
15966
|
+
function _rustModuleProducer(index, fileEntry, filePath, call) {
|
|
15967
|
+
if (!call.isPathCall || !call.receiver || call.receiverType || call.localShadow) return null;
|
|
15968
|
+
const parts = call.receiver.split('::');
|
|
15969
|
+
const root = parts[0];
|
|
15970
|
+
const bindings = (fileEntry.importBindings || []).filter(b => (b.alias || b.name) === root);
|
|
15971
|
+
const locals = (index.symbols.get(root) || []).filter(d => d.file === filePath);
|
|
15972
|
+
if (locals.some(d => d.type !== 'module') || bindings.length > 1) return null;
|
|
15973
|
+
const binding = bindings[0];
|
|
15974
|
+
const specifier = binding ? [binding.module, ...parts.slice(1)].join('::') : call.receiver;
|
|
15975
|
+
const terminal = specifier.split('::').at(-1);
|
|
15976
|
+
if (!terminal || ['Self', 'self', 'super', 'crate'].includes(terminal)) return null;
|
|
15977
|
+
const relative = fileEntry.moduleResolved?.[specifier];
|
|
15978
|
+
const destination = relative ? path.resolve(index.root, relative)
|
|
15979
|
+
: resolveRustImport(specifier, filePath, index.root);
|
|
15980
|
+
if (!destination || !index.files.has(destination) || destination === filePath) return null;
|
|
15981
|
+
const base = path.basename(destination, '.rs');
|
|
15982
|
+
if ((base === 'mod' ? path.basename(path.dirname(destination)) : base) !== terminal) return null;
|
|
15983
|
+
const candidates = (index.symbols.get(call.name) || []).filter(d => d.file === destination &&
|
|
15984
|
+
!NON_CALLABLE_TYPES.has(d.type) && !d.className && !d.receiver && !d.namespace);
|
|
15985
|
+
if (candidates.length !== 1 || !candidates[0].returnType) return null;
|
|
15986
|
+
const definition = candidates[0];
|
|
15987
|
+
if ((index.files.get(destination).symbols || []).some(d => d.type === 'module' &&
|
|
15988
|
+
d.startLine < definition.startLine && d.endLine >= definition.endLine)) return null;
|
|
15989
|
+
return { definition, facts: {
|
|
15990
|
+
call: { file: fileEntry.relativePath, line: call.line, start: call.callStart,
|
|
15991
|
+
end: call.callEnd, receiver: call.receiver, name: call.name },
|
|
15992
|
+
binding: binding || null, module: { specifier, file: path.relative(index.root, destination) },
|
|
15993
|
+
declaration: { ...declarationIdentity(definition), returnType: definition.returnType },
|
|
15994
|
+
} };
|
|
15995
|
+
}
|
|
15996
|
+
|
|
15997
|
+
function _rustPatternReceiverType(index, fileEntry, filePath, record, ctx) {
|
|
15998
|
+
if (['Some', 'Ok'].includes(record.receiverPatternVariant) && !record.receiverPatternOwner &&
|
|
15999
|
+
(record.receiverPatternIndex || 0) === 0 &&
|
|
16000
|
+
(record.receiverPatternSourceCallStart != null || record.receiverPatternSourceVariable)) {
|
|
16001
|
+
if (!ctx) {
|
|
16002
|
+
const records = index.getCachedCalls(filePath);
|
|
16003
|
+
ctx = { records, memo: new Map(), visiting: new Set(),
|
|
16004
|
+
getFlowMap: () => _buildReturnTypeFlowMap(index, filePath, records) };
|
|
16005
|
+
}
|
|
16006
|
+
const shadow = (index.symbols.get(record.receiverPatternVariant) || []).some(d => d.file === filePath) ||
|
|
16007
|
+
(fileEntry.importBindings || []).some(b => (b.alias || b.name) === record.receiverPatternVariant || b.name === '*');
|
|
16008
|
+
const sources = record.receiverPatternSourceVariable
|
|
16009
|
+
? [_lookupReturnTypeFlow(ctx.getFlowMap(), { ...record, receiver: record.receiverPatternSourceVariable,
|
|
16010
|
+
receiverPatternShadow: false })]
|
|
16011
|
+
: ctx.records.filter(source => source.callStart === record.receiverPatternSourceCallStart &&
|
|
16012
|
+
source.callEnd === record.receiverPatternSourceCallEnd).map(source =>
|
|
16013
|
+
_typeOfCallResultFold(index, fileEntry, filePath, source, ctx));
|
|
16014
|
+
const producer = sources.length === 1 && sources[0]?.rustReturnDeclaration;
|
|
16015
|
+
const contract = !shadow && producer && _rustReturnWrapper(index, producer.returnType, producer.file,
|
|
16016
|
+
producer.className, producer, record.receiverPatternProjection || []);
|
|
16017
|
+
if (contract && validateRustWrapperContract(contract) &&
|
|
16018
|
+
contract.kind === (record.receiverPatternVariant === 'Some' ? 'Option' : 'Result')) {
|
|
16019
|
+
return { type: contract.type, fromFile: contract.fromFile,
|
|
16020
|
+
wrapperPattern: { variant: record.receiverPatternVariant, contract,
|
|
16021
|
+
source: { start: record.receiverPatternSourceCallStart, end: record.receiverPatternSourceCallEnd,
|
|
16022
|
+
variable: record.receiverPatternSourceVariable }, shadowDeclarations: [], shadowBindings: [] } };
|
|
16023
|
+
}
|
|
16024
|
+
// A tuple payload cannot be treated as its outer type by the older
|
|
16025
|
+
// whole-payload path below.
|
|
16026
|
+
if (record.receiverPatternProjection?.length) return null;
|
|
16027
|
+
}
|
|
15270
16028
|
let variants = (index.symbols.get(record.receiverPatternVariant) || [])
|
|
15271
16029
|
.filter(definition => definition.type === 'variant');
|
|
15272
16030
|
if (record.receiverPatternOwner) {
|
|
@@ -15356,7 +16114,15 @@ const _RUST_ITERATOR_ITEM_CALLBACKS = new Set([
|
|
|
15356
16114
|
]);
|
|
15357
16115
|
|
|
15358
16116
|
function _rustRecordReceiverType(index, fileEntry, filePath, record, ctx) {
|
|
15359
|
-
if (record.
|
|
16117
|
+
if (record.receiver === 'self') {
|
|
16118
|
+
const enclosing = index.findEnclosingFunction(filePath, record.line, true);
|
|
16119
|
+
if (enclosing?.className) {
|
|
16120
|
+
const origin = _resolveFlowTypeOrigin(index, filePath, enclosing.className);
|
|
16121
|
+
if (origin?.fromFile) return { type: enclosing.className, fromFile: origin.fromFile };
|
|
16122
|
+
}
|
|
16123
|
+
}
|
|
16124
|
+
if (record.receiverType && !record.receiverIsChainRoot &&
|
|
16125
|
+
!record.receiverTypeGuessed && record.receiverTypeSource !== 'guess') {
|
|
15360
16126
|
const origin = _resolveFlowTypeOrigin(
|
|
15361
16127
|
index, filePath, record.receiverType, record.receiverTypeQualifier);
|
|
15362
16128
|
if (origin?.fromFile) {
|
|
@@ -15383,9 +16149,13 @@ function _rustRecordReceiverType(index, fileEntry, filePath, record, ctx) {
|
|
|
15383
16149
|
}
|
|
15384
16150
|
if (record.receiverPatternVariant) {
|
|
15385
16151
|
const pattern = _rustPatternReceiverType(
|
|
15386
|
-
index, fileEntry, filePath, record);
|
|
16152
|
+
index, fileEntry, filePath, record, ctx);
|
|
15387
16153
|
if (pattern) return pattern;
|
|
15388
16154
|
}
|
|
16155
|
+
if (record.enclosingFunction?.closureParameterNames?.includes(record.receiver)) {
|
|
16156
|
+
const parameter = _rustClosureReceiverType(index, fileEntry, filePath, record, ctx);
|
|
16157
|
+
if (parameter) return parameter;
|
|
16158
|
+
}
|
|
15389
16159
|
if (record.receiver && !record.receiverIsChainRoot &&
|
|
15390
16160
|
!record.receiverPatternShadow) {
|
|
15391
16161
|
const flow = _lookupReturnTypeFlow(ctx.getFlowMap(), record);
|
|
@@ -15485,6 +16255,35 @@ const _RUST_ITERATOR_ITEM_PRESERVING = new Set([
|
|
|
15485
16255
|
'skip', 'skip_while', 'step_by', 'take', 'take_while',
|
|
15486
16256
|
]);
|
|
15487
16257
|
|
|
16258
|
+
function _rustIterationReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
16259
|
+
let items = [];
|
|
16260
|
+
let producers = [];
|
|
16261
|
+
if (call.receiverIterationVariable) {
|
|
16262
|
+
const flow = _lookupReturnTypeFlow(ctx.getFlowMap(), {
|
|
16263
|
+
...call, receiver: call.receiverIterationVariable,
|
|
16264
|
+
});
|
|
16265
|
+
if (flow?.iteratorItemType) {
|
|
16266
|
+
items = [{ type: flow.iteratorItemType, fromFile: flow.iteratorItemFromFile }];
|
|
16267
|
+
producers = [{ ...flow }];
|
|
16268
|
+
}
|
|
16269
|
+
} else {
|
|
16270
|
+
const sources = _chainedProducerRecords(ctx, {
|
|
16271
|
+
receiverCall: call.receiverIterationCall,
|
|
16272
|
+
receiverCallIsMethod: call.receiverIterationCallIsMethod,
|
|
16273
|
+
receiverCallLine: call.receiverIterationCallLine,
|
|
16274
|
+
receiverCallStart: call.receiverIterationCallStart,
|
|
16275
|
+
receiverCallEnd: call.receiverIterationCallEnd,
|
|
16276
|
+
});
|
|
16277
|
+
items = sources.map(source => _rustIteratorOutputItemType(index, fileEntry, filePath, source, ctx));
|
|
16278
|
+
producers = sources.map(source => ({ name: source.name,
|
|
16279
|
+
site: occurrenceIdentity(fileEntry.relativePath, source) }));
|
|
16280
|
+
}
|
|
16281
|
+
if (!items.length || items.some(item => !item?.fromFile) ||
|
|
16282
|
+
new Set(items.map(item => item.type)).size !== 1 ||
|
|
16283
|
+
new Set(items.map(item => item.fromFile)).size !== 1) return null;
|
|
16284
|
+
return { ...items[0], alternatives: items, producers };
|
|
16285
|
+
}
|
|
16286
|
+
|
|
15488
16287
|
function _rustIteratorOutputItemType(index, fileEntry, filePath, source, ctx, visiting = new Set()) {
|
|
15489
16288
|
if (!source || visiting.has(source)) return null;
|
|
15490
16289
|
visiting.add(source);
|
|
@@ -15731,6 +16530,16 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
15731
16530
|
externalConcrete: true,
|
|
15732
16531
|
};
|
|
15733
16532
|
}
|
|
16533
|
+
const module = language === 'rust' ? _rustModuleProducer(index, fileEntry, filePath, record) : null;
|
|
16534
|
+
if (module) {
|
|
16535
|
+
const producer = module.definition;
|
|
16536
|
+
const parsed = _returnTypeNameNominal(producer.returnType, language);
|
|
16537
|
+
const origin = parsed && _resolveFlowTypeOrigin(index, producer.file, parsed.name, parsed.qualifier);
|
|
16538
|
+
if (!origin) return null;
|
|
16539
|
+
return { type: parsed.name, fromFile: origin.fromFile, moduleProducer: module.facts,
|
|
16540
|
+
rustReturnDeclaration: _rustReturnDeclaration(producer),
|
|
16541
|
+
rustWrapper: _rustReturnWrapper(index, producer.returnType, producer.file, null, producer) };
|
|
16542
|
+
}
|
|
15734
16543
|
const segs = String(record.receiver).split('::');
|
|
15735
16544
|
let seg = segs.pop();
|
|
15736
16545
|
if (seg === 'Self') {
|
|
@@ -15872,11 +16681,15 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
15872
16681
|
if (record.isMethod) {
|
|
15873
16682
|
let rt = _goBuiltinChainedReceiverType(
|
|
15874
16683
|
index, fileEntry, filePath, record);
|
|
15875
|
-
if (record.receiverType && !record.receiverIsChainRoot
|
|
15876
|
-
|
|
16684
|
+
if (record.receiverType && !record.receiverIsChainRoot &&
|
|
16685
|
+
!record.receiverTypeGuessed && record.receiverTypeSource !== 'guess') {
|
|
16686
|
+
const origin = language === 'rust'
|
|
16687
|
+
? _rustFlowReceiverOrigin(index, filePath, record.receiverType, record.receiverTypeQualifier)
|
|
16688
|
+
: nominal
|
|
15877
16689
|
? _resolveFlowTypeOrigin(index, filePath, record.receiverType,
|
|
15878
16690
|
record.receiverTypeQualifier)
|
|
15879
16691
|
: null;
|
|
16692
|
+
if (language === 'rust' && !origin?.fromFile) return null;
|
|
15880
16693
|
rt = {
|
|
15881
16694
|
type: record.receiverType,
|
|
15882
16695
|
...(origin?.fromFile && { fromFile: origin.fromFile }),
|
|
@@ -16073,7 +16886,8 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
16073
16886
|
if (!parsed) return null;
|
|
16074
16887
|
const origin = _resolveFlowTypeOrigin(index, chosen.file || filePath, parsed.name, parsed.qualifier);
|
|
16075
16888
|
if (!origin) return null;
|
|
16076
|
-
return { type: parsed.name, ...(origin.fromFile && { fromFile: origin.fromFile })
|
|
16889
|
+
return { type: parsed.name, ...(origin.fromFile && { fromFile: origin.fromFile }),
|
|
16890
|
+
...(language === 'rust' && chosen.file === filePath && { rustReturnDeclaration: _rustReturnDeclaration(chosen) }) };
|
|
16077
16891
|
}
|
|
16078
16892
|
if (language === 'python' && !consumerAwaited && chosen.isAsync) return null;
|
|
16079
16893
|
let head = _structuralTypeHead(chosen.returnType, {
|
|
@@ -16167,6 +16981,9 @@ function _foldChainedReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
16167
16981
|
const typeTexts = new Set(results.map(r => r.typeText));
|
|
16168
16982
|
let result = {
|
|
16169
16983
|
type: results[0].type,
|
|
16984
|
+
...(results.length === 1 && results[0].moduleProducer && {
|
|
16985
|
+
moduleProducer: results[0].moduleProducer,
|
|
16986
|
+
}),
|
|
16170
16987
|
...(typeTexts.size === 1 && results[0].typeText && {
|
|
16171
16988
|
typeText: results[0].typeText,
|
|
16172
16989
|
}),
|
|
@@ -16317,4 +17134,4 @@ function findCallbackUsages(index, name) {
|
|
|
16317
17134
|
return usages;
|
|
16318
17135
|
}
|
|
16319
17136
|
|
|
16320
|
-
module.exports = { _unresolvedModuleIsGap, _importReaches, _sameNominalPackageDir, getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|
|
17137
|
+
module.exports = { isProvenanceBuiltinReceiver, provenanceParameterIdentity: _overloadTypeIdentity, selectProvenanceOverload, _unresolvedModuleIsGap, _importReaches, _sameNominalPackageDir, getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|