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