ucn 5.2.1 → 5.3.0
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 +49 -3
- package/.claude/skills/ucn/references/commands.md +3 -1
- package/README.md +158 -533
- package/cli/index.js +74 -10
- package/core/account.js +36 -8
- package/core/cache.js +167 -21
- package/core/callers.js +1229 -162
- package/core/execute.js +24 -6
- package/core/graph.js +167 -35
- package/core/index-ir.js +17 -12
- package/core/ir.js +56 -8
- package/core/output/graph.js +60 -11
- package/core/output/lines.js +259 -0
- package/core/output/public.js +15 -0
- package/core/output/reporting.js +9 -2
- package/core/output-budget.js +7 -4
- package/core/project.js +103 -5
- package/core/registry.js +7 -6
- package/core/reporting.js +159 -14
- package/languages/c-family.js +19 -17
- package/languages/go.js +170 -42
- package/languages/javascript.js +470 -15
- package/languages/python.js +678 -38
- package/languages/rust.js +1 -0
- package/mcp/server.js +3 -1
- package/package.json +2 -2
- package/assets/demo.svg +0 -31
package/core/callers.js
CHANGED
|
@@ -20,6 +20,15 @@ const CONSTRUCTABLE_BINDING_KINDS = new Set([
|
|
|
20
20
|
const RESERVED_RECEIVER_NAMES = new Set([
|
|
21
21
|
'self', 'cls', 'this', 'super', 'base', 'Self',
|
|
22
22
|
]);
|
|
23
|
+
const CROSS_OPERATION_FLOW_CACHE_LIMIT = 4096;
|
|
24
|
+
|
|
25
|
+
// `base` is a contextual receiver keyword only in C#. TypeScript and the
|
|
26
|
+
// other supported languages may bind an ordinary local named base; treating
|
|
27
|
+
// it as reserved globally suppresses otherwise exact assignment flow.
|
|
28
|
+
function _isReservedReceiver(language, receiver) {
|
|
29
|
+
if (receiver === 'base') return language === 'csharp';
|
|
30
|
+
return RESERVED_RECEIVER_NAMES.has(receiver);
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
/** Set.some() helper — like Array.some() but for Sets */
|
|
25
34
|
function setSome(set, predicate) {
|
|
@@ -583,9 +592,22 @@ function findCallers(index, name, options = {}) {
|
|
|
583
592
|
// completion. Phase 2 still only enriches the first `maxResults` items —
|
|
584
593
|
// file reads stay bounded, but the candidate count reflects the true total.
|
|
585
594
|
const needsTotal = !!options.needsTotal;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
595
|
+
// Per-file query-time derivations that depend only on a file's immutable
|
|
596
|
+
// call records — never on the pinned target — so they live for the whole
|
|
597
|
+
// OPERATION, not one findCallers call (fix #340): stats --hot / repo run
|
|
598
|
+
// findCallers for hundreds of candidates over the same files, and
|
|
599
|
+
// rebuilding fold contexts (producer index, flow maps) and typed-local
|
|
600
|
+
// maps per candidate made grpc-go's `repo` cost 40s (1375 calls).
|
|
601
|
+
if (!index._opFindCallersCaches) {
|
|
602
|
+
index._opFindCallersCaches = {
|
|
603
|
+
localTypeCache: new Map(), // `${filePath}:${startLine}` -> localTypes Map or null
|
|
604
|
+
returnFlowCache: new Map(), // filePath -> return-type-flow map (see _buildReturnTypeFlowMap)
|
|
605
|
+
foldCtxCache: new Map(), // filePath -> chained-receiver fold context (fix #258)
|
|
606
|
+
pythonIndexedReceiverCache: new Map(),
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const { localTypeCache, returnFlowCache, foldCtxCache, pythonIndexedReceiverCache } =
|
|
610
|
+
index._opFindCallersCaches;
|
|
589
611
|
|
|
590
612
|
// Use inverted callee index to skip files that don't contain calls to this name
|
|
591
613
|
let calleeFiles = index.getCalleeFiles(name);
|
|
@@ -651,6 +673,26 @@ function findCallers(index, name, options = {}) {
|
|
|
651
673
|
continue;
|
|
652
674
|
}
|
|
653
675
|
|
|
676
|
+
if (fileEntry.language === 'go' && call.isMethod &&
|
|
677
|
+
!call.receiverType && call.receiverIndexField) {
|
|
678
|
+
const indexedType = _goIndexedReceiverType(index, filePath, call);
|
|
679
|
+
if (indexedType?.type) {
|
|
680
|
+
call = {
|
|
681
|
+
...call,
|
|
682
|
+
receiverType: indexedType.type,
|
|
683
|
+
...(indexedType.fromFile && {
|
|
684
|
+
receiverTypeFlowFile: indexedType.fromFile,
|
|
685
|
+
}),
|
|
686
|
+
};
|
|
687
|
+
} else if (indexedType?.externalVia) {
|
|
688
|
+
call = {
|
|
689
|
+
...call,
|
|
690
|
+
receiverExternalFlow: indexedType.externalVia,
|
|
691
|
+
receiverExternalConcreteFlow: true,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
654
696
|
// A call-shaped identifier in a C/C++ replacement list can
|
|
655
697
|
// be a macro parameter (`#define APPLY(fn, x) fn(x)`). It is
|
|
656
698
|
// dynamically supplied by each expansion and therefore is
|
|
@@ -870,7 +912,7 @@ function findCallers(index, name, options = {}) {
|
|
|
870
912
|
// type derives from OTHER files' annotations, so it must never be
|
|
871
913
|
// persisted with this file's calls.
|
|
872
914
|
if (call.isMethod && call.receiver &&
|
|
873
|
-
!
|
|
915
|
+
!_isReservedReceiver(fileEntry.language, call.receiver) &&
|
|
874
916
|
(!call.receiverType || call.receiverTypeGuessed) &&
|
|
875
917
|
!call.receiverPatternShadow && !call.receiverFlowInvalidated &&
|
|
876
918
|
!call.receiverIsChainRoot &&
|
|
@@ -916,6 +958,34 @@ function findCallers(index, name, options = {}) {
|
|
|
916
958
|
...(flowEntry.fromFile && { receiverTypeFlowFile: flowEntry.fromFile }) };
|
|
917
959
|
}
|
|
918
960
|
}
|
|
961
|
+
|
|
962
|
+
// Python indexed receivers (fix #324): `layout["body"].update()`
|
|
963
|
+
// dispatch through the container's compiler-visible
|
|
964
|
+
// `__getitem__` return contract. The parser retains only a
|
|
965
|
+
// simple identifier root; query time pins that root's type to
|
|
966
|
+
// an exact project definition before trusting the contract.
|
|
967
|
+
// External, unresolved, and ambiguous containers abstain.
|
|
968
|
+
if (fileEntry.language === 'python' && call.isMethod &&
|
|
969
|
+
!call.receiverType && call.receiverSubscriptRoot) {
|
|
970
|
+
const indexedType = _pythonIndexedReceiverType(
|
|
971
|
+
index, filePath, call, () => {
|
|
972
|
+
let flowMap = returnFlowCache.get(filePath);
|
|
973
|
+
if (flowMap === undefined) {
|
|
974
|
+
flowMap = _buildReturnTypeFlowMap(index, filePath, calls);
|
|
975
|
+
returnFlowCache.set(filePath, flowMap);
|
|
976
|
+
}
|
|
977
|
+
return flowMap;
|
|
978
|
+
}, pythonIndexedReceiverCache);
|
|
979
|
+
if (indexedType?.type) {
|
|
980
|
+
call = {
|
|
981
|
+
...call,
|
|
982
|
+
receiverType: indexedType.type,
|
|
983
|
+
...(indexedType.fromFile && {
|
|
984
|
+
receiverTypeFlowFile: indexedType.fromFile,
|
|
985
|
+
}),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
}
|
|
919
989
|
// Python loop/comprehension bindings can inherit item types
|
|
920
990
|
// from a declared attribute path. The parser records the
|
|
921
991
|
// source path (`request.headers.raw`) and tuple position;
|
|
@@ -1099,12 +1169,8 @@ function findCallers(index, name, options = {}) {
|
|
|
1099
1169
|
receiverTypeFlowFile: path.join(index.root, project.rel),
|
|
1100
1170
|
};
|
|
1101
1171
|
} else {
|
|
1102
|
-
const projectish = bindings.some(b =>
|
|
1103
|
-
|
|
1104
|
-
const first = mod.split(/[./]/).filter(Boolean)[0];
|
|
1105
|
-
return mod.startsWith('.') ||
|
|
1106
|
-
(first && _projectTopLevelNames(index).has(first));
|
|
1107
|
-
});
|
|
1172
|
+
const projectish = bindings.some(b =>
|
|
1173
|
+
_unresolvedModuleIsGap(index, b.module, b));
|
|
1108
1174
|
const via = `${bindings[0].module}.${bindings[0].name}`;
|
|
1109
1175
|
if (BUILTIN_RECEIVER_TYPES.has(call.receiverType)) {
|
|
1110
1176
|
// Stable stdlib runtime classes (StringIO,
|
|
@@ -1581,9 +1647,53 @@ function findCallers(index, name, options = {}) {
|
|
|
1581
1647
|
cbTargetDefs.some(d => d.file &&
|
|
1582
1648
|
_sameNominalPackageDir(path.dirname(d.file), path.dirname(filePath), fileEntry.language));
|
|
1583
1649
|
let cbImportLink = false;
|
|
1650
|
+
// A module-scoped variable passed as a callback has a
|
|
1651
|
+
// concrete lexical owner, but its VALUE may be dynamic
|
|
1652
|
+
// (`const app = express(); use(app)`). It must not borrow
|
|
1653
|
+
// target identity from an unrelated file-level import
|
|
1654
|
+
// edge. Exact named/default imports still confirm when
|
|
1655
|
+
// their export chain reaches the pin; definitive chains
|
|
1656
|
+
// elsewhere exclude; dynamic/CJS factory values remain
|
|
1657
|
+
// visible. Account-gated to preserve legacy trace/blast
|
|
1658
|
+
// behavior while strengthening grep-reliable surfaces.
|
|
1659
|
+
if (collectAccount && !cbSameFile && call.moduleLocalBinding &&
|
|
1660
|
+
langTraits(fileEntry.language)?.typeSystem === 'structural') {
|
|
1661
|
+
const cbNameBindings = (fileEntry.importBindings || []).filter(binding =>
|
|
1662
|
+
binding.name === call.name || binding.alias === call.name);
|
|
1663
|
+
let cbBindingReaches = false;
|
|
1664
|
+
let cbBindingUnknown = cbNameBindings.length === 0;
|
|
1665
|
+
for (const binding of cbNameBindings) {
|
|
1666
|
+
const rel = fileEntry.moduleResolved?.[binding.module];
|
|
1667
|
+
if (!rel) {
|
|
1668
|
+
if (_unresolvedModuleIsGap(index, binding.module, binding)) {
|
|
1669
|
+
cbBindingUnknown = true;
|
|
1670
|
+
}
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
const resolvedAbs = path.join(index.root, rel);
|
|
1674
|
+
const verdict = binding.defaultLike
|
|
1675
|
+
? _defaultBindingReaches(index, resolvedAbs, cbTargetFiles)
|
|
1676
|
+
: _nameBindingReaches(index, resolvedAbs, binding.name, cbTargetFiles);
|
|
1677
|
+
if (verdict === 'yes') {
|
|
1678
|
+
cbBindingReaches = true;
|
|
1679
|
+
break;
|
|
1680
|
+
}
|
|
1681
|
+
if (verdict === 'unknown') cbBindingUnknown = true;
|
|
1682
|
+
}
|
|
1683
|
+
if (!cbBindingReaches && !cbBindingUnknown) {
|
|
1684
|
+
recordExcluded(filePath, call.line, 'other-definition-import');
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
if (!cbBindingReaches) {
|
|
1688
|
+
routeUnverified(filePath, fileEntry, call, 'ambiguous-binding', calledAs);
|
|
1689
|
+
continue;
|
|
1690
|
+
}
|
|
1691
|
+
cbImportLink = true;
|
|
1692
|
+
}
|
|
1584
1693
|
if (!cbSameFile && !cbSamePackage) {
|
|
1585
1694
|
const cbImports = index.importGraph.get(filePath);
|
|
1586
|
-
cbImportLink =
|
|
1695
|
+
cbImportLink = cbImportLink ||
|
|
1696
|
+
!!(cbImports && setSome(cbImports, imp => cbTargetFiles.has(imp)));
|
|
1587
1697
|
if (!cbImportLink && cbImports) {
|
|
1588
1698
|
for (const imp of cbImports) {
|
|
1589
1699
|
const trans = index.importGraph.get(imp);
|
|
@@ -2072,7 +2182,8 @@ function findCallers(index, name, options = {}) {
|
|
|
2072
2182
|
continue;
|
|
2073
2183
|
}
|
|
2074
2184
|
}
|
|
2075
|
-
} else if (['self', 'cls', 'this', 'super'
|
|
2185
|
+
} else if (['self', 'cls', 'this', 'super'].includes(call.receiver) ||
|
|
2186
|
+
(call.receiver === 'base' && fileEntry.language === 'csharp') ||
|
|
2076
2187
|
(call.receiver === 'Self' && fileEntry.language === 'rust')) {
|
|
2077
2188
|
// self/this/super.method() — resolve to same-class or parent method.
|
|
2078
2189
|
// Rust `Self::method()` (fix #232) is the path-call same-class form:
|
|
@@ -2089,8 +2200,8 @@ function findCallers(index, name, options = {}) {
|
|
|
2089
2200
|
}
|
|
2090
2201
|
} else {
|
|
2091
2202
|
// For super(), skip same-class — only check parent chain
|
|
2092
|
-
const parentOnlyReceiver =
|
|
2093
|
-
call.receiver === '
|
|
2203
|
+
const parentOnlyReceiver = call.receiver === 'super' ||
|
|
2204
|
+
(call.receiver === 'base' && fileEntry.language === 'csharp');
|
|
2094
2205
|
let matchedClass = !parentOnlyReceiver &&
|
|
2095
2206
|
definitions.some(d => d.className === callerSymbol.className)
|
|
2096
2207
|
? callerSymbol.className : null;
|
|
@@ -2244,7 +2355,23 @@ function findCallers(index, name, options = {}) {
|
|
|
2244
2355
|
...call,
|
|
2245
2356
|
receiver: call.receiverRoot,
|
|
2246
2357
|
});
|
|
2247
|
-
if (inferredRoot?.type)
|
|
2358
|
+
if (inferredRoot?.type) {
|
|
2359
|
+
fieldHopRootType = inferredRoot.type;
|
|
2360
|
+
} else if (inferredRoot?.externalVia &&
|
|
2361
|
+
!call.receiverExternalFlow) {
|
|
2362
|
+
// External root-flow through a field path (fix #306,
|
|
2363
|
+
// chi-measured): `resp := http.Get(...); resp.Body.Close()`.
|
|
2364
|
+
// The compiler fixed `resp` outside the project, so
|
|
2365
|
+
// `.Body` cannot turn a globally unique project Close
|
|
2366
|
+
// into an exact edge. Preserve the provenance on the
|
|
2367
|
+
// demote-only external-contract rail; a generic
|
|
2368
|
+
// external root may still carry a project value.
|
|
2369
|
+
call = {
|
|
2370
|
+
...call,
|
|
2371
|
+
receiverExternalFlow:
|
|
2372
|
+
`${inferredRoot.externalVia}.${call.receiverField}`,
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2248
2375
|
}
|
|
2249
2376
|
// Go's parser preserves the package qualifier on a declared
|
|
2250
2377
|
// root type (`r *http.Response; r.Body.Close()`). If that
|
|
@@ -2305,6 +2432,27 @@ function findCallers(index, name, options = {}) {
|
|
|
2305
2432
|
};
|
|
2306
2433
|
}
|
|
2307
2434
|
}
|
|
2435
|
+
// Structural namespace value hop (fix #315, Zod-measured):
|
|
2436
|
+
// `core.globalRegistry.add()` has a module namespace root and
|
|
2437
|
+
// an exported VALUE whose explicit annotation fixes the
|
|
2438
|
+
// receiver type. Follow exact ESM re-export chains (including
|
|
2439
|
+
// barrels) to that annotation; untyped/conflicting/dynamic
|
|
2440
|
+
// surfaces abstain. This is the value-level counterpart of
|
|
2441
|
+
// the declared class-field hop above.
|
|
2442
|
+
if (!fieldHopType && call.isMethod && !call.receiverType &&
|
|
2443
|
+
call.receiverField && !resolvedBySameClass &&
|
|
2444
|
+
langTraits(fileEntry.language)?.typeSystem === 'structural') {
|
|
2445
|
+
const moduleValueInfo = {};
|
|
2446
|
+
fieldHopType = _structuralModuleValueFieldType(
|
|
2447
|
+
index, fileEntry, call, moduleValueInfo);
|
|
2448
|
+
if (fieldHopType && moduleValueInfo.fromFile &&
|
|
2449
|
+
!call.receiverTypeFlowFile) {
|
|
2450
|
+
call = {
|
|
2451
|
+
...call,
|
|
2452
|
+
receiverTypeFlowFile: moduleValueInfo.fromFile,
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2308
2456
|
if (!resolvedByExtensionMethod && fileEntry.language === 'csharp' &&
|
|
2309
2457
|
fieldHopType) {
|
|
2310
2458
|
resolvedByExtensionMethod = _csharpExtensionCallMatches(
|
|
@@ -2708,10 +2856,7 @@ function findCallers(index, name, options = {}) {
|
|
|
2708
2856
|
// relative (project-internal by construction)
|
|
2709
2857
|
// or its first segment names a project path
|
|
2710
2858
|
// (resolution gap, not externality evidence)
|
|
2711
|
-
|
|
2712
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
2713
|
-
if (mod.startsWith('.') ||
|
|
2714
|
-
(firstSeg && _projectTopLevelNames(index).has(firstSeg))) {
|
|
2859
|
+
if (_unresolvedModuleIsGap(index, b.module, b)) {
|
|
2715
2860
|
undetermined = true;
|
|
2716
2861
|
}
|
|
2717
2862
|
continue;
|
|
@@ -2908,7 +3053,7 @@ function findCallers(index, name, options = {}) {
|
|
|
2908
3053
|
// only when every target is a class method; standalone-function
|
|
2909
3054
|
// and class (constructor) targets keep flowing on import evidence.
|
|
2910
3055
|
if ((!bindingId || recvExportedNamespace) && !resolvedBySameClass && call.isMethod &&
|
|
2911
|
-
(call.receiverIsModule || recvExportedNamespace) &&
|
|
3056
|
+
(call.receiverIsModule || call.receiverModuleSpecifier || recvExportedNamespace) &&
|
|
2912
3057
|
langTraits(fileEntry.language)?.typeSystem === 'structural' &&
|
|
2913
3058
|
targetDefs.length > 0 && targetDefs.every(d => d.className)) {
|
|
2914
3059
|
isUncertain = true;
|
|
@@ -2935,11 +3080,30 @@ function findCallers(index, name, options = {}) {
|
|
|
2935
3080
|
// dynamic CJS surfaces can exceed the modeled ownership);
|
|
2936
3081
|
// unresolved-but-project-looking → visible (resolver gap).
|
|
2937
3082
|
if ((!bindingId || recvExportedNamespace) && !resolvedBySameClass && call.isMethod &&
|
|
2938
|
-
(call.receiverIsModule ||
|
|
3083
|
+
(call.receiverIsModule || call.receiverModuleSpecifier ||
|
|
3084
|
+
recvSubmoduleRel || recvExportedNamespace ||
|
|
3085
|
+
call.receiverModuleComposition) &&
|
|
2939
3086
|
langTraits(fileEntry.language)?.typeSystem === 'structural') {
|
|
2940
|
-
const recvBindings = recvExportedNamespace
|
|
2941
|
-
? [] : _structuralModuleBindings(fileEntry, call);
|
|
2942
3087
|
const tFiles = targetDefinitionFiles;
|
|
3088
|
+
const compositeOwnership = call.receiverModuleComposition
|
|
3089
|
+
? _structuralCompositeModuleOwnership(
|
|
3090
|
+
index, fileEntry, call, tFiles)
|
|
3091
|
+
: null;
|
|
3092
|
+
if (compositeOwnership?.verdict === 'no') {
|
|
3093
|
+
recordExcluded(filePath, call.line, 'other-definition-import');
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
3096
|
+
if (compositeOwnership?.verdict === 'unknown') {
|
|
3097
|
+
if (collectAccount) {
|
|
3098
|
+
routeUnverified(filePath, fileEntry, call,
|
|
3099
|
+
'no-import-link', calledAs);
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
const recvBindings = recvExportedNamespace
|
|
3104
|
+
? [] : (compositeOwnership?.verdict === 'yes'
|
|
3105
|
+
? [compositeOwnership.binding]
|
|
3106
|
+
: _structuralModuleBindings(fileEntry, call));
|
|
2943
3107
|
// Same-file targets get NO bypass (fix #294, flask-measured:
|
|
2944
3108
|
// `import json as _json; _json.dump(...)` in the file
|
|
2945
3109
|
// defining flask's own `dump` confirmed a self-recursive
|
|
@@ -2967,10 +3131,7 @@ function findCallers(index, name, options = {}) {
|
|
|
2967
3131
|
const rel = recvSubmoduleRel ||
|
|
2968
3132
|
(fileEntry.moduleResolved && fileEntry.moduleResolved[b.module]);
|
|
2969
3133
|
if (!rel) {
|
|
2970
|
-
|
|
2971
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
2972
|
-
if (mod.startsWith('.') ||
|
|
2973
|
-
(firstSeg && _projectTopLevelNames(index).has(firstSeg))) {
|
|
3134
|
+
if (_unresolvedModuleIsGap(index, b.module, b)) {
|
|
2974
3135
|
projectish = true;
|
|
2975
3136
|
undetermined = true;
|
|
2976
3137
|
}
|
|
@@ -4099,18 +4260,37 @@ function findCallers(index, name, options = {}) {
|
|
|
4099
4260
|
if (r && index.files.has(r)) _modFiles.push(r);
|
|
4100
4261
|
} catch { /* resolver gap — never exclusion evidence */ }
|
|
4101
4262
|
}
|
|
4263
|
+
const _targetFiles = new Set(
|
|
4264
|
+
targetDefs2.map(d => d.file).filter(Boolean));
|
|
4102
4265
|
const _pinnedIn = _modFiles.length > 0 &&
|
|
4103
4266
|
targetDefs2.some(d => _modFiles.includes(d.file));
|
|
4104
|
-
|
|
4267
|
+
// A module/crate surface may re-export the
|
|
4268
|
+
// callable from a child module (`pub use
|
|
4269
|
+
// self::join::join`). Path ownership is
|
|
4270
|
+
// name-aware: reaching a file that declares
|
|
4271
|
+
// a same-named module is not a competing
|
|
4272
|
+
// value-namespace definition. Follow the
|
|
4273
|
+
// exact name through import/re-export
|
|
4274
|
+
// bindings before deciding this path owns a
|
|
4275
|
+
// different callable (fix #302, Rayon).
|
|
4276
|
+
const _reexportVerdicts = _pinnedIn ? [] :
|
|
4277
|
+
_modFiles.map(f => _nameBindingReaches(
|
|
4278
|
+
index, f, name, _targetFiles));
|
|
4279
|
+
const _reexportPinned =
|
|
4280
|
+
_reexportVerdicts.includes('yes');
|
|
4281
|
+
if (!_pinnedIn && !_reexportPinned) {
|
|
4105
4282
|
const _ownsName = _modFiles.some(f => {
|
|
4106
4283
|
const fe2 = index.files.get(f);
|
|
4107
4284
|
return fe2 && fe2.symbols && fe2.symbols.some(s =>
|
|
4108
|
-
s.name === name &&
|
|
4285
|
+
s.name === name && s.type !== 'module' &&
|
|
4286
|
+
!NON_CALLABLE_TYPES.has(s.type));
|
|
4109
4287
|
});
|
|
4110
4288
|
if (_ownsName) {
|
|
4111
4289
|
recordExcluded(filePath, call.line, 'other-definition');
|
|
4112
4290
|
continue;
|
|
4113
4291
|
}
|
|
4292
|
+
// A live but unresolved re-export path
|
|
4293
|
+
// is uncertainty, never negative proof.
|
|
4114
4294
|
routeUnverified(filePath, fileEntry, call, 'method-ambiguous', calledAs, {
|
|
4115
4295
|
dispatchCandidates: methodOwnerKeys().size,
|
|
4116
4296
|
});
|
|
@@ -4398,6 +4578,13 @@ function findCallers(index, name, options = {}) {
|
|
|
4398
4578
|
if (!typeQualifiedReceiver && call.receiverLocalBinding &&
|
|
4399
4579
|
!call.receiverType && !fieldHopType && !fieldDispatchType &&
|
|
4400
4580
|
!call.receiverExternalFlow && !call.receiverQualifiedFlow) {
|
|
4581
|
+
if (call.receiverUntypedIteration) {
|
|
4582
|
+
routeUnverified(filePath, fileEntry, call,
|
|
4583
|
+
'possible-dispatch', calledAs, {
|
|
4584
|
+
dispatchVia: 'untyped loop element',
|
|
4585
|
+
});
|
|
4586
|
+
continue;
|
|
4587
|
+
}
|
|
4401
4588
|
let demoteFlowMap = returnFlowCache.get(filePath);
|
|
4402
4589
|
if (demoteFlowMap === undefined) {
|
|
4403
4590
|
demoteFlowMap = _buildReturnTypeFlowMap(index, filePath, calls);
|
|
@@ -4446,7 +4633,7 @@ function findCallers(index, name, options = {}) {
|
|
|
4446
4633
|
// root counts only when the resolver PROVED it a
|
|
4447
4634
|
// submodule (#224 — a from-import name may be a
|
|
4448
4635
|
// plain symbol, never assume).
|
|
4449
|
-
if (!typeQualifiedReceiver && !knownDispatchType &&
|
|
4636
|
+
if (!call.moduleOwnedPath && !typeQualifiedReceiver && !knownDispatchType &&
|
|
4450
4637
|
call.receiverRoot && !call.receiverRootType &&
|
|
4451
4638
|
langTraits(fileEntry.language)?.typeSystem === 'structural') {
|
|
4452
4639
|
const rootBinding = (fileEntry.importBindings || []).find(b =>
|
|
@@ -5114,6 +5301,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5114
5301
|
// Return-type flow map (lazy — only built if a single-owner
|
|
5115
5302
|
// resolution needs the external-producer/typed-receiver defeater).
|
|
5116
5303
|
let _flowMap;
|
|
5304
|
+
const pythonIndexedReceiverCache = new Map();
|
|
5117
5305
|
const flowMap = () => {
|
|
5118
5306
|
if (_flowMap === undefined) {
|
|
5119
5307
|
if (queryProfile) {
|
|
@@ -5131,7 +5319,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5131
5319
|
const mayNeedDirectReceiverFlow = call =>
|
|
5132
5320
|
call.isMethod && call.receiver && !call.receiverType &&
|
|
5133
5321
|
!call.receiverPatternShadow &&
|
|
5134
|
-
!
|
|
5322
|
+
!_isReservedReceiver(language, call.receiver) &&
|
|
5135
5323
|
!call.isPathCall && !call.receiverIsModule &&
|
|
5136
5324
|
!call.receiverIsChainRoot;
|
|
5137
5325
|
// Chained-receiver fold context (fix #268 — the #258 rails, callee
|
|
@@ -5140,6 +5328,11 @@ function findCallees(index, definition, options = {}) {
|
|
|
5140
5328
|
const foldCtx = () => {
|
|
5141
5329
|
if (!calleeFoldCtx) {
|
|
5142
5330
|
calleeFoldCtx = { memo: new Map(), visiting: new Set(), records: calls,
|
|
5331
|
+
// A called local arrow factory may be declared outside
|
|
5332
|
+
// the current callee definition. Keep the narrow records
|
|
5333
|
+
// set for ordinary producer indexing, but expose the
|
|
5334
|
+
// already-loaded file records for exact returned spans.
|
|
5335
|
+
allRecords: allCalls,
|
|
5143
5336
|
getFlowMap: () => flowMap() };
|
|
5144
5337
|
}
|
|
5145
5338
|
return calleeFoldCtx;
|
|
@@ -5149,6 +5342,25 @@ function findCallees(index, definition, options = {}) {
|
|
|
5149
5342
|
for (let call of calls) {
|
|
5150
5343
|
siteOrdinal++;
|
|
5151
5344
|
const siteId = siteOrdinal;
|
|
5345
|
+
if (language === 'go' && call.isMethod &&
|
|
5346
|
+
!call.receiverType && call.receiverIndexField) {
|
|
5347
|
+
const indexedType = _goIndexedReceiverType(index, def.file, call);
|
|
5348
|
+
if (indexedType?.type) {
|
|
5349
|
+
call = {
|
|
5350
|
+
...call,
|
|
5351
|
+
receiverType: indexedType.type,
|
|
5352
|
+
...(indexedType.fromFile && {
|
|
5353
|
+
receiverTypeFlowFile: indexedType.fromFile,
|
|
5354
|
+
}),
|
|
5355
|
+
};
|
|
5356
|
+
} else if (indexedType?.externalVia) {
|
|
5357
|
+
call = {
|
|
5358
|
+
...call,
|
|
5359
|
+
receiverExternalFlow: indexedType.externalVia,
|
|
5360
|
+
receiverExternalConcreteFlow: true,
|
|
5361
|
+
};
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5152
5364
|
// Filter to calls within this function's scope
|
|
5153
5365
|
// Method 1: Direct match via enclosingFunction (fast path for direct calls)
|
|
5154
5366
|
const isDirectMatch = call.enclosingFunction &&
|
|
@@ -5264,6 +5476,25 @@ function findCallees(index, definition, options = {}) {
|
|
|
5264
5476
|
}
|
|
5265
5477
|
}
|
|
5266
5478
|
|
|
5479
|
+
// Python indexed receivers share the caller-side #324 contract:
|
|
5480
|
+
// only an exact project container type plus its declared
|
|
5481
|
+
// `__getitem__` return type may identify the selected value.
|
|
5482
|
+
if (language === 'python' && call.isMethod &&
|
|
5483
|
+
!call.receiverType && call.receiverSubscriptRoot) {
|
|
5484
|
+
const indexedType = _pythonIndexedReceiverType(
|
|
5485
|
+
index, def.file, call, flowMap,
|
|
5486
|
+
pythonIndexedReceiverCache);
|
|
5487
|
+
if (indexedType?.type) {
|
|
5488
|
+
call = {
|
|
5489
|
+
...call,
|
|
5490
|
+
receiverType: indexedType.type,
|
|
5491
|
+
...(indexedType.fromFile && {
|
|
5492
|
+
receiverTypeFlowFile: indexedType.fromFile,
|
|
5493
|
+
}),
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
|
|
5267
5498
|
// Query-time return flow for an ordinary receiver assignment:
|
|
5268
5499
|
// `v := New(); v.ReadConfig()`. The compiler-declared return type
|
|
5269
5500
|
// is stronger than constructor-name guesses and must participate
|
|
@@ -5271,6 +5502,17 @@ function findCallees(index, definition, options = {}) {
|
|
|
5271
5502
|
const directReceiverFlow = mayNeedDirectReceiverFlow(call)
|
|
5272
5503
|
? _lookupReturnTypeFlow(flowMap(), call)
|
|
5273
5504
|
: undefined;
|
|
5505
|
+
// Caller-side fix #305 twin: a loop element drawn from an
|
|
5506
|
+
// untyped identifier/attribute iterable has no class identity.
|
|
5507
|
+
// Unique project ownership of the method spelling is not enough
|
|
5508
|
+
// to invent an exact callee.
|
|
5509
|
+
if (collectAccount && call.isMethod && !call.receiverType &&
|
|
5510
|
+
call.receiverUntypedIteration) {
|
|
5511
|
+
noteUnverified(siteId, call, 'possible-dispatch', {
|
|
5512
|
+
dispatchVia: 'untyped loop element',
|
|
5513
|
+
});
|
|
5514
|
+
continue;
|
|
5515
|
+
}
|
|
5274
5516
|
// Callee-side twin of the structural caller gate (#222(4)). A
|
|
5275
5517
|
// local receiver whose nearest producer was examined but could
|
|
5276
5518
|
// not be typed (`C2 = decorator(Base); value = C2()`) has unknown
|
|
@@ -5303,19 +5545,26 @@ function findCallees(index, definition, options = {}) {
|
|
|
5303
5545
|
hopRoot = localTypes.get(call.receiverRoot);
|
|
5304
5546
|
}
|
|
5305
5547
|
if (!hopRoot && call.receiverRoot &&
|
|
5306
|
-
!
|
|
5548
|
+
!_isReservedReceiver(language, call.receiverRoot)) {
|
|
5307
5549
|
const inferredRoot = _lookupReturnTypeFlow(flowMap(), {
|
|
5308
5550
|
...call,
|
|
5309
5551
|
receiver: call.receiverRoot,
|
|
5310
5552
|
});
|
|
5311
|
-
if (inferredRoot?.type)
|
|
5553
|
+
if (inferredRoot?.type) {
|
|
5554
|
+
hopRoot = inferredRoot.type;
|
|
5555
|
+
} else if (inferredRoot?.externalVia) {
|
|
5556
|
+
fieldHopInfo = {
|
|
5557
|
+
externalVia:
|
|
5558
|
+
`${inferredRoot.externalVia}.${call.receiverField}`,
|
|
5559
|
+
};
|
|
5560
|
+
}
|
|
5312
5561
|
}
|
|
5313
5562
|
if (!hopRoot && call.receiverRoot === 'this' &&
|
|
5314
5563
|
langTraits(language)?.typeSystem === 'structural') {
|
|
5315
5564
|
hopRoot = index.findEnclosingFunction(def.file, call.line, true)?.className;
|
|
5316
5565
|
}
|
|
5317
5566
|
if (hopRoot) {
|
|
5318
|
-
fieldHopInfo = {};
|
|
5567
|
+
fieldHopInfo = fieldHopInfo || {};
|
|
5319
5568
|
const fields = call.receiverFields || [call.receiverField];
|
|
5320
5569
|
fieldHopType = _declaredFieldPathType(index, hopRoot, fields,
|
|
5321
5570
|
language, fieldHopInfo, call.receiverRootNamespace);
|
|
@@ -5349,6 +5598,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
5349
5598
|
}
|
|
5350
5599
|
}
|
|
5351
5600
|
}
|
|
5601
|
+
if (!fieldHopType &&
|
|
5602
|
+
langTraits(language)?.typeSystem === 'structural') {
|
|
5603
|
+
fieldHopInfo = fieldHopInfo || {};
|
|
5604
|
+
fieldHopType = _structuralModuleValueFieldType(
|
|
5605
|
+
index, fileEntry, call, fieldHopInfo);
|
|
5606
|
+
}
|
|
5352
5607
|
}
|
|
5353
5608
|
|
|
5354
5609
|
if (fieldDispatchType) {
|
|
@@ -5510,7 +5765,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5510
5765
|
if (call.isMethod && !call.isConstructor && call.receiver &&
|
|
5511
5766
|
!call.receiverType && !fieldHopType && !goImportModule &&
|
|
5512
5767
|
!call.receiverIsModule && !call.selfAttribute &&
|
|
5513
|
-
!
|
|
5768
|
+
!_isReservedReceiver(language, call.receiver) &&
|
|
5514
5769
|
!(localTypes && localTypes.has(call.receiver))) {
|
|
5515
5770
|
typeQual = _calleeTypeQualifiedReceiver(index, def, fileEntry, call, language);
|
|
5516
5771
|
}
|
|
@@ -5551,7 +5806,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5551
5806
|
(call.receiver === 'Self' && language === 'rust')) {
|
|
5552
5807
|
// self.method() / cls.method() / this.method() — resolve to same-class method below
|
|
5553
5808
|
// Rust Self::method() resolves same-impl the same way (fix #236, the #232 callee analog)
|
|
5554
|
-
} else if (call.receiver === 'super' ||
|
|
5809
|
+
} else if (call.receiver === 'super' ||
|
|
5810
|
+
(call.receiver === 'base' && language === 'csharp')) {
|
|
5555
5811
|
// super().method() — resolve to parent class method below
|
|
5556
5812
|
} else if (directReceiverFlow?.externalVia) {
|
|
5557
5813
|
if (directReceiverFlow.externalConcrete) {
|
|
@@ -5682,7 +5938,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5682
5938
|
const isCallableRT = (s) => !NON_CALLABLE_TYPES.has(s.type) ||
|
|
5683
5939
|
(s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
|
|
5684
5940
|
// Same-class overload selection by static call shape (fix #268)
|
|
5685
|
-
const receiverOriginFile =
|
|
5941
|
+
const receiverOriginFile = call.receiverTypeFlowFile ||
|
|
5942
|
+
directReceiverFlow?.fromFile ||
|
|
5686
5943
|
fieldHopInfo?.fromFile ||
|
|
5687
5944
|
(call.receiverType
|
|
5688
5945
|
? _resolveFlowTypeOrigin(
|
|
@@ -5696,7 +5953,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5696
5953
|
(symbol.file &&
|
|
5697
5954
|
path.dirname(symbol.file) === qualifiedType.dir)) &&
|
|
5698
5955
|
(!receiverOriginFile || !symbol.file ||
|
|
5699
|
-
((language === '
|
|
5956
|
+
((langTraits(language)?.typeSystem === 'structural' ||
|
|
5957
|
+
language === 'java' || language === 'csharp')
|
|
5700
5958
|
? symbol.file === receiverOriginFile
|
|
5701
5959
|
: path.dirname(symbol.file) ===
|
|
5702
5960
|
path.dirname(receiverOriginFile)));
|
|
@@ -5786,18 +6044,20 @@ function findCallees(index, definition, options = {}) {
|
|
|
5786
6044
|
continue;
|
|
5787
6045
|
}
|
|
5788
6046
|
// Structural legacy mode retains its historical fallback.
|
|
5789
|
-
} else if (call.receiverCall && (!call.receiver || call.receiverIsChainRoot) &&
|
|
5790
|
-
|
|
5791
|
-
// Chained receiver,
|
|
5792
|
-
//
|
|
5793
|
-
// `
|
|
5794
|
-
//
|
|
5795
|
-
//
|
|
5796
|
-
//
|
|
5797
|
-
//
|
|
5798
|
-
//
|
|
6047
|
+
} else if (call.receiverCall && (!call.receiver || call.receiverIsChainRoot) &&
|
|
6048
|
+
!call.receiverIsModule && !call.isConstructor) {
|
|
6049
|
+
// Chained receiver, callee direction (fix #302):
|
|
6050
|
+
// `Environment().getattr(...)` is the structural twin of
|
|
6051
|
+
// the nominal #268 shapes (`m.NotFound().ServeHTTP(...)`).
|
|
6052
|
+
// The shared fold already resolves constructor roots and
|
|
6053
|
+
// method-return chains for both type-system families; the
|
|
6054
|
+
// old nominal-only gate threw that evidence away and
|
|
6055
|
+
// classified exact Python/JS calls as external. Module
|
|
6056
|
+
// producers (`require("./lib").target()`) stay on the
|
|
6057
|
+
// stronger name-aware module route below.
|
|
5799
6058
|
let chained = _foldChainedReceiverType(index, fileEntry, def.file, call, foldCtx());
|
|
5800
|
-
if (!chained || (!chained.type && !chained.externalVia))
|
|
6059
|
+
if ((!chained || (!chained.type && !chained.externalVia)) &&
|
|
6060
|
+
langTraits(language)?.typeSystem === 'nominal') {
|
|
5801
6061
|
chained = _nominalChainedReceiverType(index, call, fileEntry, def.file);
|
|
5802
6062
|
}
|
|
5803
6063
|
if (chained?.type) {
|
|
@@ -5816,7 +6076,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
5816
6076
|
def.file, acceptsChainedDefinition);
|
|
5817
6077
|
if (sel.match) {
|
|
5818
6078
|
const match = sel.match;
|
|
5819
|
-
|
|
6079
|
+
// A direct constructor expression fixes the
|
|
6080
|
+
// runtime class exactly (`Environment()` cannot be
|
|
6081
|
+
// a subclass instance). Declared/returned base
|
|
6082
|
+
// types keep normal virtual-dispatch demotion.
|
|
6083
|
+
if (!chained.exactConstructor &&
|
|
6084
|
+
routeVirtualOverride(siteId, call, chained.type, match)) continue;
|
|
5820
6085
|
const key = match.bindingId || `${chained.type}.${call.name}`;
|
|
5821
6086
|
const existing = callees.get(key);
|
|
5822
6087
|
if (existing) {
|
|
@@ -5906,7 +6171,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
5906
6171
|
}
|
|
5907
6172
|
if (collectAccount && call.receiver && !call.receiverIsModule && !call.receiverType &&
|
|
5908
6173
|
!call.receiverCall && !call.isPotentialCallback &&
|
|
5909
|
-
!
|
|
6174
|
+
!_isReservedReceiver(language, call.receiver)) {
|
|
5910
6175
|
const owners = new Set((index.symbols.get(call.name) || [])
|
|
5911
6176
|
.filter(s => !NON_CALLABLE_TYPES.has(s.type))
|
|
5912
6177
|
.map(s => s.className || (s.receiver && s.receiver.replace(/^\*/, '')))
|
|
@@ -5926,7 +6191,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
5926
6191
|
// `super(...)` 'constructor' record were routed external here
|
|
5927
6192
|
// because __init__/constructor sit in the builtin name sets).
|
|
5928
6193
|
const selfShaped = call.isMethod &&
|
|
5929
|
-
(['self', 'cls', 'this', 'super'
|
|
6194
|
+
(['self', 'cls', 'this', 'super'].includes(call.receiver) ||
|
|
6195
|
+
(call.receiver === 'base' && language === 'csharp') ||
|
|
5930
6196
|
(call.receiver === 'Self' && language === 'rust'));
|
|
5931
6197
|
// Builtin/global names are shadowable. `Request` is a web global,
|
|
5932
6198
|
// but `const Request = require('./request')` owns `new Request()`
|
|
@@ -6011,7 +6277,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
6011
6277
|
|
|
6012
6278
|
// Collect super().method() calls for parent-class resolution
|
|
6013
6279
|
if (call.isMethod &&
|
|
6014
|
-
(call.receiver === 'super' ||
|
|
6280
|
+
(call.receiver === 'super' ||
|
|
6281
|
+
(call.receiver === 'base' && language === 'csharp'))) {
|
|
6015
6282
|
if (!selfMethodCalls) selfMethodCalls = [];
|
|
6016
6283
|
selfMethodCalls.push({ call, siteId });
|
|
6017
6284
|
continue;
|
|
@@ -6024,7 +6291,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
6024
6291
|
// the callee. Follow the module's name-level re-export chain and
|
|
6025
6292
|
// add only definitions it actually exposes. Unknown CJS/dynamic
|
|
6026
6293
|
// surfaces stay visible; external modules are external.
|
|
6027
|
-
if (call.isMethod && call.receiverIsModule
|
|
6294
|
+
if (call.isMethod && (call.receiverIsModule ||
|
|
6295
|
+
call.receiverModuleSpecifier || call.receiverModuleComposition) &&
|
|
6028
6296
|
langTraits(language)?.typeSystem === 'structural') {
|
|
6029
6297
|
const moduleRoute = _calleeStructuralModuleRoute(index, fileEntry, call, language);
|
|
6030
6298
|
if (moduleRoute.matches?.length) {
|
|
@@ -6147,7 +6415,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
6147
6415
|
// also named ReadConfig. Otherwise the bare wrapper steals the
|
|
6148
6416
|
// exact callee before receiver evidence is considered.
|
|
6149
6417
|
const receiverBlindMethodBinding = call.isMethod &&
|
|
6150
|
-
!
|
|
6418
|
+
!_isReservedReceiver(language, call.receiver);
|
|
6151
6419
|
let bindings = receiverBlindMethodBinding ? [] :
|
|
6152
6420
|
fileEntry.bindings.filter(b => b.name === call.name);
|
|
6153
6421
|
// For Go, also check sibling files in same directory (same
|
|
@@ -6682,8 +6950,8 @@ function findCallees(index, definition, options = {}) {
|
|
|
6682
6950
|
}
|
|
6683
6951
|
|
|
6684
6952
|
// For super().method(), skip same-class — start from parent
|
|
6685
|
-
const parentOnlyReceiver =
|
|
6686
|
-
call.receiver === '
|
|
6953
|
+
const parentOnlyReceiver = call.receiver === 'super' ||
|
|
6954
|
+
(call.receiver === 'base' && language === 'csharp');
|
|
6687
6955
|
const selectOwner = owner => _calleeOverloadSelect(
|
|
6688
6956
|
index,
|
|
6689
6957
|
call,
|
|
@@ -7031,12 +7299,33 @@ function getInstanceAttributeTypes(index, filePath, className) {
|
|
|
7031
7299
|
const parser = getParser('python');
|
|
7032
7300
|
const fileEntry = index.files.get(filePath);
|
|
7033
7301
|
fileCache = langModule.findInstanceAttributeTypes(content, parser, {
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7302
|
+
resolveTypeAliasMembers(typeName) {
|
|
7303
|
+
const owner = _resolveFlowTypeOrigin(
|
|
7304
|
+
index, filePath, typeName);
|
|
7305
|
+
if (!owner?.fromFile) return null;
|
|
7306
|
+
const definitions = (index.symbols.get(typeName) || [])
|
|
7307
|
+
.filter(definition => definition.file === owner.fromFile &&
|
|
7308
|
+
definition.type === 'type' &&
|
|
7309
|
+
Array.isArray(definition.aliasMembers));
|
|
7310
|
+
if (definitions.length === 0) return null;
|
|
7311
|
+
const identities = new Set(definitions.map(definition =>
|
|
7312
|
+
definition.aliasMembers.join('\0')));
|
|
7313
|
+
return identities.size === 1
|
|
7314
|
+
? [...definitions[0].aliasMembers] : null;
|
|
7315
|
+
},
|
|
7316
|
+
resolveCallType(moduleName, functionName) {
|
|
7317
|
+
if (_pythonBuiltinContractAllowed(index, fileEntry, moduleName)) {
|
|
7318
|
+
const builtin = langModule.getBuiltinCallReturnType?.(
|
|
7319
|
+
moduleName, functionName);
|
|
7320
|
+
if (builtin) return builtin;
|
|
7321
|
+
}
|
|
7322
|
+
const owner = _resolveFlowTypeOrigin(
|
|
7323
|
+
index, filePath, moduleName);
|
|
7324
|
+
if (!owner?.fromFile) return null;
|
|
7325
|
+
const result = _methodReturnOnType(
|
|
7326
|
+
index, moduleName, owner.fromFile, functionName,
|
|
7327
|
+
'python', { filePath, consumerAwaited: false });
|
|
7328
|
+
return result?.fromFile ? result.type : null;
|
|
7040
7329
|
},
|
|
7041
7330
|
});
|
|
7042
7331
|
index._attrTypeCache.set(filePath, fileCache);
|
|
@@ -7141,11 +7430,11 @@ function _typeNameFromReturnAnnotation(text) {
|
|
|
7141
7430
|
t = m[2].trim();
|
|
7142
7431
|
}
|
|
7143
7432
|
// generic base: Foo[...] / Foo<...> → Foo (the value is a Foo)
|
|
7144
|
-
m = t.match(/^([\w
|
|
7433
|
+
m = t.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*[[<]/);
|
|
7145
7434
|
if (m) t = m[1];
|
|
7146
7435
|
// dotted → last segment; validate a bare identifier remains
|
|
7147
7436
|
const last = t.split('.').pop();
|
|
7148
|
-
return /^[A-Za-z_]\w*$/.test(last) ? last : undefined;
|
|
7437
|
+
return /^[A-Za-z_$][\w$]*$/.test(last) ? last : undefined;
|
|
7149
7438
|
}
|
|
7150
7439
|
|
|
7151
7440
|
/**
|
|
@@ -7186,6 +7475,19 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7186
7475
|
// Map.has() so files without resolvable flow are cheap too.
|
|
7187
7476
|
const opCache = index._opReturnTypeFlowCache;
|
|
7188
7477
|
if (opCache?.has(filePath)) return opCache.get(filePath);
|
|
7478
|
+
// Return-flow derives from symbols in OTHER files, so it must never enter
|
|
7479
|
+
// the persisted calls cache. It is nevertheless immutable until the next
|
|
7480
|
+
// ProjectIndex build. Retain a bounded, calls-array-identity-guarded copy
|
|
7481
|
+
// across composed agent queries; build() clears it when any annotation or
|
|
7482
|
+
// import graph could have changed.
|
|
7483
|
+
const persistentCache = index._returnTypeFlowCache;
|
|
7484
|
+
const persistent = persistentCache?.get(filePath);
|
|
7485
|
+
if (persistent?.calls === calls) {
|
|
7486
|
+
persistentCache.delete(filePath);
|
|
7487
|
+
persistentCache.set(filePath, persistent);
|
|
7488
|
+
if (opCache) opCache.set(filePath, persistent.map);
|
|
7489
|
+
return persistent.map;
|
|
7490
|
+
}
|
|
7189
7491
|
const fileEntry = index.files.get(filePath);
|
|
7190
7492
|
const language = fileEntry?.language;
|
|
7191
7493
|
const nominal = langTraits(language)?.typeSystem === 'nominal';
|
|
@@ -7247,6 +7549,23 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7247
7549
|
if (via) routeUnknownAssignment(call, via);
|
|
7248
7550
|
continue;
|
|
7249
7551
|
}
|
|
7552
|
+
// Package-owned value method producer (fix #306, chi-measured):
|
|
7553
|
+
// `resp, err := http.DefaultClient.Do(req)`. The parser correctly
|
|
7554
|
+
// retains `http` as a module root and `DefaultClient` as its exported
|
|
7555
|
+
// value, but this shape is neither an ordinary package call nor a
|
|
7556
|
+
// locally typed method receiver. When that package is external, its
|
|
7557
|
+
// result is externally decided and must defeat later single-owner
|
|
7558
|
+
// confirmation (`resp.Body.Close()`). Project-owned package values
|
|
7559
|
+
// abstain here until their field declarations can be resolved.
|
|
7560
|
+
if (language === 'go' && call.isMethod && call.receiverRootIsModule &&
|
|
7561
|
+
call.receiverRoot && call.receiverField) {
|
|
7562
|
+
const qualified = _goQualifiedReceiverType(
|
|
7563
|
+
index, fileEntry, call.receiverRoot, call.receiverField);
|
|
7564
|
+
if (qualified && qualified.kind !== 'project') {
|
|
7565
|
+
routeUnknownAssignment(call, `${qualified.via}.${call.name}`);
|
|
7566
|
+
continue;
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7250
7569
|
const delegatedUnwrapAssignment = language === 'rust' &&
|
|
7251
7570
|
call.receiverCall && calls.some(candidate =>
|
|
7252
7571
|
candidate !== call &&
|
|
@@ -7280,11 +7599,16 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7280
7599
|
continue;
|
|
7281
7600
|
}
|
|
7282
7601
|
|
|
7283
|
-
// Assigned builder chain (chi-measured):
|
|
7284
|
-
// `hr := RouteHeaders().Route(...)`
|
|
7285
|
-
// as the assignment producer. Fold
|
|
7286
|
-
// before composing later
|
|
7287
|
-
|
|
7602
|
+
// Assigned builder chain (chi/zod-measured):
|
|
7603
|
+
// `hr := RouteHeaders().Route(...)` / `const s = z.array().refine(...)`
|
|
7604
|
+
// stores the OUTERMOST method call as the assignment producer. Fold
|
|
7605
|
+
// the chain to its declared result before composing a later receiver.
|
|
7606
|
+
// Macro-result folding remains nominal-only; structural call chains
|
|
7607
|
+
// use the same compiler-annotation and module-ownership rails as the
|
|
7608
|
+
// already-supported immediate chained-receiver path.
|
|
7609
|
+
if (call.receiverCall ||
|
|
7610
|
+
(!nominal && call.isMethod && call.receiverRoot && call.receiverField) ||
|
|
7611
|
+
(nominal && call.isMacro)) {
|
|
7288
7612
|
let folded = _typeOfCallResultFold(
|
|
7289
7613
|
index, fileEntry, filePath, call, assignedFoldCtx);
|
|
7290
7614
|
// Rust result aliases are commonly imported under a local name
|
|
@@ -7365,7 +7689,8 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7365
7689
|
} else if (callableFlow?.returnedFunctionResult) {
|
|
7366
7690
|
returnType = callableFlow.returnedFunctionResult;
|
|
7367
7691
|
fromFile = callableFlow.fromFile;
|
|
7368
|
-
} else if (call.isMethod && call.receiverType
|
|
7692
|
+
} else if (call.isMethod && call.receiverType &&
|
|
7693
|
+
!call.receiverTypeGuessed) {
|
|
7369
7694
|
const defs = index.symbols.get(call.name) || [];
|
|
7370
7695
|
if (nominal) {
|
|
7371
7696
|
const matches = defs.filter(d => d.className === call.receiverType && d.returnType);
|
|
@@ -7402,6 +7727,25 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7402
7727
|
index._getInheritanceParents?.(owner, filePath) || []))];
|
|
7403
7728
|
}
|
|
7404
7729
|
}
|
|
7730
|
+
} else if (!nominal && language === 'python' && call.isMethod &&
|
|
7731
|
+
call.receiver && !call.receiverLocalBinding &&
|
|
7732
|
+
!call.receiverIsModule && !call.receiverModuleComposition) {
|
|
7733
|
+
// Exact imported class factory: `text = Text.from_markup(...)`.
|
|
7734
|
+
// The parser intentionally does not label every capitalized
|
|
7735
|
+
// receiver as an instance type. Pin the value to an indexed type
|
|
7736
|
+
// in the caller's import scope first, then reuse the method
|
|
7737
|
+
// contract agreement discipline to type the assigned result.
|
|
7738
|
+
const owner = _resolveFlowTypeOrigin(index, filePath, call.receiver);
|
|
7739
|
+
const resolved = owner?.fromFile
|
|
7740
|
+
? _methodReturnOnType(
|
|
7741
|
+
index, call.receiver, owner.fromFile, call.name,
|
|
7742
|
+
language, { filePath, consumerAwaited: false })
|
|
7743
|
+
: null;
|
|
7744
|
+
if (resolved?.type) {
|
|
7745
|
+
returnType = resolved.type;
|
|
7746
|
+
fromFile = resolved.fromFile;
|
|
7747
|
+
selfClass = call.receiver;
|
|
7748
|
+
}
|
|
7405
7749
|
} else if (call.isMethod && call.receiver &&
|
|
7406
7750
|
!['self', 'this', 'cls'].includes(call.receiver) &&
|
|
7407
7751
|
_lookupReturnTypeFlow(map, call)) {
|
|
@@ -7583,7 +7927,9 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7583
7927
|
}
|
|
7584
7928
|
continue;
|
|
7585
7929
|
}
|
|
7586
|
-
} else if (!nominal && call.isMethod &&
|
|
7930
|
+
} else if (!nominal && call.isMethod &&
|
|
7931
|
+
(call.receiverIsModule || call.receiverModuleSpecifier ||
|
|
7932
|
+
call.receiverModuleComposition) &&
|
|
7587
7933
|
(call.receiver || call.receiverModuleSpecifier)) {
|
|
7588
7934
|
// Structural module-qualified producer (fix #209): schema =
|
|
7589
7935
|
// z.string() — the module alias resolves through the file's
|
|
@@ -7591,8 +7937,15 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7591
7937
|
// and the producer's return annotation types the variable.
|
|
7592
7938
|
// Standalone exports only (className-less): a module attr is
|
|
7593
7939
|
// never a class method.
|
|
7594
|
-
const
|
|
7595
|
-
|
|
7940
|
+
const composite = call.receiverModuleComposition
|
|
7941
|
+
? _structuralCompositeModuleOwnership(index, fileEntry, call)
|
|
7942
|
+
: null;
|
|
7943
|
+
if (composite && composite.verdict !== 'yes') continue;
|
|
7944
|
+
const binding = composite?.binding ||
|
|
7945
|
+
_structuralModuleBindings(fileEntry, call)[0];
|
|
7946
|
+
const rel = composite?.rel ||
|
|
7947
|
+
(binding && fileEntry.moduleResolved &&
|
|
7948
|
+
fileEntry.moduleResolved[binding.module]);
|
|
7596
7949
|
if (binding && !rel) {
|
|
7597
7950
|
// External module producer (fix #222, httpx-measured — the
|
|
7598
7951
|
// #220 Go external-producer rule for structural languages):
|
|
@@ -7602,10 +7955,7 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7602
7955
|
// project `info`) is not identity evidence. Same externality
|
|
7603
7956
|
// test as #209 module ownership: relative or project-ish
|
|
7604
7957
|
// modules are resolver gaps, never externality evidence.
|
|
7605
|
-
|
|
7606
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
7607
|
-
if (!mod.startsWith('.') &&
|
|
7608
|
-
!(firstSeg && _projectTopLevelNames(index).has(firstSeg))) {
|
|
7958
|
+
if (!_unresolvedModuleIsGap(index, binding.module, binding)) {
|
|
7609
7959
|
const scope = call.enclosingFunction ? `${call.enclosingFunction.startLine}` : '';
|
|
7610
7960
|
if (!map) map = new Map();
|
|
7611
7961
|
const key = `${scope}:${call.assignedTo}`;
|
|
@@ -7620,6 +7970,19 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7620
7970
|
const cands = (index.symbols.get(call.name) || [])
|
|
7621
7971
|
.filter(d => !NON_CALLABLE_TYPES.has(d.type) && d.returnType && !d.className);
|
|
7622
7972
|
let matches = cands.filter(d => d.file === modFile);
|
|
7973
|
+
// A namespace binding owns a NAME through the complete
|
|
7974
|
+
// re-export surface, not merely through one import edge.
|
|
7975
|
+
// Zod's self exports run index -> external -> types; the
|
|
7976
|
+
// one-hop fallback below lost the producer's defining-file
|
|
7977
|
+
// provenance and conflated the v3/v4 types with identical
|
|
7978
|
+
// short names. Reuse the same name-aware ownership proof as
|
|
7979
|
+
// chained producers before retaining the legacy fallback.
|
|
7980
|
+
if (matches.length === 0) {
|
|
7981
|
+
matches = cands.filter(definition =>
|
|
7982
|
+
_importedNamespaceMemberOwnership(
|
|
7983
|
+
index, fileEntry, call,
|
|
7984
|
+
new Set([definition.file]))?.verdict === 'yes');
|
|
7985
|
+
}
|
|
7623
7986
|
if (matches.length === 0) {
|
|
7624
7987
|
const hop = index.importGraph.get(modFile);
|
|
7625
7988
|
if (hop) matches = cands.filter(d => hop.has(d.file));
|
|
@@ -7974,6 +8337,13 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7974
8337
|
}) });
|
|
7975
8338
|
}
|
|
7976
8339
|
if (opCache) opCache.set(filePath, map);
|
|
8340
|
+
if (persistentCache) {
|
|
8341
|
+
persistentCache.delete(filePath);
|
|
8342
|
+
persistentCache.set(filePath, { calls, map });
|
|
8343
|
+
if (persistentCache.size > CROSS_OPERATION_FLOW_CACHE_LIMIT) {
|
|
8344
|
+
persistentCache.delete(persistentCache.keys().next().value);
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
7977
8347
|
return map;
|
|
7978
8348
|
}
|
|
7979
8349
|
|
|
@@ -8187,7 +8557,6 @@ function _returnTypeNameNominal(text, language, opts = {}) {
|
|
|
8187
8557
|
if (t.startsWith('(')) {
|
|
8188
8558
|
if (!opts.tuple) return undefined;
|
|
8189
8559
|
const inner = t.slice(1, -1);
|
|
8190
|
-
if (inner.includes('func(') || inner.includes('func (')) return undefined;
|
|
8191
8560
|
const position = Number.isInteger(opts.tupleIndex)
|
|
8192
8561
|
? opts.tupleIndex : 0;
|
|
8193
8562
|
const item = _splitTopLevelGenericArgs(inner)[position]?.trim();
|
|
@@ -8730,9 +9099,7 @@ function _structuralQualifiedReceiverOrigin(index, fileEntry, qualifier, typeNam
|
|
|
8730
9099
|
fromFile: path.join(index.root, rel),
|
|
8731
9100
|
};
|
|
8732
9101
|
}
|
|
8733
|
-
|
|
8734
|
-
if (moduleName.startsWith('.') ||
|
|
8735
|
-
(first && _projectTopLevelNames(index).has(first))) {
|
|
9102
|
+
if (_unresolvedModuleIsGap(index, moduleName)) {
|
|
8736
9103
|
projectish = true;
|
|
8737
9104
|
}
|
|
8738
9105
|
}
|
|
@@ -8850,13 +9217,41 @@ function _receiverPackageResolution(index, fileEntry, receiver, targetDefs) {
|
|
|
8850
9217
|
* back to the original route through records this chase follows or flags).
|
|
8851
9218
|
*/
|
|
8852
9219
|
function _nameBindingReaches(index, startAbs, name, targetFiles, maxDepth = 4) {
|
|
9220
|
+
// The same export path is asked once per competing definition and again
|
|
9221
|
+
// by caller/callee projections in a composed command. The graph and file
|
|
9222
|
+
// surfaces are immutable during an operation, so retain the tri-state
|
|
9223
|
+
// verdict beside the existing import-reachability memo. Target identity
|
|
9224
|
+
// is part of the key; no answer can leak between pinned definitions.
|
|
9225
|
+
const opCache = index._opImportReachCache;
|
|
9226
|
+
const targetKey = [...targetFiles].sort(codeUnitCompare).join('\x00');
|
|
9227
|
+
const cacheKey = `name\x00${maxDepth}\x00${startAbs}\x00${name}\x00${targetKey}`;
|
|
9228
|
+
if (opCache?.has(cacheKey)) return opCache.get(cacheKey);
|
|
9229
|
+
const persistentCache = index._nameBindingReachCache;
|
|
9230
|
+
if (persistentCache?.has(cacheKey)) {
|
|
9231
|
+
const value = persistentCache.get(cacheKey);
|
|
9232
|
+
// Map insertion order is the LRU order.
|
|
9233
|
+
persistentCache.delete(cacheKey);
|
|
9234
|
+
persistentCache.set(cacheKey, value);
|
|
9235
|
+
if (opCache) opCache.set(cacheKey, value);
|
|
9236
|
+
return value;
|
|
9237
|
+
}
|
|
9238
|
+
const finish = value => {
|
|
9239
|
+
if (opCache) opCache.set(cacheKey, value);
|
|
9240
|
+
if (persistentCache) {
|
|
9241
|
+
persistentCache.set(cacheKey, value);
|
|
9242
|
+
if (persistentCache.size > 16384) {
|
|
9243
|
+
persistentCache.delete(persistentCache.keys().next().value);
|
|
9244
|
+
}
|
|
9245
|
+
}
|
|
9246
|
+
return value;
|
|
9247
|
+
};
|
|
8853
9248
|
let unknown = false;
|
|
8854
9249
|
const visited = new Set();
|
|
8855
9250
|
let frontier = [[startAbs, name]];
|
|
8856
9251
|
for (let d = 0; d <= maxDepth && frontier.length > 0; d++) {
|
|
8857
9252
|
const next = [];
|
|
8858
9253
|
for (const [abs, attr] of frontier) {
|
|
8859
|
-
if (targetFiles.has(abs)) return 'yes';
|
|
9254
|
+
if (targetFiles.has(abs)) return finish('yes');
|
|
8860
9255
|
const stateKey = `${abs}\x00${attr}`;
|
|
8861
9256
|
if (visited.has(stateKey)) continue;
|
|
8862
9257
|
visited.add(stateKey);
|
|
@@ -8886,7 +9281,7 @@ function _nameBindingReaches(index, startAbs, name, targetFiles, maxDepth = 4) {
|
|
|
8886
9281
|
e.defaultLike && e.type === 'module.exports' &&
|
|
8887
9282
|
(e.alias || e.name) !== attr);
|
|
8888
9283
|
if (staticOwner && !competingDynamic) {
|
|
8889
|
-
return 'no';
|
|
9284
|
+
return finish('no');
|
|
8890
9285
|
}
|
|
8891
9286
|
|
|
8892
9287
|
const enqueue = (module, nextAttr) => {
|
|
@@ -8895,10 +9290,7 @@ function _nameBindingReaches(index, startAbs, name, targetFiles, maxDepth = 4) {
|
|
|
8895
9290
|
// Unresolved: relative or project-ish → resolver gap, not
|
|
8896
9291
|
// a terminal; clearly external → that path pins outside
|
|
8897
9292
|
// the project (dead end, consistent with #209c).
|
|
8898
|
-
|
|
8899
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
8900
|
-
if (mod.startsWith('.') ||
|
|
8901
|
-
(firstSeg && _projectTopLevelNames(index).has(firstSeg))) unknown = true;
|
|
9293
|
+
if (_unresolvedModuleIsGap(index, module)) unknown = true;
|
|
8902
9294
|
return;
|
|
8903
9295
|
}
|
|
8904
9296
|
next.push([path.join(index.root, rel), nextAttr]);
|
|
@@ -8944,7 +9336,7 @@ function _nameBindingReaches(index, startAbs, name, targetFiles, maxDepth = 4) {
|
|
|
8944
9336
|
frontier = next;
|
|
8945
9337
|
}
|
|
8946
9338
|
if (frontier.length > 0) unknown = true; // depth exhausted with live paths
|
|
8947
|
-
return unknown ? 'unknown' : 'no';
|
|
9339
|
+
return finish(unknown ? 'unknown' : 'no');
|
|
8948
9340
|
}
|
|
8949
9341
|
|
|
8950
9342
|
/**
|
|
@@ -9143,6 +9535,122 @@ function _importedNamespaceMemberOwnership(index, fileEntry, call, targetFiles)
|
|
|
9143
9535
|
return { verdict: unknown ? 'unknown' : 'no' };
|
|
9144
9536
|
}
|
|
9145
9537
|
|
|
9538
|
+
/**
|
|
9539
|
+
* Resolve the compiler-declared type of an exported JS/TS value through an
|
|
9540
|
+
* exact ESM export chain. This is deliberately narrower than ordinary name
|
|
9541
|
+
* ownership: a local value without an explicit annotation, a dynamic/CJS
|
|
9542
|
+
* surface, an unresolved module, or competing exports returns unknown.
|
|
9543
|
+
*/
|
|
9544
|
+
function _structuralExportedValueType(
|
|
9545
|
+
index, startAbs, exposedName, language, maxDepth = 6, visited = new Set()
|
|
9546
|
+
) {
|
|
9547
|
+
if (maxDepth < 0) return { verdict: 'unknown' };
|
|
9548
|
+
const stateKey = `${startAbs}\x00${exposedName}`;
|
|
9549
|
+
if (visited.has(stateKey)) return { verdict: 'unknown' };
|
|
9550
|
+
visited.add(stateKey);
|
|
9551
|
+
const fileEntry = index.files.get(startAbs);
|
|
9552
|
+
if (!fileEntry) return { verdict: 'unknown' };
|
|
9553
|
+
const details = fileEntry.exportDetails || [];
|
|
9554
|
+
if (details.some(item => item.type === 'exports' ||
|
|
9555
|
+
item.type === 'module.exports')) return { verdict: 'unknown' };
|
|
9556
|
+
|
|
9557
|
+
const local = details.filter(item =>
|
|
9558
|
+
!item.source && (item.alias || item.name) === exposedName);
|
|
9559
|
+
if (local.length > 0) {
|
|
9560
|
+
if (local.length !== 1 || !local[0].isVariable ||
|
|
9561
|
+
!local[0].typeAnnotation) return { verdict: 'unknown' };
|
|
9562
|
+
const type = _structuralTypeHead(local[0].typeAnnotation, {
|
|
9563
|
+
index,
|
|
9564
|
+
language,
|
|
9565
|
+
originFile: startAbs,
|
|
9566
|
+
});
|
|
9567
|
+
if (!type || _STRUCTURAL_FLOW_REJECT.has(type) ||
|
|
9568
|
+
(/^[A-Z][A-Z0-9]?$/.test(type) &&
|
|
9569
|
+
!(index.symbols.get(type) || [])
|
|
9570
|
+
.some(definition => IDENTITY_TYPE_KINDS.has(definition.type)))) {
|
|
9571
|
+
return { verdict: 'unknown' };
|
|
9572
|
+
}
|
|
9573
|
+
const typeDefs = (index.symbols.get(type) || [])
|
|
9574
|
+
.filter(definition => IDENTITY_TYPE_KINDS.has(definition.type));
|
|
9575
|
+
if (typeDefs.length === 0) return { verdict: 'yes', type };
|
|
9576
|
+
const origin = _resolveFlowTypeOrigin(index, startAbs, type);
|
|
9577
|
+
if (!origin?.fromFile) return { verdict: 'unknown' };
|
|
9578
|
+
return { verdict: 'yes', type, fromFile: origin.fromFile };
|
|
9579
|
+
}
|
|
9580
|
+
|
|
9581
|
+
const resolveSource = (item, name) => {
|
|
9582
|
+
const rel = fileEntry.moduleResolved?.[item.source];
|
|
9583
|
+
if (!rel) return { verdict: 'unknown' };
|
|
9584
|
+
return _structuralExportedValueType(
|
|
9585
|
+
index, path.join(index.root, rel), name, language,
|
|
9586
|
+
maxDepth - 1, new Set(visited));
|
|
9587
|
+
};
|
|
9588
|
+
const merge = results => {
|
|
9589
|
+
if (results.some(result => result.verdict === 'unknown')) {
|
|
9590
|
+
return { verdict: 'unknown' };
|
|
9591
|
+
}
|
|
9592
|
+
const matches = results.filter(result => result.verdict === 'yes');
|
|
9593
|
+
if (matches.length === 0) return { verdict: 'no' };
|
|
9594
|
+
const identities = new Set(matches.map(result =>
|
|
9595
|
+
`${result.type}\x00${result.fromFile || ''}`));
|
|
9596
|
+
return identities.size === 1 ? matches[0] : { verdict: 'unknown' };
|
|
9597
|
+
};
|
|
9598
|
+
|
|
9599
|
+
const exact = details.filter(item => item.source &&
|
|
9600
|
+
item.type === 're-export' && (item.alias || item.name) === exposedName);
|
|
9601
|
+
if (exact.length > 0) {
|
|
9602
|
+
return merge(exact.map(item => resolveSource(item, item.name)));
|
|
9603
|
+
}
|
|
9604
|
+
const stars = details.filter(item => item.source &&
|
|
9605
|
+
item.type === 're-export-all' && !item.alias);
|
|
9606
|
+
if (stars.length === 0) return { verdict: 'no' };
|
|
9607
|
+
return merge(stars.map(item => resolveSource(item, exposedName)));
|
|
9608
|
+
}
|
|
9609
|
+
|
|
9610
|
+
/**
|
|
9611
|
+
* Type a one-hop field receiver rooted at an unshadowed namespace import:
|
|
9612
|
+
* `api.service.run()` where `service` is an explicitly typed exported value.
|
|
9613
|
+
*/
|
|
9614
|
+
function _structuralModuleValueFieldType(index, fileEntry, call, info = null) {
|
|
9615
|
+
if (!call?.receiverRoot || !call.receiverField || call.receiverLocalBinding) {
|
|
9616
|
+
return null;
|
|
9617
|
+
}
|
|
9618
|
+
const fields = call.receiverFields || [call.receiverField];
|
|
9619
|
+
if (fields.length !== 1) return null;
|
|
9620
|
+
const cache = index._opImportReachCache;
|
|
9621
|
+
const cacheKey = `module-value-type\x00${fileEntry?.path || ''}\x00` +
|
|
9622
|
+
`${call.receiverRoot}\x00${call.receiverField}`;
|
|
9623
|
+
if (cache?.has(cacheKey)) {
|
|
9624
|
+
const cached = cache.get(cacheKey);
|
|
9625
|
+
if (info && cached?.fromFile) info.fromFile = cached.fromFile;
|
|
9626
|
+
return cached?.type || null;
|
|
9627
|
+
}
|
|
9628
|
+
const finish = result => {
|
|
9629
|
+
if (cache) cache.set(cacheKey, result);
|
|
9630
|
+
if (info && result?.fromFile) info.fromFile = result.fromFile;
|
|
9631
|
+
return result?.type || null;
|
|
9632
|
+
};
|
|
9633
|
+
const bindings = (fileEntry?.importBindings || []).filter(binding =>
|
|
9634
|
+
binding.kind === 'namespace' &&
|
|
9635
|
+
(binding.alias || binding.name) === call.receiverRoot);
|
|
9636
|
+
if (bindings.length === 0) return finish(null);
|
|
9637
|
+
|
|
9638
|
+
const results = [];
|
|
9639
|
+
for (const binding of bindings) {
|
|
9640
|
+
const rel = fileEntry.moduleResolved?.[binding.module];
|
|
9641
|
+
if (!rel) return finish(null);
|
|
9642
|
+
const result = _structuralExportedValueType(
|
|
9643
|
+
index, path.join(index.root, rel), call.receiverField,
|
|
9644
|
+
fileEntry.language);
|
|
9645
|
+
if (result.verdict !== 'yes') return finish(null);
|
|
9646
|
+
results.push(result);
|
|
9647
|
+
}
|
|
9648
|
+
const identities = new Set(results.map(result =>
|
|
9649
|
+
`${result.type}\x00${result.fromFile || ''}`));
|
|
9650
|
+
if (identities.size !== 1) return finish(null);
|
|
9651
|
+
return finish(results[0]);
|
|
9652
|
+
}
|
|
9653
|
+
|
|
9146
9654
|
/**
|
|
9147
9655
|
* Ownership chase for a CommonJS default-like require binding:
|
|
9148
9656
|
* `const local = require('./module')`. The local binding name says nothing
|
|
@@ -9343,6 +9851,25 @@ function _projectTopLevelNames(index) {
|
|
|
9343
9851
|
return names;
|
|
9344
9852
|
}
|
|
9345
9853
|
|
|
9854
|
+
/**
|
|
9855
|
+
* Is an UNRESOLVED module specifier a resolver gap rather than externality
|
|
9856
|
+
* evidence? (fix #337b) Relative specifiers and first segments naming a
|
|
9857
|
+
* project top-level path were already gaps (#209); a NON-LITERAL specifier —
|
|
9858
|
+
* `require(path.join(__dirname, ...))`, `require(name)`, template paths — is
|
|
9859
|
+
* one too: the parser records the expression text as the module, which can
|
|
9860
|
+
* never match a package name, so judging it external excluded true callers as
|
|
9861
|
+
* `other-definition-import`. Statically composable `__dirname` paths are
|
|
9862
|
+
* resolved parser-side; whatever stays dynamic must route 'unknown'.
|
|
9863
|
+
*/
|
|
9864
|
+
function _unresolvedModuleIsGap(index, module, binding) {
|
|
9865
|
+
const mod = String(module || '');
|
|
9866
|
+
if (binding && binding.dynamic) return true;
|
|
9867
|
+
if (mod.startsWith('.')) return true;
|
|
9868
|
+
if (/[()$`{}+\s]/.test(mod)) return true;
|
|
9869
|
+
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
9870
|
+
return !!(firstSeg && _projectTopLevelNames(index).has(firstSeg));
|
|
9871
|
+
}
|
|
9872
|
+
|
|
9346
9873
|
const IDENTITY_TYPE_KINDS = new Set(['class', 'struct', 'interface', 'trait', 'enum']);
|
|
9347
9874
|
|
|
9348
9875
|
/**
|
|
@@ -9373,6 +9900,21 @@ function _genericParamNames(genericsText) {
|
|
|
9373
9900
|
return names.size > 0 ? names : null;
|
|
9374
9901
|
}
|
|
9375
9902
|
|
|
9903
|
+
/**
|
|
9904
|
+
* A method declared on a generic owner parameter (`impl<I> Trait for I`) has
|
|
9905
|
+
* no concrete receiver identity. The owner may be instantiated by every type
|
|
9906
|
+
* satisfying the impl bounds, so it can neither confirm nor exclude a typed
|
|
9907
|
+
* receiver. Parser-provided ownerGenerics distinguishes this from an actual
|
|
9908
|
+
* project type whose short name happens to look generic.
|
|
9909
|
+
*/
|
|
9910
|
+
function _definitionHasGenericOwner(definition) {
|
|
9911
|
+
const owner = definition?.className ||
|
|
9912
|
+
(definition?.receiver || '').replace(/^[*&]\s*/, '');
|
|
9913
|
+
if (!owner) return false;
|
|
9914
|
+
const params = _genericParamNames(definition.ownerGenerics);
|
|
9915
|
+
return !!params?.has(owner);
|
|
9916
|
+
}
|
|
9917
|
+
|
|
9376
9918
|
/**
|
|
9377
9919
|
* Is typeName a declared GENERIC TYPE PARAMETER in scope at this call site —
|
|
9378
9920
|
* on the enclosing function itself (`fn f<TStore: Wipe>(t: &TStore)`) or on
|
|
@@ -10208,10 +10750,9 @@ function _goQualifierNamesImport(index, fieldFile, qualifier) {
|
|
|
10208
10750
|
function _iterExternalProducerVia(index, fileEntry, call) {
|
|
10209
10751
|
if (!fileEntry || langTraits(fileEntry.language)?.typeSystem === 'nominal') return null;
|
|
10210
10752
|
const externalModule = (mod) => {
|
|
10211
|
-
if (!mod
|
|
10753
|
+
if (!mod) return false;
|
|
10212
10754
|
if (fileEntry.moduleResolved?.[mod]) return false;
|
|
10213
|
-
|
|
10214
|
-
return !(firstSeg && _projectTopLevelNames(index).has(firstSeg));
|
|
10755
|
+
return !_unresolvedModuleIsGap(index, mod);
|
|
10215
10756
|
};
|
|
10216
10757
|
if (call.isMethod && call.receiverIsModule && call.receiver) {
|
|
10217
10758
|
const binding = _structuralModuleBindings(fileEntry, call)[0];
|
|
@@ -10241,12 +10782,67 @@ function _structuralModuleBindings(fileEntry, call) {
|
|
|
10241
10782
|
return (fileEntry?.importBindings || []).filter(b => b.name === call?.receiver);
|
|
10242
10783
|
}
|
|
10243
10784
|
|
|
10785
|
+
/**
|
|
10786
|
+
* Resolve an ordinary module-local object composed from namespace spreads:
|
|
10787
|
+
* `const z = { ...schemas, ...checks, iso }`.
|
|
10788
|
+
*
|
|
10789
|
+
* The JS parser records this only for a private, unescaped, unmodified const
|
|
10790
|
+
* object. Walk layers from last to first because later object spreads and
|
|
10791
|
+
* explicit properties override earlier names. A layer may be skipped only
|
|
10792
|
+
* when its complete modeled export surface definitively lacks the requested
|
|
10793
|
+
* name; resolver gaps and dynamic surfaces remain unknown.
|
|
10794
|
+
*
|
|
10795
|
+
* When targetFiles is supplied, verdict answers whether the winning layer
|
|
10796
|
+
* owns that pinned definition. Without targetFiles it returns the winning
|
|
10797
|
+
* module binding for return-flow/callee lookup.
|
|
10798
|
+
*/
|
|
10799
|
+
function _structuralCompositeModuleOwnership(
|
|
10800
|
+
index, fileEntry, call, targetFiles = null
|
|
10801
|
+
) {
|
|
10802
|
+
const layers = call?.receiverModuleComposition;
|
|
10803
|
+
if (!Array.isArray(layers) || layers.length === 0 || !call.name) return null;
|
|
10804
|
+
const nameFiles = new Set((index.symbols.get(call.name) || [])
|
|
10805
|
+
.filter(definition => definition.file &&
|
|
10806
|
+
(!NON_CALLABLE_TYPES.has(definition.type) || definition.type === 'class'))
|
|
10807
|
+
.map(definition => definition.file));
|
|
10808
|
+
if (nameFiles.size === 0) return { verdict: 'unknown' };
|
|
10809
|
+
|
|
10810
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
10811
|
+
const layer = layers[i];
|
|
10812
|
+
if (layer.kind === 'property') {
|
|
10813
|
+
if (layer.name === call.name) return { verdict: 'unknown' };
|
|
10814
|
+
continue;
|
|
10815
|
+
}
|
|
10816
|
+
if (layer.kind !== 'spread' || !layer.receiver) {
|
|
10817
|
+
return { verdict: 'unknown' };
|
|
10818
|
+
}
|
|
10819
|
+
const bindings = (fileEntry?.importBindings || []).filter(binding =>
|
|
10820
|
+
(binding.alias || binding.name) === layer.receiver &&
|
|
10821
|
+
binding.kind === 'namespace');
|
|
10822
|
+
if (bindings.length !== 1) return { verdict: 'unknown' };
|
|
10823
|
+
const binding = bindings[0];
|
|
10824
|
+
const rel = fileEntry.moduleResolved?.[binding.module];
|
|
10825
|
+
if (!rel) return { verdict: 'unknown' };
|
|
10826
|
+
const moduleFile = path.join(index.root, rel);
|
|
10827
|
+
const presence = _nameBindingReaches(
|
|
10828
|
+
index, moduleFile, call.name, nameFiles);
|
|
10829
|
+
if (presence === 'unknown') return { verdict: 'unknown' };
|
|
10830
|
+
if (presence === 'no') continue;
|
|
10831
|
+
if (!targetFiles) {
|
|
10832
|
+
return { verdict: 'yes', binding, rel, moduleFile };
|
|
10833
|
+
}
|
|
10834
|
+
const verdict = _nameBindingReaches(
|
|
10835
|
+
index, moduleFile, call.name, targetFiles);
|
|
10836
|
+
return { verdict, binding, rel, moduleFile };
|
|
10837
|
+
}
|
|
10838
|
+
return { verdict: 'no' };
|
|
10839
|
+
}
|
|
10840
|
+
|
|
10244
10841
|
function _pythonBuiltinContractAllowed(index, fileEntry, moduleName) {
|
|
10245
10842
|
const module = String(moduleName || '');
|
|
10246
|
-
if (!module
|
|
10843
|
+
if (!module) return false;
|
|
10247
10844
|
if (fileEntry.moduleResolved?.[module]) return false;
|
|
10248
|
-
|
|
10249
|
-
return !first || !_projectTopLevelNames(index).has(first);
|
|
10845
|
+
return !_unresolvedModuleIsGap(index, module);
|
|
10250
10846
|
}
|
|
10251
10847
|
|
|
10252
10848
|
function _structuralImportedReceiverType(index, fileEntry, receiver) {
|
|
@@ -10341,7 +10937,12 @@ function _structuralReturnedConstructorFlow(index, definition) {
|
|
|
10341
10937
|
}
|
|
10342
10938
|
|
|
10343
10939
|
function _calleeStructuralModuleRoute(index, fileEntry, call, language) {
|
|
10344
|
-
const
|
|
10940
|
+
const composite = call.receiverModuleComposition
|
|
10941
|
+
? _structuralCompositeModuleOwnership(index, fileEntry, call)
|
|
10942
|
+
: null;
|
|
10943
|
+
if (composite && composite.verdict !== 'yes') return { unknown: true };
|
|
10944
|
+
const bindings = composite
|
|
10945
|
+
? [composite.binding] : _structuralModuleBindings(fileEntry, call);
|
|
10345
10946
|
if (bindings.length === 0) return { unknown: true };
|
|
10346
10947
|
return _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, call.name, false);
|
|
10347
10948
|
}
|
|
@@ -10364,10 +10965,7 @@ function _calleeStructuralBindingRoute(index, fileEntry, call, language, binding
|
|
|
10364
10965
|
for (const binding of bindings) {
|
|
10365
10966
|
const rel = fileEntry.moduleResolved && fileEntry.moduleResolved[binding.module];
|
|
10366
10967
|
if (!rel) {
|
|
10367
|
-
|
|
10368
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
10369
|
-
if (mod.startsWith('.') ||
|
|
10370
|
-
(firstSeg && _projectTopLevelNames(index).has(firstSeg))) {
|
|
10968
|
+
if (_unresolvedModuleIsGap(index, binding.module, binding)) {
|
|
10371
10969
|
sawProjectish = true;
|
|
10372
10970
|
sawUnknown = true;
|
|
10373
10971
|
}
|
|
@@ -10415,10 +11013,7 @@ function _calleeExportDefinitions(index, startAbs, exposedName, language, call,
|
|
|
10415
11013
|
const enqueue = (module, nextAttr) => {
|
|
10416
11014
|
const rel = fe.moduleResolved && fe.moduleResolved[module];
|
|
10417
11015
|
if (!rel) {
|
|
10418
|
-
|
|
10419
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
10420
|
-
if (mod.startsWith('.') ||
|
|
10421
|
-
(firstSeg && _projectTopLevelNames(index).has(firstSeg))) unknown = true;
|
|
11016
|
+
if (_unresolvedModuleIsGap(index, module)) unknown = true;
|
|
10422
11017
|
return;
|
|
10423
11018
|
}
|
|
10424
11019
|
next.push([path.join(index.root, rel), nextAttr]);
|
|
@@ -10641,8 +11236,15 @@ function _calleeSelectReceiverMethod(index, call, symbols, typeName, language,
|
|
|
10641
11236
|
(symbol.receiver && symbol.receiver.replace(/^\*/, '') === owner)));
|
|
10642
11237
|
let selected = _calleeOverloadSelect(
|
|
10643
11238
|
index, call, onOwner(typeName), language);
|
|
10644
|
-
|
|
10645
|
-
|
|
11239
|
+
// Nominal lookup and compiler-typed TS/TSX class values inherit methods
|
|
11240
|
+
// from the nearest recorded base. Structural lookup stays restricted to
|
|
11241
|
+
// TypeScript here: plain JavaScript/Python receivers can be dynamically
|
|
11242
|
+
// reshaped, while a TS return annotation plus a concrete class heritage
|
|
11243
|
+
// edge is compiler-grade method ownership.
|
|
11244
|
+
const followsRecordedClassInheritance =
|
|
11245
|
+
langTraits(language)?.typeSystem === 'nominal' ||
|
|
11246
|
+
language === 'typescript' || language === 'tsx';
|
|
11247
|
+
if (!selected.match && !selected.ambiguous && followsRecordedClassInheritance) {
|
|
10646
11248
|
const visited = new Set([typeName]);
|
|
10647
11249
|
const queue = [...(index._getInheritanceParents?.(
|
|
10648
11250
|
typeName, contextFile) || [])];
|
|
@@ -10695,7 +11297,97 @@ function _csharpParamsNormalFormApplicable(index, call, definition) {
|
|
|
10695
11297
|
return !!actual && !!expected && actual === expected;
|
|
10696
11298
|
}
|
|
10697
11299
|
|
|
10698
|
-
function
|
|
11300
|
+
function _goContainerElementType(raw) {
|
|
11301
|
+
const text = String(raw || '').trim();
|
|
11302
|
+
let element = null;
|
|
11303
|
+
const bracketStart = text.startsWith('map[') ? 3 : text.startsWith('[') ? 0 : -1;
|
|
11304
|
+
if (bracketStart >= 0) {
|
|
11305
|
+
let depth = 0;
|
|
11306
|
+
for (let i = bracketStart; i < text.length; i++) {
|
|
11307
|
+
if (text[i] === '[') depth++;
|
|
11308
|
+
else if (text[i] === ']') {
|
|
11309
|
+
depth--;
|
|
11310
|
+
if (depth === 0) {
|
|
11311
|
+
element = text.slice(i + 1).trim();
|
|
11312
|
+
break;
|
|
11313
|
+
}
|
|
11314
|
+
}
|
|
11315
|
+
}
|
|
11316
|
+
}
|
|
11317
|
+
if (!element) return null;
|
|
11318
|
+
element = element.replace(/^\*+/, '').trim();
|
|
11319
|
+
const qualified = element.match(/^([A-Za-z_]\w*)\.([A-Za-z_]\w*)$/);
|
|
11320
|
+
if (qualified) {
|
|
11321
|
+
return { qualifier: qualified[1], type: qualified[2] };
|
|
11322
|
+
}
|
|
11323
|
+
return /^[A-Za-z_]\w*$/.test(element)
|
|
11324
|
+
? { qualifier: null, type: element } : null;
|
|
11325
|
+
}
|
|
11326
|
+
|
|
11327
|
+
function _goIndexedReceiverType(index, filePath, call) {
|
|
11328
|
+
const rootType = call.receiverIndexRootType;
|
|
11329
|
+
const fieldName = call.receiverIndexField;
|
|
11330
|
+
if (!rootType || !fieldName) return null;
|
|
11331
|
+
|
|
11332
|
+
const rootOrigin = _resolveFlowTypeOrigin(
|
|
11333
|
+
index, filePath, rootType, call.receiverIndexRootTypeQualifier);
|
|
11334
|
+
if (call.receiverIndexRootTypeQualifier && !rootOrigin?.fromFile) {
|
|
11335
|
+
return { externalVia: `${call.receiverIndexRootTypeQualifier}.${rootType}` };
|
|
11336
|
+
}
|
|
11337
|
+
|
|
11338
|
+
let fields = (index.symbols.get(fieldName) || []).filter(definition =>
|
|
11339
|
+
definition.className === rootType && definition.fieldType &&
|
|
11340
|
+
(definition.type === 'field' || definition.memberType === 'field'));
|
|
11341
|
+
if (rootOrigin?.fromFile) {
|
|
11342
|
+
const owned = fields.filter(field => field.file === rootOrigin.fromFile);
|
|
11343
|
+
if (owned.length > 0) fields = owned;
|
|
11344
|
+
}
|
|
11345
|
+
if (fields.length === 0) return null;
|
|
11346
|
+
|
|
11347
|
+
const candidates = new Map();
|
|
11348
|
+
for (const field of fields) {
|
|
11349
|
+
const element = _goContainerElementType(field.fieldType);
|
|
11350
|
+
if (!element) return null;
|
|
11351
|
+
if (element.qualifier) {
|
|
11352
|
+
const origin = field.file && _resolveFlowTypeOrigin(
|
|
11353
|
+
index, field.file, element.type, element.qualifier);
|
|
11354
|
+
if (origin?.fromFile) {
|
|
11355
|
+
candidates.set(`${element.type}\0${origin.fromFile}`, {
|
|
11356
|
+
type: element.type,
|
|
11357
|
+
fromFile: origin.fromFile,
|
|
11358
|
+
});
|
|
11359
|
+
} else if (field.file &&
|
|
11360
|
+
_goQualifierNamesImport(index, field.file, element.qualifier)) {
|
|
11361
|
+
candidates.set(`external\0${element.qualifier}.${element.type}`, {
|
|
11362
|
+
externalVia: `${element.qualifier}.${element.type}`,
|
|
11363
|
+
});
|
|
11364
|
+
} else {
|
|
11365
|
+
return null;
|
|
11366
|
+
}
|
|
11367
|
+
continue;
|
|
11368
|
+
}
|
|
11369
|
+
|
|
11370
|
+
const typeDefs = (index.symbols.get(element.type) || [])
|
|
11371
|
+
.filter(definition => IDENTITY_TYPE_KINDS.has(definition.type));
|
|
11372
|
+
if (typeDefs.length === 0) {
|
|
11373
|
+
if (!BUILTIN_RECEIVER_TYPES.has(element.type)) return null;
|
|
11374
|
+
candidates.set(`builtin\0${element.type}`, { type: element.type });
|
|
11375
|
+
continue;
|
|
11376
|
+
}
|
|
11377
|
+
const origin = field.file && _resolveFlowTypeOrigin(
|
|
11378
|
+
index, field.file, element.type);
|
|
11379
|
+
if (!origin?.fromFile) return null;
|
|
11380
|
+
candidates.set(`${element.type}\0${origin.fromFile}`, {
|
|
11381
|
+
type: element.type,
|
|
11382
|
+
fromFile: origin.fromFile,
|
|
11383
|
+
});
|
|
11384
|
+
}
|
|
11385
|
+
return candidates.size === 1 ? [...candidates.values()][0] : null;
|
|
11386
|
+
}
|
|
11387
|
+
|
|
11388
|
+
function _declaredFieldType(
|
|
11389
|
+
index, rootType, fieldName, language, info = null, rootNamespace = undefined
|
|
11390
|
+
) {
|
|
10699
11391
|
const defs = index.symbols.get(fieldName) || [];
|
|
10700
11392
|
if (defs.length === 0 && language !== 'python') return null;
|
|
10701
11393
|
// 'private field' (JS #-fields, fix #219): equally compiler-true, and
|
|
@@ -10799,7 +11491,10 @@ function _declaredFieldType(index, rootType, fieldName, language, info, rootName
|
|
|
10799
11491
|
return null;
|
|
10800
11492
|
}
|
|
10801
11493
|
}
|
|
10802
|
-
const localType = _normalizeFieldTypeName(rawText, language
|
|
11494
|
+
const localType = _normalizeFieldTypeName(rawText, language, {
|
|
11495
|
+
index,
|
|
11496
|
+
originFile: f.file,
|
|
11497
|
+
});
|
|
10803
11498
|
const importedIdentity = language === 'rust' && f.file && localType
|
|
10804
11499
|
? _rustImportedTypeIdentity(index, f.file, localType) : null;
|
|
10805
11500
|
const t = importedIdentity?.type || localType;
|
|
@@ -10829,7 +11524,10 @@ function _declaredFieldType(index, rootType, fieldName, language, info, rootName
|
|
|
10829
11524
|
const qualifier = language === 'java'
|
|
10830
11525
|
? _javaNestedTypeQualifier(rawText) : undefined;
|
|
10831
11526
|
if (qualifier) namespaces.add(qualifier);
|
|
10832
|
-
const localType = _normalizeFieldTypeName(rawText, language
|
|
11527
|
+
const localType = _normalizeFieldTypeName(rawText, language, {
|
|
11528
|
+
index,
|
|
11529
|
+
originFile: field.file,
|
|
11530
|
+
});
|
|
10833
11531
|
const importedIdentity = language === 'rust' && localType
|
|
10834
11532
|
? _rustImportedTypeIdentity(index, field.file, localType) : null;
|
|
10835
11533
|
const origin = importedIdentity?.type === typeName
|
|
@@ -12190,8 +12888,17 @@ function _javaArgKindMatches(index, kind, paramType, language) {
|
|
|
12190
12888
|
return allowed.includes(bare);
|
|
12191
12889
|
}
|
|
12192
12890
|
|
|
12193
|
-
function _cppTypeCategory(type) {
|
|
12891
|
+
function _cppTypeCategory(index, type) {
|
|
12194
12892
|
if (!type) return { kind: 'unknown', head: null };
|
|
12893
|
+
const cacheKey = String(type);
|
|
12894
|
+
const cached = index._opCppTypeCategoryCache?.get(cacheKey);
|
|
12895
|
+
if (cached) return cached;
|
|
12896
|
+
const result = _computeCppTypeCategory(type);
|
|
12897
|
+
index._opCppTypeCategoryCache?.set(cacheKey, result);
|
|
12898
|
+
return result;
|
|
12899
|
+
}
|
|
12900
|
+
|
|
12901
|
+
function _computeCppTypeCategory(type) {
|
|
12195
12902
|
const original = String(type).trim();
|
|
12196
12903
|
const compact = original.replace(/\s+/g, '');
|
|
12197
12904
|
const unqualified = original
|
|
@@ -12271,9 +12978,9 @@ function _cppTypeCategory(type) {
|
|
|
12271
12978
|
* converting constructors and unknown template constraints keep the
|
|
12272
12979
|
* candidate alive.
|
|
12273
12980
|
*/
|
|
12274
|
-
function _cppArgKindMatches(kind, paramType) {
|
|
12981
|
+
function _cppArgKindMatches(index, kind, paramType) {
|
|
12275
12982
|
if (!kind || kind === 'expr' || !paramType) return true;
|
|
12276
|
-
const expected = _cppTypeCategory(paramType);
|
|
12983
|
+
const expected = _cppTypeCategory(index, paramType);
|
|
12277
12984
|
if (expected.kind === 'unknown' || expected.kind === 'generic') return true;
|
|
12278
12985
|
|
|
12279
12986
|
if (kind.startsWith('string:')) {
|
|
@@ -12320,7 +13027,7 @@ function _cppArgKindMatches(kind, paramType) {
|
|
|
12320
13027
|
if (kind.startsWith('type:') || kind.startsWith('call:') ||
|
|
12321
13028
|
kind.startsWith('bcall:')) {
|
|
12322
13029
|
const actualType = kind.slice(kind.indexOf(':') + 1);
|
|
12323
|
-
const actual = _cppTypeCategory(actualType);
|
|
13030
|
+
const actual = _cppTypeCategory(index, actualType);
|
|
12324
13031
|
if (actual.head && expected.head && actual.head === expected.head) return true;
|
|
12325
13032
|
const closed = new Set([
|
|
12326
13033
|
'format-string', 'string', 'locale', 'style',
|
|
@@ -12362,7 +13069,7 @@ function _overloadApplicable(index, call, def) {
|
|
|
12362
13069
|
const p = ps[i];
|
|
12363
13070
|
if (!p || p.rest) break;
|
|
12364
13071
|
const matches = language === 'cpp'
|
|
12365
|
-
? _cppArgKindMatches(kinds[i], p.type)
|
|
13072
|
+
? _cppArgKindMatches(index, kinds[i], p.type)
|
|
12366
13073
|
: _javaArgKindMatches(index, kinds[i], p.type, language);
|
|
12367
13074
|
if (!matches) return false;
|
|
12368
13075
|
}
|
|
@@ -12656,6 +13363,10 @@ function _cppTargetVisibility(index, callerFile, targetDefs) {
|
|
|
12656
13363
|
|
|
12657
13364
|
function _cppPathReceiverNamesType(index, receiver) {
|
|
12658
13365
|
if (!receiver) return false;
|
|
13366
|
+
const cacheKey = String(receiver);
|
|
13367
|
+
if (index._opCppPathReceiverTypeCache?.has(cacheKey)) {
|
|
13368
|
+
return index._opCppPathReceiverTypeCache.get(cacheKey);
|
|
13369
|
+
}
|
|
12659
13370
|
// Remove template arguments before selecting the terminal path segment;
|
|
12660
13371
|
// nested qualifiers inside `<...>` must not be mistaken for the owner.
|
|
12661
13372
|
let plain = '';
|
|
@@ -12672,9 +13383,11 @@ function _cppPathReceiverNamesType(index, receiver) {
|
|
|
12672
13383
|
if (depth === 0) plain += character;
|
|
12673
13384
|
}
|
|
12674
13385
|
const name = plain.split('::').filter(Boolean).pop();
|
|
12675
|
-
|
|
13386
|
+
const result = !!(name && (index.symbols.get(name) || []).some(definition =>
|
|
12676
13387
|
IDENTITY_TYPE_KINDS.has(definition.type) ||
|
|
12677
13388
|
(definition.type === 'type' && definition.aliasOf)));
|
|
13389
|
+
index._opCppPathReceiverTypeCache?.set(cacheKey, result);
|
|
13390
|
+
return result;
|
|
12678
13391
|
}
|
|
12679
13392
|
|
|
12680
13393
|
function _cppQualifiedPathOwnsTarget(index, callerFile, call, targetDefs) {
|
|
@@ -13130,13 +13843,38 @@ function _cppExactOverloadWinner(call, applicable) {
|
|
|
13130
13843
|
*/
|
|
13131
13844
|
function _buildTargetTypeSet(index, targetDefs, definitions) {
|
|
13132
13845
|
const targetTypes = new Set();
|
|
13846
|
+
const targetTypeOrigins = [];
|
|
13847
|
+
// Callable-identity closure joins a trait declaration with its impl slot.
|
|
13848
|
+
// If any member is a blanket impl over a generic owner, the whole slot is
|
|
13849
|
+
// universally quantified: a concrete receiver can satisfy it without
|
|
13850
|
+
// having the trait name as its nominal class. No member of that closed
|
|
13851
|
+
// target group has exclusion-grade concrete receiver identity.
|
|
13852
|
+
if (targetDefs.some(_definitionHasGenericOwner)) return targetTypes;
|
|
13133
13853
|
for (const td of targetDefs) {
|
|
13854
|
+
// Blanket/generic impl owner (`impl<I> Trait for I`) is a quantified
|
|
13855
|
+
// parameter, not a concrete type called I. Keeping it in targetTypes
|
|
13856
|
+
// made every real receiver look provably unrelated and excluded true
|
|
13857
|
+
// Rayon par_iter/par_iter_mut calls. Empty target identity deliberately
|
|
13858
|
+
// falls through to the visible dispatch tier.
|
|
13134
13859
|
if (td.explicitInterface) {
|
|
13135
13860
|
const interfaceType = _csharpTypeIdentity(td.explicitInterface);
|
|
13136
|
-
if (interfaceType)
|
|
13861
|
+
if (interfaceType) {
|
|
13862
|
+
targetTypes.add(interfaceType);
|
|
13863
|
+
targetTypeOrigins.push({ name: interfaceType, file: td.file });
|
|
13864
|
+
}
|
|
13137
13865
|
} else {
|
|
13138
|
-
|
|
13139
|
-
|
|
13866
|
+
const owners = [
|
|
13867
|
+
td.className,
|
|
13868
|
+
td.receiver && td.receiver.replace(/^\*/, ''),
|
|
13869
|
+
].filter(Boolean);
|
|
13870
|
+
for (const owner of owners) {
|
|
13871
|
+
targetTypes.add(owner);
|
|
13872
|
+
const origin = _resolveFlowTypeOrigin(index, td.file, owner);
|
|
13873
|
+
targetTypeOrigins.push({
|
|
13874
|
+
name: owner,
|
|
13875
|
+
file: origin?.fromFile || td.file,
|
|
13876
|
+
});
|
|
13877
|
+
}
|
|
13140
13878
|
}
|
|
13141
13879
|
}
|
|
13142
13880
|
if (targetTypes.size > 0) {
|
|
@@ -13153,26 +13891,42 @@ function _buildTargetTypeSet(index, targetDefs, definitions) {
|
|
|
13153
13891
|
const overloadedSlots = !!langTraits(language)?.hasArityOverloads;
|
|
13154
13892
|
const targetSignatures = new Set(targetDefs.map(signature)
|
|
13155
13893
|
.filter(value => value !== null));
|
|
13156
|
-
const queue =
|
|
13894
|
+
const queue = targetTypeOrigins.length > 0
|
|
13895
|
+
? targetTypeOrigins : [...targetTypes].map(name => ({ name }));
|
|
13157
13896
|
while (queue.length > 0) {
|
|
13158
|
-
const
|
|
13897
|
+
const parent = queue.pop();
|
|
13898
|
+
const children = index.extendedByGraph?.get(parent.name);
|
|
13159
13899
|
if (!children) continue;
|
|
13160
13900
|
for (const child of children) {
|
|
13161
13901
|
const cName = typeof child === 'string' ? child : child.name;
|
|
13162
13902
|
if (!cName || targetTypes.has(cName)) continue;
|
|
13903
|
+
const childFile = typeof child === 'string' ? null : child.file;
|
|
13904
|
+
if (parent.file && childFile) {
|
|
13905
|
+
// extendedByGraph is keyed by the parent's SHORT name.
|
|
13906
|
+
// Parallel package versions can therefore share a bucket
|
|
13907
|
+
// (zod v3/v4 both define ZodType). Admit a child only when
|
|
13908
|
+
// its written parent resolves back to this exact target
|
|
13909
|
+
// type origin. An unresolved parent stays conservative;
|
|
13910
|
+
// a positively foreign origin is never confirmation-grade.
|
|
13911
|
+
const parentOrigin = _resolveFlowTypeOrigin(
|
|
13912
|
+
index, childFile, parent.name);
|
|
13913
|
+
if (parentOrigin?.fromFile &&
|
|
13914
|
+
parentOrigin.fromFile !== parent.file) continue;
|
|
13915
|
+
}
|
|
13163
13916
|
// In overload-capable languages, another same-named overload
|
|
13164
13917
|
// on the child does not override the pinned virtual slot.
|
|
13165
13918
|
// JsonTextWriter.WriteValue(Guid) must not hide inherited
|
|
13166
13919
|
// JsonWriter.WriteValue(Guid?). Only an agreeing parameter
|
|
13167
13920
|
// signature blocks the subtype closure.
|
|
13168
13921
|
const childDefinitions = definitions.filter(definition =>
|
|
13169
|
-
definition.className === cName
|
|
13922
|
+
definition.className === cName &&
|
|
13923
|
+
(!childFile || definition.file === childFile));
|
|
13170
13924
|
const overrides = childDefinitions.some(definition =>
|
|
13171
13925
|
!overloadedSlots ||
|
|
13172
13926
|
targetSignatures.has(signature(definition)));
|
|
13173
13927
|
if (overrides) continue;
|
|
13174
13928
|
targetTypes.add(cName);
|
|
13175
|
-
queue.push(cName);
|
|
13929
|
+
queue.push({ name: cName, file: childFile || parent.file });
|
|
13176
13930
|
}
|
|
13177
13931
|
}
|
|
13178
13932
|
}
|
|
@@ -13180,12 +13934,16 @@ function _buildTargetTypeSet(index, targetDefs, definitions) {
|
|
|
13180
13934
|
// callable through the wrapper value. Close only over wrappers whose
|
|
13181
13935
|
// indexed type definitions all agree on one Deref target.
|
|
13182
13936
|
if (targetTypes.size > 0) {
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13937
|
+
let derefPairs = index._opDerefPairs;
|
|
13938
|
+
if (!Array.isArray(derefPairs)) {
|
|
13939
|
+
derefPairs = [];
|
|
13940
|
+
for (const [wrapper, defs] of index.symbols) {
|
|
13941
|
+
const typeDefs = defs.filter(d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
13942
|
+
if (typeDefs.length === 0 || !typeDefs.every(d => d.derefTarget)) continue;
|
|
13943
|
+
const targets = new Set(typeDefs.map(d => d.derefTarget));
|
|
13944
|
+
if (targets.size === 1) derefPairs.push([wrapper, [...targets][0]]);
|
|
13945
|
+
}
|
|
13946
|
+
if (index._opDerefPairs !== null) index._opDerefPairs = derefPairs;
|
|
13189
13947
|
}
|
|
13190
13948
|
let changed = derefPairs.length > 0;
|
|
13191
13949
|
while (changed) {
|
|
@@ -13209,19 +13967,23 @@ function _buildTargetTypeSet(index, targetDefs, definitions) {
|
|
|
13209
13967
|
// package must not confirm foreign receivers (#206 discipline). The
|
|
13210
13968
|
// parser records aliasOf for Rust/Go; names without it never close.
|
|
13211
13969
|
if (targetTypes.size > 0) {
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
if (
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13970
|
+
let aliasPairs = index._opAliasPairs;
|
|
13971
|
+
if (!Array.isArray(aliasPairs)) {
|
|
13972
|
+
aliasPairs = [];
|
|
13973
|
+
for (const [aliasName, defs] of index.symbols) {
|
|
13974
|
+
let base = null;
|
|
13975
|
+
let pure = true;
|
|
13976
|
+
for (const d of defs) {
|
|
13977
|
+
if (d.type !== 'type' && !IDENTITY_TYPE_KINDS.has(d.type)) continue;
|
|
13978
|
+
if (d.type === 'type' && d.aliasOf) {
|
|
13979
|
+
const normalized = _normalizedAliasBase(index, d);
|
|
13980
|
+
if (base === null) base = normalized;
|
|
13981
|
+
else if (base !== normalized) { pure = false; break; }
|
|
13982
|
+
} else { pure = false; break; }
|
|
13983
|
+
}
|
|
13984
|
+
if (pure && base) aliasPairs.push([aliasName, base]);
|
|
13985
|
+
}
|
|
13986
|
+
if (index._opAliasPairs !== null) index._opAliasPairs = aliasPairs;
|
|
13225
13987
|
}
|
|
13226
13988
|
let changed = aliasPairs.length > 0;
|
|
13227
13989
|
while (changed) {
|
|
@@ -13592,7 +14354,7 @@ function _javaNestedTypeQualifier(raw) {
|
|
|
13592
14354
|
* go: `*ignore.Ig` → Ig; slices/maps/chans/funcs → null
|
|
13593
14355
|
* java: `java.util.List<Foo>` → List; arrays → null
|
|
13594
14356
|
*/
|
|
13595
|
-
function _normalizeFieldTypeName(raw, language) {
|
|
14357
|
+
function _normalizeFieldTypeName(raw, language, options = {}) {
|
|
13596
14358
|
let t = String(raw).trim();
|
|
13597
14359
|
if (language === 'rust') {
|
|
13598
14360
|
let prev;
|
|
@@ -13631,7 +14393,7 @@ function _normalizeFieldTypeName(raw, language) {
|
|
|
13631
14393
|
if (langTraits(language)?.typeSystem === 'structural') {
|
|
13632
14394
|
// JS/TS/Python (fix #219): compiler-true annotation heads, value-
|
|
13633
14395
|
// position semantics — a field declared Promise<X> HOLDS a Promise.
|
|
13634
|
-
return _structuralTypeHead(t, { language });
|
|
14396
|
+
return _structuralTypeHead(t, { language, ...options });
|
|
13635
14397
|
}
|
|
13636
14398
|
return null;
|
|
13637
14399
|
}
|
|
@@ -13874,6 +14636,50 @@ function _nominalChainedReceiverType(index, call, fileEntry, filePath) {
|
|
|
13874
14636
|
return { type: parsed.name, ...(origin.fromFile && { fromFile: origin.fromFile }) };
|
|
13875
14637
|
}
|
|
13876
14638
|
|
|
14639
|
+
/**
|
|
14640
|
+
* Type a Python subscript expression used as a method receiver from the
|
|
14641
|
+
* indexed container's declared `__getitem__` return contract.
|
|
14642
|
+
*
|
|
14643
|
+
* The root must resolve to an exact project type definition. This prevents a
|
|
14644
|
+
* globally unique project `__getitem__` (or a same-named local class) from
|
|
14645
|
+
* lending identity to an external or ambiguous container. The returned value
|
|
14646
|
+
* then follows the same origin-pinned structural return rails as an ordinary
|
|
14647
|
+
* method call.
|
|
14648
|
+
*/
|
|
14649
|
+
function _pythonIndexedReceiverType(
|
|
14650
|
+
index, filePath, call, getFlowMap, cache = null
|
|
14651
|
+
) {
|
|
14652
|
+
if (!call?.receiverSubscriptRoot) return null;
|
|
14653
|
+
let rootType = call.receiverSubscriptRootType;
|
|
14654
|
+
let rootFromFile;
|
|
14655
|
+
if (!rootType && typeof getFlowMap === 'function') {
|
|
14656
|
+
const flow = _lookupReturnTypeFlow(getFlowMap(), {
|
|
14657
|
+
...call,
|
|
14658
|
+
receiver: call.receiverSubscriptRoot,
|
|
14659
|
+
});
|
|
14660
|
+
if (flow?.type) {
|
|
14661
|
+
rootType = flow.type;
|
|
14662
|
+
rootFromFile = flow.fromFile;
|
|
14663
|
+
}
|
|
14664
|
+
}
|
|
14665
|
+
if (!rootType) return null;
|
|
14666
|
+
|
|
14667
|
+
const origin = _resolveFlowTypeOrigin(
|
|
14668
|
+
index, rootFromFile || filePath, rootType,
|
|
14669
|
+
call.receiverSubscriptRootTypeQualifier);
|
|
14670
|
+
if (!origin?.fromFile) return null;
|
|
14671
|
+
|
|
14672
|
+
const key = `${origin.fromFile}\0${rootType}`;
|
|
14673
|
+
if (cache?.has(key)) return cache.get(key);
|
|
14674
|
+
const result = _methodReturnOnType(
|
|
14675
|
+
index, rootType, origin.fromFile, '__getitem__', 'python', {
|
|
14676
|
+
filePath,
|
|
14677
|
+
selfType: rootType,
|
|
14678
|
+
});
|
|
14679
|
+
if (cache) cache.set(key, result || null);
|
|
14680
|
+
return result;
|
|
14681
|
+
}
|
|
14682
|
+
|
|
13877
14683
|
function _chainedReceiverType(index, call, language) {
|
|
13878
14684
|
const defs = (index.symbols.get(call.receiverCall) || [])
|
|
13879
14685
|
.filter(d => !NON_CALLABLE_TYPES.has(d.type));
|
|
@@ -13932,6 +14738,131 @@ function _chainedReceiverType(index, call, language) {
|
|
|
13932
14738
|
|
|
13933
14739
|
const _FOLD_TYPE_KINDS = new Set(['class', 'struct', 'enum', 'trait', 'interface', 'record', 'type', 'namespace']);
|
|
13934
14740
|
|
|
14741
|
+
function _structuralTypeExpression(text) {
|
|
14742
|
+
if (!text || typeof text !== 'string') return null;
|
|
14743
|
+
const source = text.trim();
|
|
14744
|
+
const match = source.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/);
|
|
14745
|
+
if (!match) return null;
|
|
14746
|
+
const qualifiedHead = match[1];
|
|
14747
|
+
const rest = source.slice(qualifiedHead.length).trim();
|
|
14748
|
+
if (!rest) {
|
|
14749
|
+
return { head: qualifiedHead.split('.').pop(), qualifiedHead, args: [], text: source };
|
|
14750
|
+
}
|
|
14751
|
+
if (!rest.startsWith('<') || !rest.endsWith('>')) return null;
|
|
14752
|
+
let depth = 0;
|
|
14753
|
+
for (let i = 0; i < rest.length; i++) {
|
|
14754
|
+
if (rest[i] === '<') depth++;
|
|
14755
|
+
else if (rest[i] === '>') depth--;
|
|
14756
|
+
if (depth === 0 && i !== rest.length - 1) return null;
|
|
14757
|
+
if (depth < 0) return null;
|
|
14758
|
+
}
|
|
14759
|
+
if (depth !== 0) return null;
|
|
14760
|
+
return {
|
|
14761
|
+
head: qualifiedHead.split('.').pop(),
|
|
14762
|
+
qualifiedHead,
|
|
14763
|
+
args: _splitTopLevelGenericArgs(rest.slice(1, -1)).map(arg => arg.trim()),
|
|
14764
|
+
text: source,
|
|
14765
|
+
};
|
|
14766
|
+
}
|
|
14767
|
+
|
|
14768
|
+
function _structuralGenericParameters(generics) {
|
|
14769
|
+
if (!generics || typeof generics !== 'string') return [];
|
|
14770
|
+
const source = generics.trim();
|
|
14771
|
+
if (!source.startsWith('<') || !source.endsWith('>')) return [];
|
|
14772
|
+
return _splitTopLevelGenericArgs(source.slice(1, -1)).map(part => {
|
|
14773
|
+
const match = part.trim().match(/^([A-Za-z_$][\w$]*)/);
|
|
14774
|
+
return match ? match[1] : null;
|
|
14775
|
+
}).filter(Boolean);
|
|
14776
|
+
}
|
|
14777
|
+
|
|
14778
|
+
function _substituteStructuralGenerics(text, bindings) {
|
|
14779
|
+
let result = String(text || '').trim();
|
|
14780
|
+
for (const [name, value] of [...bindings].sort((a, b) => b[0].length - a[0].length)) {
|
|
14781
|
+
if (!value || !_structuralTypeExpression(value)) continue;
|
|
14782
|
+
result = result.replace(new RegExp(`\\b${name}\\b`, 'g'), value);
|
|
14783
|
+
}
|
|
14784
|
+
return result;
|
|
14785
|
+
}
|
|
14786
|
+
|
|
14787
|
+
function _pinnedStructuralTypeDefinition(index, typeName, fromFile) {
|
|
14788
|
+
let defs = (index.symbols.get(typeName) || []).filter(definition =>
|
|
14789
|
+
IDENTITY_TYPE_KINDS.has(definition.type) && definition.file);
|
|
14790
|
+
if (fromFile) {
|
|
14791
|
+
const origin = _resolveFlowTypeOrigin(index, fromFile, typeName);
|
|
14792
|
+
if (!origin?.fromFile) return null;
|
|
14793
|
+
defs = defs.filter(definition => definition.file === origin.fromFile);
|
|
14794
|
+
}
|
|
14795
|
+
return defs.length === 1 ? defs[0] : null;
|
|
14796
|
+
}
|
|
14797
|
+
|
|
14798
|
+
function _singleStructuralParent(text) {
|
|
14799
|
+
if (!text) return null;
|
|
14800
|
+
let angle = 0, square = 0, paren = 0;
|
|
14801
|
+
for (const ch of String(text)) {
|
|
14802
|
+
if (ch === '<') angle++;
|
|
14803
|
+
else if (ch === '>') angle--;
|
|
14804
|
+
else if (ch === '[') square++;
|
|
14805
|
+
else if (ch === ']') square--;
|
|
14806
|
+
else if (ch === '(') paren++;
|
|
14807
|
+
else if (ch === ')') paren--;
|
|
14808
|
+
else if (ch === ',' && angle === 0 && square === 0 && paren === 0) return null;
|
|
14809
|
+
if (angle < 0 || square < 0 || paren < 0) return null;
|
|
14810
|
+
}
|
|
14811
|
+
return angle === 0 && square === 0 && paren === 0 ? String(text).trim() : null;
|
|
14812
|
+
}
|
|
14813
|
+
|
|
14814
|
+
function _structuralFieldTypeExpression(
|
|
14815
|
+
index, typeText, fromFile, fieldName, depth = 0
|
|
14816
|
+
) {
|
|
14817
|
+
if (depth > 12) return null;
|
|
14818
|
+
const expression = _structuralTypeExpression(typeText);
|
|
14819
|
+
if (!expression) return null;
|
|
14820
|
+
const definition = _pinnedStructuralTypeDefinition(
|
|
14821
|
+
index, expression.head, fromFile);
|
|
14822
|
+
if (!definition) return null;
|
|
14823
|
+
const params = _structuralGenericParameters(definition.generics);
|
|
14824
|
+
const bindings = new Map();
|
|
14825
|
+
for (let i = 0; i < params.length && i < expression.args.length; i++) {
|
|
14826
|
+
if (_structuralTypeExpression(expression.args[i])) {
|
|
14827
|
+
bindings.set(params[i], expression.args[i]);
|
|
14828
|
+
}
|
|
14829
|
+
}
|
|
14830
|
+
const fields = (index.symbols.get(fieldName) || []).filter(field =>
|
|
14831
|
+
field.className === expression.head && field.file === definition.file &&
|
|
14832
|
+
(field.type === 'field' || field.memberType === 'field' ||
|
|
14833
|
+
field.memberType === 'private field') && field.fieldType);
|
|
14834
|
+
if (fields.length > 0) {
|
|
14835
|
+
const resolved = new Set(fields.map(field =>
|
|
14836
|
+
_substituteStructuralGenerics(field.fieldType, bindings)));
|
|
14837
|
+
if (resolved.size !== 1) return null;
|
|
14838
|
+
const text = [...resolved][0];
|
|
14839
|
+
const parsed = _structuralTypeExpression(text);
|
|
14840
|
+
if (!parsed) return null;
|
|
14841
|
+
const origin = _resolveFlowTypeOrigin(index, definition.file, parsed.head);
|
|
14842
|
+
return { text, fromFile: origin?.fromFile || definition.file };
|
|
14843
|
+
}
|
|
14844
|
+
const parent = _singleStructuralParent(definition.extends);
|
|
14845
|
+
if (!parent) return null;
|
|
14846
|
+
const parentText = _substituteStructuralGenerics(parent, bindings);
|
|
14847
|
+
const parsedParent = _structuralTypeExpression(parentText);
|
|
14848
|
+
if (!parsedParent) return null;
|
|
14849
|
+
const parentOrigin = _resolveFlowTypeOrigin(
|
|
14850
|
+
index, definition.file, parsedParent.head);
|
|
14851
|
+
if (!parentOrigin?.fromFile) return null;
|
|
14852
|
+
return _structuralFieldTypeExpression(
|
|
14853
|
+
index, parentText, parentOrigin.fromFile, fieldName, depth + 1);
|
|
14854
|
+
}
|
|
14855
|
+
|
|
14856
|
+
function _structuralReturnedReceiverType(index, receiverText, fromFile, fieldNames) {
|
|
14857
|
+
let current = { text: receiverText, fromFile };
|
|
14858
|
+
for (const fieldName of fieldNames) {
|
|
14859
|
+
current = _structuralFieldTypeExpression(
|
|
14860
|
+
index, current.text, current.fromFile, fieldName);
|
|
14861
|
+
if (!current) return null;
|
|
14862
|
+
}
|
|
14863
|
+
return current;
|
|
14864
|
+
}
|
|
14865
|
+
|
|
13935
14866
|
/**
|
|
13936
14867
|
* Resolve method `methodName` on type `typeName` (identity-pinned to
|
|
13937
14868
|
* `fromFile` when known) and return its resolved return-type head as
|
|
@@ -13956,8 +14887,16 @@ function _methodReturnOnType(index, typeName, fromFile, methodName, language, op
|
|
|
13956
14887
|
const typeDefs = (index.symbols.get(typeName) || []).filter(d => _FOLD_TYPE_KINDS.has(d.type));
|
|
13957
14888
|
if (typeDefs.length > 1 && owned.length > 0) {
|
|
13958
14889
|
if (!fromFile) return null;
|
|
13959
|
-
|
|
13960
|
-
|
|
14890
|
+
if (nominal) {
|
|
14891
|
+
const dir = path.dirname(fromFile);
|
|
14892
|
+
owned = owned.filter(d => d.file === fromFile ||
|
|
14893
|
+
(d.file && path.dirname(d.file) === dir));
|
|
14894
|
+
} else {
|
|
14895
|
+
// Structural sibling modules are distinct identities. Directory
|
|
14896
|
+
// co-location is package evidence for Go/Rust/Java impl layouts,
|
|
14897
|
+
// but never merges two Python/JS/TS classes with the same name.
|
|
14898
|
+
owned = owned.filter(d => d.file === fromFile);
|
|
14899
|
+
}
|
|
13961
14900
|
}
|
|
13962
14901
|
if (owned.length === 0) {
|
|
13963
14902
|
// Inheritance walk: resolve on a declared ancestor; Self/this still
|
|
@@ -13989,16 +14928,46 @@ function _methodReturnOnType(index, typeName, fromFile, methodName, language, op
|
|
|
13989
14928
|
// Structural: heads must agree; `this`/`Self` are the receiver's type
|
|
13990
14929
|
// (checked BEFORE the reject set — with a known owner they ARE identity);
|
|
13991
14930
|
// un-awaited async producers stay untyped (the value is a coroutine).
|
|
13992
|
-
|
|
14931
|
+
// TypeScript/Python overload signatures are the public call contract;
|
|
14932
|
+
// their runtime implementation may intentionally omit a return annotation.
|
|
14933
|
+
// Once a compiler-recognized signature group exists, use only those
|
|
14934
|
+
// declarations for result-flow agreement. Conflicting signature heads
|
|
14935
|
+
// still abstain below (fix #316, zod-measured fluent default chains).
|
|
14936
|
+
const contracts = owned.some(d => d.isSignature)
|
|
14937
|
+
? owned.filter(d => d.isSignature) : owned;
|
|
14938
|
+
if (language === 'python' && !opts.consumerAwaited && contracts.some(d => d.isAsync)) return null;
|
|
14939
|
+
const returnedPaths = contracts.map(definition => definition.returnedReceiverPath);
|
|
14940
|
+
if (contracts.length > 0 && returnedPaths.every(path =>
|
|
14941
|
+
Array.isArray(path) && path.length > 0) &&
|
|
14942
|
+
new Set(returnedPaths.map(path => path.join('\0'))).size === 1) {
|
|
14943
|
+
const receiverText = opts.selfTypeText || selfType;
|
|
14944
|
+
const resolved = _structuralReturnedReceiverType(
|
|
14945
|
+
index, receiverText, fromFile || contracts[0].file, returnedPaths[0]);
|
|
14946
|
+
const parsed = resolved && _structuralTypeExpression(resolved.text);
|
|
14947
|
+
if (!parsed || /^[A-Z][A-Z0-9]?$/.test(parsed.head) ||
|
|
14948
|
+
_STRUCTURAL_FLOW_REJECT.has(parsed.head)) return null;
|
|
14949
|
+
const origin = _resolveFlowTypeOrigin(
|
|
14950
|
+
index, resolved.fromFile || contracts[0].file, parsed.head);
|
|
14951
|
+
if (!origin?.fromFile) return null;
|
|
14952
|
+
return {
|
|
14953
|
+
type: parsed.head,
|
|
14954
|
+
typeText: resolved.text,
|
|
14955
|
+
fromFile: origin.fromFile,
|
|
14956
|
+
};
|
|
14957
|
+
}
|
|
13993
14958
|
const heads = new Set();
|
|
13994
|
-
|
|
14959
|
+
const typeTexts = new Set();
|
|
14960
|
+
for (const d of contracts) {
|
|
13995
14961
|
if (!d.returnType) return null;
|
|
13996
|
-
|
|
14962
|
+
const returnText = String(d.returnType).replace(
|
|
14963
|
+
/\b(?:this|Self)\b/g, opts.selfTypeText || selfType);
|
|
14964
|
+
let h = _structuralTypeHead(returnText, {
|
|
13997
14965
|
unwrapAsync: opts.consumerAwaited, index, language, originFile: d.file,
|
|
13998
14966
|
});
|
|
13999
14967
|
if (h === 'this' || h === 'Self') h = selfType;
|
|
14000
14968
|
if (!h) return null;
|
|
14001
14969
|
heads.add(h);
|
|
14970
|
+
typeTexts.add(returnText);
|
|
14002
14971
|
if (heads.size > 1) return null;
|
|
14003
14972
|
}
|
|
14004
14973
|
const head = [...heads][0];
|
|
@@ -14007,16 +14976,23 @@ function _methodReturnOnType(index, typeName, fromFile, methodName, language, op
|
|
|
14007
14976
|
const returnTypeDefs = (index.symbols.get(head) || []).filter(d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
14008
14977
|
if (returnTypeDefs.length > 0) {
|
|
14009
14978
|
const origins = new Set();
|
|
14010
|
-
for (const d of
|
|
14979
|
+
for (const d of contracts) {
|
|
14011
14980
|
const origin = _resolveFlowTypeOrigin(index, d.file || opts.filePath, head);
|
|
14012
14981
|
if (!origin) return null;
|
|
14013
14982
|
origins.add(origin.fromFile);
|
|
14014
14983
|
if (origins.size > 1) return null;
|
|
14015
14984
|
}
|
|
14016
14985
|
const fromFile = [...origins][0];
|
|
14017
|
-
return {
|
|
14986
|
+
return {
|
|
14987
|
+
type: head,
|
|
14988
|
+
...(typeTexts.size === 1 && { typeText: [...typeTexts][0] }),
|
|
14989
|
+
...(fromFile && { fromFile }),
|
|
14990
|
+
};
|
|
14018
14991
|
}
|
|
14019
|
-
return {
|
|
14992
|
+
return {
|
|
14993
|
+
type: head,
|
|
14994
|
+
...(typeTexts.size === 1 && { typeText: [...typeTexts][0] }),
|
|
14995
|
+
};
|
|
14020
14996
|
}
|
|
14021
14997
|
|
|
14022
14998
|
function _rustMacroDefinitions(index, fileEntry, filePath, record) {
|
|
@@ -14501,6 +15477,26 @@ function _typeOfCallResultFold(index, fileEntry, filePath, record, ctx, consumer
|
|
|
14501
15477
|
return out;
|
|
14502
15478
|
}
|
|
14503
15479
|
|
|
15480
|
+
function _returnedCallRecord(ctx, start, end, current) {
|
|
15481
|
+
if (!ctx.returnedCallIndex) {
|
|
15482
|
+
ctx.returnedCallIndex = new Map();
|
|
15483
|
+
const records = ctx.allRecords || ctx.records || [];
|
|
15484
|
+
for (const candidate of records) {
|
|
15485
|
+
if (candidate.callStart == null || candidate.callEnd == null) continue;
|
|
15486
|
+
const key = `${candidate.callStart}:${candidate.callEnd}`;
|
|
15487
|
+
let group = ctx.returnedCallIndex.get(key);
|
|
15488
|
+
if (!group) {
|
|
15489
|
+
group = [];
|
|
15490
|
+
ctx.returnedCallIndex.set(key, group);
|
|
15491
|
+
}
|
|
15492
|
+
group.push(candidate);
|
|
15493
|
+
}
|
|
15494
|
+
}
|
|
15495
|
+
const matches = (ctx.returnedCallIndex.get(`${start}:${end}`) || [])
|
|
15496
|
+
.filter(candidate => candidate !== current);
|
|
15497
|
+
return matches.length === 1 ? matches[0] : null;
|
|
15498
|
+
}
|
|
15499
|
+
|
|
14504
15500
|
function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, consumerAwaited) {
|
|
14505
15501
|
const language = fileEntry.language;
|
|
14506
15502
|
const traits = langTraits(language);
|
|
@@ -14511,6 +15507,27 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14511
15507
|
return _rustMacroCallResultType(index, fileEntry, filePath, record);
|
|
14512
15508
|
}
|
|
14513
15509
|
|
|
15510
|
+
// Structural constructor expression: `Environment().getattr(...)`.
|
|
15511
|
+
// Python/JS class calls are plain call records rather than nominal
|
|
15512
|
+
// constructor records. A unique in-scope class declaration is exact
|
|
15513
|
+
// result-type evidence; a same-named callable or unresolved origin keeps
|
|
15514
|
+
// the chain untyped. This is deliberately identity-pinned through the
|
|
15515
|
+
// calling file's imports, not a project-wide capitalization guess.
|
|
15516
|
+
if (!nominal && !record.isMethod && !record.receiver) {
|
|
15517
|
+
const named = index.symbols.get(name) || [];
|
|
15518
|
+
const typeDefs = named.filter(definition =>
|
|
15519
|
+
IDENTITY_TYPE_KINDS.has(definition.type) && definition.file);
|
|
15520
|
+
const callableDefs = named.filter(definition =>
|
|
15521
|
+
!NON_CALLABLE_TYPES.has(definition.type));
|
|
15522
|
+
if (typeDefs.length === 1 && callableDefs.length === 0) {
|
|
15523
|
+
const origin = _resolveFlowTypeOrigin(index, filePath, name);
|
|
15524
|
+
if (origin?.fromFile === typeDefs[0].file) {
|
|
15525
|
+
return { type: name, fromFile: origin.fromFile,
|
|
15526
|
+
exactConstructor: true };
|
|
15527
|
+
}
|
|
15528
|
+
}
|
|
15529
|
+
}
|
|
15530
|
+
|
|
14514
15531
|
// Path producer (Rust): Command::new(...) — the last path segment names
|
|
14515
15532
|
// the impl type (flow-map rails: module-path producers stay untyped);
|
|
14516
15533
|
// Self::new() resolves through the enclosing impl. The type's identity
|
|
@@ -14575,13 +15592,17 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14575
15592
|
if (!nominal && record.isMethod &&
|
|
14576
15593
|
(record.receiver || record.receiverModuleSpecifier) &&
|
|
14577
15594
|
(record.receiverIsModule || _isStructuralImportReceiver(fileEntry, record))) {
|
|
14578
|
-
const
|
|
14579
|
-
|
|
15595
|
+
const composite = record.receiverModuleComposition
|
|
15596
|
+
? _structuralCompositeModuleOwnership(index, fileEntry, record)
|
|
15597
|
+
: null;
|
|
15598
|
+
if (composite && composite.verdict !== 'yes') return null;
|
|
15599
|
+
const binding = composite?.binding ||
|
|
15600
|
+
_structuralModuleBindings(fileEntry, record)[0];
|
|
15601
|
+
const rel = composite?.rel ||
|
|
15602
|
+
(binding && fileEntry.moduleResolved &&
|
|
15603
|
+
fileEntry.moduleResolved[binding.module]);
|
|
14580
15604
|
if (binding && !rel) {
|
|
14581
|
-
|
|
14582
|
-
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
14583
|
-
if (!mod.startsWith('.') &&
|
|
14584
|
-
!(firstSeg && _projectTopLevelNames(index).has(firstSeg))) {
|
|
15605
|
+
if (!_unresolvedModuleIsGap(index, binding.module, binding)) {
|
|
14585
15606
|
return {
|
|
14586
15607
|
externalVia: `${record.receiver}.${name}`,
|
|
14587
15608
|
...(/^[A-Z]/.test(name) && { externalConcrete: true }),
|
|
@@ -14635,7 +15656,8 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14635
15656
|
return { type: head, ...(fromFile && { fromFile }) };
|
|
14636
15657
|
}
|
|
14637
15658
|
// self/this/cls receiver: resolve through the enclosing class (+ walk).
|
|
14638
|
-
if (record.isMethod &&
|
|
15659
|
+
if (record.isMethod && !record.receiverField &&
|
|
15660
|
+
['self', 'this', 'cls'].includes(record.receiver)) {
|
|
14639
15661
|
const enclosing = index.findEnclosingFunction(filePath, record.line, true);
|
|
14640
15662
|
let cls = enclosing && enclosing.className;
|
|
14641
15663
|
let ctxFile = filePath;
|
|
@@ -14674,6 +15696,12 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14674
15696
|
if (!rt && record.receiverField && record.receiverRoot) {
|
|
14675
15697
|
let rootType = record.receiverRootType;
|
|
14676
15698
|
let rootFromFile;
|
|
15699
|
+
if (!rootType && language === 'python' &&
|
|
15700
|
+
['self', 'cls'].includes(record.receiverRoot)) {
|
|
15701
|
+
rootType = index.findEnclosingFunction(
|
|
15702
|
+
filePath, record.line, true)?.className;
|
|
15703
|
+
if (rootType) rootFromFile = filePath;
|
|
15704
|
+
}
|
|
14677
15705
|
if (!rootType) {
|
|
14678
15706
|
const flowMap = ctx.getFlowMap();
|
|
14679
15707
|
const rootFlow = flowMap && _lookupReturnTypeFlow(flowMap, {
|
|
@@ -14711,6 +15739,19 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14711
15739
|
};
|
|
14712
15740
|
}
|
|
14713
15741
|
}
|
|
15742
|
+
if (!rt && langTraits(language)?.typeSystem === 'structural') {
|
|
15743
|
+
const moduleValueInfo = {};
|
|
15744
|
+
const moduleValueType = _structuralModuleValueFieldType(
|
|
15745
|
+
index, fileEntry, record, moduleValueInfo);
|
|
15746
|
+
if (moduleValueType) {
|
|
15747
|
+
rt = {
|
|
15748
|
+
type: moduleValueType,
|
|
15749
|
+
...(moduleValueInfo.fromFile && {
|
|
15750
|
+
fromFile: moduleValueInfo.fromFile,
|
|
15751
|
+
}),
|
|
15752
|
+
};
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
14714
15755
|
}
|
|
14715
15756
|
if (!rt && record.receiverCall && (!record.receiver || record.receiverIsChainRoot)) {
|
|
14716
15757
|
rt = _foldChainedReceiverType(index, fileEntry, filePath, record, ctx);
|
|
@@ -14739,7 +15780,7 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14739
15780
|
if (builtinReturn) return { type: builtinReturn };
|
|
14740
15781
|
}
|
|
14741
15782
|
return _methodReturnOnType(index, rt.type, rt.fromFile, name, language,
|
|
14742
|
-
{ filePath, consumerAwaited });
|
|
15783
|
+
{ filePath, consumerAwaited, selfTypeText: rt.typeText });
|
|
14743
15784
|
}
|
|
14744
15785
|
if (nominal) return null;
|
|
14745
15786
|
// One-hop agreement (the #207/#219 discipline, one level deeper):
|
|
@@ -14807,7 +15848,24 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
14807
15848
|
const sameFile = defs.filter(d => d.file === filePath);
|
|
14808
15849
|
if (sameFile.length === 1) chosen = sameFile[0];
|
|
14809
15850
|
}
|
|
14810
|
-
if (!chosen
|
|
15851
|
+
if (!chosen) return null;
|
|
15852
|
+
// An expression-bodied arrow returns its expression by construction.
|
|
15853
|
+
// The parser persists the exact span only when that expression IS a call;
|
|
15854
|
+
// resolve that existing call record through the normal compiler-evidence
|
|
15855
|
+
// rails. Async functions expose the inner value only when the consumer
|
|
15856
|
+
// awaits them; generators are never ordinary value factories (fix #317,
|
|
15857
|
+
// zod-measured local `base = () => z.object(...)` flow).
|
|
15858
|
+
if (!nominal && !chosen.returnType && !chosen.isGenerator &&
|
|
15859
|
+
(!chosen.isAsync || consumerAwaited) &&
|
|
15860
|
+
chosen.returnedCallStart != null && chosen.returnedCallEnd != null) {
|
|
15861
|
+
const returned = _returnedCallRecord(
|
|
15862
|
+
ctx, chosen.returnedCallStart, chosen.returnedCallEnd, record);
|
|
15863
|
+
if (returned) {
|
|
15864
|
+
return _typeOfCallResultFold(
|
|
15865
|
+
index, fileEntry, filePath, returned, ctx, consumerAwaited);
|
|
15866
|
+
}
|
|
15867
|
+
}
|
|
15868
|
+
if (!chosen.returnType) return null;
|
|
14811
15869
|
if (nominal) {
|
|
14812
15870
|
if (language === 'cpp') {
|
|
14813
15871
|
const concrete = _cppAutoReturnConcreteType(index, chosen);
|
|
@@ -14902,7 +15960,8 @@ function _foldChainedReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
14902
15960
|
// fallback is not allowed to borrow Mocker.number (or another
|
|
14903
15961
|
// module/version) as its return type. Keep the consumer untyped and
|
|
14904
15962
|
// visible instead of manufacturing exclusion-grade evidence.
|
|
14905
|
-
if (prods.some(r => r.receiverIsModule ||
|
|
15963
|
+
if (prods.some(r => r.receiverIsModule || r.receiverModuleComposition ||
|
|
15964
|
+
_isStructuralImportReceiver(fileEntry, r))) {
|
|
14906
15965
|
return { suppressFallback: true };
|
|
14907
15966
|
}
|
|
14908
15967
|
return null;
|
|
@@ -14916,11 +15975,18 @@ function _foldChainedReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
14916
15975
|
if (results.some(r => r.externalVia)) return null;
|
|
14917
15976
|
if (new Set(results.map(r => r.type)).size !== 1) return null;
|
|
14918
15977
|
const fromFiles = new Set(results.map(r => r.fromFile));
|
|
15978
|
+
const typeTexts = new Set(results.map(r => r.typeText));
|
|
14919
15979
|
let result = {
|
|
14920
15980
|
type: results[0].type,
|
|
15981
|
+
...(typeTexts.size === 1 && results[0].typeText && {
|
|
15982
|
+
typeText: results[0].typeText,
|
|
15983
|
+
}),
|
|
14921
15984
|
...(fromFiles.size === 1 && results[0].fromFile && {
|
|
14922
15985
|
fromFile: results[0].fromFile,
|
|
14923
15986
|
}),
|
|
15987
|
+
...(results.every(r => r.exactConstructor) && {
|
|
15988
|
+
exactConstructor: true,
|
|
15989
|
+
}),
|
|
14924
15990
|
};
|
|
14925
15991
|
if (call.receiverFields?.length) {
|
|
14926
15992
|
const fieldInfo = {};
|
|
@@ -14950,6 +16016,7 @@ function _foldChainedReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
14950
16016
|
// return annotation from an unrelated class. Capitalized named imports remain
|
|
14951
16017
|
// eligible for class/static-method resolution.
|
|
14952
16018
|
function _isStructuralImportReceiver(fileEntry, record) {
|
|
16019
|
+
if (record?.receiverModuleSpecifier || record?.receiverModuleComposition) return true;
|
|
14953
16020
|
if (!record?.receiver || !/^[a-z_$]/.test(record.receiver)) return false;
|
|
14954
16021
|
return (fileEntry?.importBindings || []).some(b => b.name === record.receiver);
|
|
14955
16022
|
}
|
|
@@ -15061,4 +16128,4 @@ function findCallbackUsages(index, name) {
|
|
|
15061
16128
|
return usages;
|
|
15062
16129
|
}
|
|
15063
16130
|
|
|
15064
|
-
module.exports = { getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|
|
16131
|
+
module.exports = { _unresolvedModuleIsGap, getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|