ucn 5.1.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/ucn/SKILL.md +11 -4
- package/.claude/skills/ucn/references/commands.md +2 -2
- package/README.md +28 -13
- package/core/cache.js +28 -1
- package/core/callers.js +1242 -57
- package/core/graph-build.js +6 -0
- package/core/index-ir.js +3 -2
- package/core/ir.js +3 -2
- package/core/output/refactoring.js +1 -1
- package/core/project.js +32 -0
- package/core/search.js +9 -0
- package/core/verify.js +1084 -36
- package/languages/c-family.js +231 -9
- package/languages/csharp.js +14 -3
- package/languages/go.js +473 -71
- package/languages/javascript.js +85 -3
- package/languages/python.js +364 -95
- package/languages/rust.js +87 -4
- package/languages/utils.js +11 -0
- package/mcp/server.js +99 -103
- package/mcp/stdio-server.js +296 -0
- package/package.json +10 -8
package/core/callers.js
CHANGED
|
@@ -660,6 +660,35 @@ function findCallers(index, name, options = {}) {
|
|
|
660
660
|
continue;
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
+
if (fileEntry.language === 'cpp' && call.macroArguments) {
|
|
664
|
+
const macroDisposition = _cppMacroTargetDisposition(
|
|
665
|
+
index, filePath, call,
|
|
666
|
+
options.targetDefinitions || definitions);
|
|
667
|
+
if (macroDisposition?.kind === 'other') {
|
|
668
|
+
recordExcluded(filePath, call.line,
|
|
669
|
+
'macro-requalified');
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (macroDisposition?.kind === 'uncertain') {
|
|
673
|
+
if (collectAccount) {
|
|
674
|
+
routeUnverified(
|
|
675
|
+
filePath, fileEntry, call,
|
|
676
|
+
'macro-expansion', calledAs, {
|
|
677
|
+
uncertaintyClass: 'compile-time-dispatch',
|
|
678
|
+
dispatchFamily:
|
|
679
|
+
`${call.name} macro expansion`,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (macroDisposition?.kind === 'qualified') {
|
|
685
|
+
call = macroDisposition.qualifier === 'global'
|
|
686
|
+
? { ...call, globalQualified: true }
|
|
687
|
+
: { ...call, isMethod: true, isPathCall: true,
|
|
688
|
+
receiver: macroDisposition.qualifier };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
663
692
|
// Static member syntax carries two compiler symbols:
|
|
664
693
|
// `JValue.Compare(...)` calls Compare and references the
|
|
665
694
|
// JValue type. Class/type queries promise usages rather than
|
|
@@ -1180,8 +1209,10 @@ function findCallers(index, name, options = {}) {
|
|
|
1180
1209
|
} };
|
|
1181
1210
|
foldCtxCache.set(filePath, foldCtx);
|
|
1182
1211
|
}
|
|
1183
|
-
const flowEntry =
|
|
1184
|
-
index, fileEntry, filePath, call
|
|
1212
|
+
const flowEntry = _goBuiltinChainedReceiverType(
|
|
1213
|
+
index, fileEntry, filePath, call) ||
|
|
1214
|
+
_foldChainedReceiverType(
|
|
1215
|
+
index, fileEntry, filePath, call, foldCtx) ||
|
|
1185
1216
|
_nominalChainedReceiverType(
|
|
1186
1217
|
index, call, fileEntry, filePath);
|
|
1187
1218
|
if (flowEntry && flowEntry.externalVia) {
|
|
@@ -1412,7 +1443,50 @@ function findCallers(index, name, options = {}) {
|
|
|
1412
1443
|
if (td.className) targetTypes.add(td.className);
|
|
1413
1444
|
if (td.receiver) targetTypes.add(td.receiver.replace(/^\*/, ''));
|
|
1414
1445
|
}
|
|
1415
|
-
|
|
1446
|
+
// Qualified receiver types resolve their package FIRST
|
|
1447
|
+
// (fix #298 — the #273 gate's callback twin):
|
|
1448
|
+
// `netDial = (&net.Dialer{}).DialContext` is net's
|
|
1449
|
+
// Dialer; the bare name must never match a project
|
|
1450
|
+
// `Dialer` pin, and the external contract must carry
|
|
1451
|
+
// its label so consumers can defer the site.
|
|
1452
|
+
if (fileEntry.language === 'go' &&
|
|
1453
|
+
call.receiverType && call.receiverTypeQualifier) {
|
|
1454
|
+
const cbQualified = _goQualifiedReceiverType(
|
|
1455
|
+
index, fileEntry, call.receiverTypeQualifier,
|
|
1456
|
+
call.receiverType);
|
|
1457
|
+
if (cbQualified) {
|
|
1458
|
+
const cbIsContract = cbQualified.kind === 'project' &&
|
|
1459
|
+
cbQualified.defs.some(d =>
|
|
1460
|
+
d.type === 'interface' || d.type === 'trait');
|
|
1461
|
+
if (cbQualified.kind !== 'project' || cbIsContract) {
|
|
1462
|
+
if (collectAccount) {
|
|
1463
|
+
const cbPlatformConcrete = cbQualified.kind !== 'project' &&
|
|
1464
|
+
getLanguageAdapter('go')?.isPlatformConcreteType?.(
|
|
1465
|
+
call.receiverTypeQualifier, call.receiverType);
|
|
1466
|
+
if (cbPlatformConcrete) {
|
|
1467
|
+
recordExcluded(filePath, call.line, 'external-package');
|
|
1468
|
+
} else {
|
|
1469
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
1470
|
+
dispatchVia: cbQualified.via,
|
|
1471
|
+
dispatchCandidates: countDispatchCandidates(call.receiverType),
|
|
1472
|
+
...(cbQualified.kind !== 'project' && { externalContract: true }),
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
} else {
|
|
1479
|
+
// Unresolvable qualifier — the bare type name
|
|
1480
|
+
// must not match a project pin (#206c). Visible,
|
|
1481
|
+
// never validated, never excluded.
|
|
1482
|
+
if (collectAccount) {
|
|
1483
|
+
routeUnverified(filePath, fileEntry, call, 'method-ambiguous', calledAs,
|
|
1484
|
+
{ dispatchCandidates: countDispatchCandidates(call.receiverType) });
|
|
1485
|
+
}
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if (targetTypes.size > 0 && call.receiverType &&
|
|
1416
1490
|
!targetTypes.has(call.receiverType)) {
|
|
1417
1491
|
// Raw-set mismatch — check the CLOSED set (aliases +
|
|
1418
1492
|
// non-overriding subtypes incl. Go embedding) before
|
|
@@ -2461,15 +2535,27 @@ function findCallers(index, name, options = {}) {
|
|
|
2461
2535
|
}
|
|
2462
2536
|
const inherited = _isAncestorOfTargetClass(
|
|
2463
2537
|
index, d.className, [boundDef]);
|
|
2538
|
+
const derivedApplicableSibling = definitions.some(candidate =>
|
|
2539
|
+
candidate !== boundDef &&
|
|
2540
|
+
!NON_CALLABLE_TYPES.has(candidate.type) &&
|
|
2541
|
+
candidate.className === boundDef.className &&
|
|
2542
|
+
candidate.file === boundDef.file &&
|
|
2543
|
+
_overloadApplicable(index, call, candidate));
|
|
2464
2544
|
// A derived same-name method hides inherited
|
|
2465
2545
|
// overloads until it proves inapplicable.
|
|
2466
2546
|
// Receiver-blind bindings may therefore be
|
|
2467
2547
|
// overruled only when static argument evidence
|
|
2468
2548
|
// accepts this inherited target and rejects the
|
|
2469
|
-
// derived
|
|
2549
|
+
// ENTIRE derived overload family. Looking only
|
|
2550
|
+
// at the first bound declaration let an
|
|
2551
|
+
// inapplicable one-arg sibling expose a hidden
|
|
2552
|
+
// base method even when another derived sibling
|
|
2553
|
+
// accepted the call (Newtonsoft's new static
|
|
2554
|
+
// JArray/JObject/JProperty.Load families).
|
|
2470
2555
|
return inherited &&
|
|
2471
2556
|
_overloadApplicable(index, call, d) &&
|
|
2472
|
-
!_overloadApplicable(index, call, boundDef)
|
|
2557
|
+
!_overloadApplicable(index, call, boundDef) &&
|
|
2558
|
+
!derivedApplicableSibling;
|
|
2473
2559
|
})) ||
|
|
2474
2560
|
(fileEntry.language === 'cpp' &&
|
|
2475
2561
|
!boundDef.className && !boundDef.receiver &&
|
|
@@ -2854,15 +2940,32 @@ function findCallers(index, name, options = {}) {
|
|
|
2854
2940
|
const recvBindings = recvExportedNamespace
|
|
2855
2941
|
? [] : _structuralModuleBindings(fileEntry, call);
|
|
2856
2942
|
const tFiles = targetDefinitionFiles;
|
|
2857
|
-
|
|
2943
|
+
// Same-file targets get NO bypass (fix #294, flask-measured:
|
|
2944
|
+
// `import json as _json; _json.dump(...)` in the file
|
|
2945
|
+
// defining flask's own `dump` confirmed a self-recursive
|
|
2946
|
+
// caller — the module indirection REPLACES file scope, so
|
|
2947
|
+
// ownership adjudicates regardless of where the pin sits;
|
|
2948
|
+
// the Go twin already excludes same-file package-qualified
|
|
2949
|
+
// targets). A self-module import stays confirmable: a
|
|
2950
|
+
// binding resolving into a target file reaches immediately.
|
|
2951
|
+
if (recvBindings.length > 0 || recvExportedNamespace) {
|
|
2858
2952
|
let reaches = recvExportedNamespace?.verdict === 'yes';
|
|
2859
2953
|
let projectish = !!recvExportedNamespace;
|
|
2860
2954
|
let undetermined = recvExportedNamespace?.verdict === 'unknown';
|
|
2861
2955
|
let resolvedBindings = recvExportedNamespace ? 1 : 0;
|
|
2862
2956
|
let definitiveOtherBindings = recvExportedNamespace?.verdict === 'no' ? 1 : 0;
|
|
2863
2957
|
for (const b of recvBindings) {
|
|
2864
|
-
|
|
2865
|
-
|
|
2958
|
+
// A #224-proven submodule receiver chases from the
|
|
2959
|
+
// SUBMODULE file, never the from-module (fix #294b,
|
|
2960
|
+
// flask fresh-arm-measured: `from . import cli;
|
|
2961
|
+
// cli.AppGroup()` chased flask/__init__.py — where
|
|
2962
|
+
// 'AppGroup' dead-ends — because moduleResolved['.']
|
|
2963
|
+
// shadowed the composed '.cli' spec, excluding a
|
|
2964
|
+
// compiler-true constructor edge other-definition-
|
|
2965
|
+
// import. Python binds the submodule OBJECT; its
|
|
2966
|
+
// attributes live in the submodule file.
|
|
2967
|
+
const rel = recvSubmoduleRel ||
|
|
2968
|
+
(fileEntry.moduleResolved && fileEntry.moduleResolved[b.module]);
|
|
2866
2969
|
if (!rel) {
|
|
2867
2970
|
const mod = String(b.module);
|
|
2868
2971
|
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
@@ -3084,7 +3187,8 @@ function findCallers(index, name, options = {}) {
|
|
|
3084
3187
|
}
|
|
3085
3188
|
if (knownType && !BUILTIN_RECEIVER_TYPES.has(knownType) &&
|
|
3086
3189
|
_isGenericParamReceiverType(index, filePath, call.line, knownType)) {
|
|
3087
|
-
knownType =
|
|
3190
|
+
knownType = _genericParamTraitTarget(
|
|
3191
|
+
index, filePath, call.line, knownType, targetDefs);
|
|
3088
3192
|
}
|
|
3089
3193
|
if (knownType) {
|
|
3090
3194
|
const explicitInterfaceTarget = fileEntry.language === 'csharp' &&
|
|
@@ -3183,7 +3287,14 @@ function findCallers(index, name, options = {}) {
|
|
|
3183
3287
|
(call.receiverTypeQualifier && call.receiverTypeFlowFile)) {
|
|
3184
3288
|
const identity = _resolveStructuralFlowTypeIdentity(
|
|
3185
3289
|
index, call.receiverTypeFlowFile || filePath, knownType, targetDefs,
|
|
3186
|
-
call.receiverTypeFlowFile ? undefined : call.receiverTypeQualifier
|
|
3290
|
+
call.receiverTypeFlowFile ? undefined : call.receiverTypeQualifier,
|
|
3291
|
+
(call.receiverTypeFlowFile &&
|
|
3292
|
+
call.receiverTypeFlowFile !== filePath) ? undefined : {
|
|
3293
|
+
file: filePath,
|
|
3294
|
+
line: call.line,
|
|
3295
|
+
scopeStart: call.enclosingFunction?.startLine,
|
|
3296
|
+
scopeEnd: call.enclosingFunction?.endLine,
|
|
3297
|
+
});
|
|
3187
3298
|
if (identity === 'other') {
|
|
3188
3299
|
receiverTypeValidated = false;
|
|
3189
3300
|
} else if (identity === 'unknown') {
|
|
@@ -3200,7 +3311,14 @@ function findCallers(index, name, options = {}) {
|
|
|
3200
3311
|
// ancestry before treating it as unrelated.
|
|
3201
3312
|
const identity = _resolveStructuralFlowTypeIdentity(
|
|
3202
3313
|
index, call.receiverTypeFlowFile || filePath, knownType, targetDefs,
|
|
3203
|
-
call.receiverTypeFlowFile ? undefined : call.receiverTypeQualifier
|
|
3314
|
+
call.receiverTypeFlowFile ? undefined : call.receiverTypeQualifier,
|
|
3315
|
+
(call.receiverTypeFlowFile &&
|
|
3316
|
+
call.receiverTypeFlowFile !== filePath) ? undefined : {
|
|
3317
|
+
file: filePath,
|
|
3318
|
+
line: call.line,
|
|
3319
|
+
scopeStart: call.enclosingFunction?.startLine,
|
|
3320
|
+
scopeEnd: call.enclosingFunction?.endLine,
|
|
3321
|
+
});
|
|
3204
3322
|
if (identity === 'target') {
|
|
3205
3323
|
receiverTypeValidated = true;
|
|
3206
3324
|
} else if (identity === 'unknown') {
|
|
@@ -3494,6 +3612,31 @@ function findCallers(index, name, options = {}) {
|
|
|
3494
3612
|
// resolution above owns it.
|
|
3495
3613
|
if (call.isPathCall && /^[A-Z]/.test(receiverSegment) &&
|
|
3496
3614
|
receiverSegment !== 'Self') {
|
|
3615
|
+
// Generic-param carve-out (fix #296 — the
|
|
3616
|
+
// #222(2) single-def branch had this, the
|
|
3617
|
+
// multi-def fallback didn't): `F::as_cast(x)`
|
|
3618
|
+
// instantiates with ANY bound-satisfying
|
|
3619
|
+
// type — visible, never excluded.
|
|
3620
|
+
if (collectAccount &&
|
|
3621
|
+
/^[A-Z][A-Z0-9]?$/.test(receiverSegment) &&
|
|
3622
|
+
!(index.symbols.get(receiverSegment) || []).some(d => IDENTITY_TYPE_KINDS.has(d.type))) {
|
|
3623
|
+
routeUnverified(filePath, fileEntry, call, 'method-ambiguous', calledAs, {
|
|
3624
|
+
dispatchCandidates: methodOwnerKeys().size,
|
|
3625
|
+
});
|
|
3626
|
+
continue;
|
|
3627
|
+
}
|
|
3628
|
+
// Trait-declaration pin (fix #296): a
|
|
3629
|
+
// concrete non-target receiver can
|
|
3630
|
+
// implement the pinned trait — route
|
|
3631
|
+
// visible, never exclude.
|
|
3632
|
+
const traitVia = collectAccount &&
|
|
3633
|
+
_traitDeclPinImplementorRoute(index, fileEntry, receiverSegment, targetDefs);
|
|
3634
|
+
if (traitVia) {
|
|
3635
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
3636
|
+
dispatchVia: `${receiverSegment} — ${traitVia} implementor`,
|
|
3637
|
+
});
|
|
3638
|
+
continue;
|
|
3639
|
+
}
|
|
3497
3640
|
isUncertain = true;
|
|
3498
3641
|
typeMismatch = true;
|
|
3499
3642
|
if (collectAccount) {
|
|
@@ -3510,7 +3653,9 @@ function findCallers(index, name, options = {}) {
|
|
|
3510
3653
|
const t = d.className || (d.receiver && d.receiver.replace(/^\*/, ''));
|
|
3511
3654
|
if (t && !targetTypes.has(t)) nonTargetClasses.add(t);
|
|
3512
3655
|
}
|
|
3513
|
-
const
|
|
3656
|
+
const matchesOtherExact = [...nonTargetClasses].some(cn => cn === receiverSegment);
|
|
3657
|
+
const matchesOther = matchesOtherExact ||
|
|
3658
|
+
[...nonTargetClasses].some(cn => cn.toLowerCase() === receiverLower);
|
|
3514
3659
|
if (matchesOther) {
|
|
3515
3660
|
isUncertain = true;
|
|
3516
3661
|
typeMismatch = true;
|
|
@@ -3530,6 +3675,21 @@ function findCallers(index, name, options = {}) {
|
|
|
3530
3675
|
});
|
|
3531
3676
|
continue;
|
|
3532
3677
|
}
|
|
3678
|
+
// fix #300: a CROSS-CASE receiver-name→class
|
|
3679
|
+
// match is the naming-convention guess
|
|
3680
|
+
// (`storage` ~ `Storage`) — a guess is never
|
|
3681
|
+
// exclusion evidence (#266(2)). itertools-
|
|
3682
|
+
// measured: a test-file `struct Iter` poisoned
|
|
3683
|
+
// every `iter`-named receiver project-wide,
|
|
3684
|
+
// excluding rust-analyzer-attested true edges.
|
|
3685
|
+
// Exact-case matches keep excluding (grpc-go
|
|
3686
|
+
// names builder structs and locals both `bb`).
|
|
3687
|
+
if (!matchesOtherExact) {
|
|
3688
|
+
routeUnverified(filePath, fileEntry, call, 'method-ambiguous', calledAs, {
|
|
3689
|
+
dispatchCandidates: methodOwnerKeys().size,
|
|
3690
|
+
});
|
|
3691
|
+
continue;
|
|
3692
|
+
}
|
|
3533
3693
|
recordExcluded(filePath, call.line, 'receiver-other-class');
|
|
3534
3694
|
continue;
|
|
3535
3695
|
}
|
|
@@ -3641,12 +3801,21 @@ function findCallers(index, name, options = {}) {
|
|
|
3641
3801
|
const targetDefs2 = options.targetDefinitions || definitions;
|
|
3642
3802
|
const targetFiles2 = new Set(targetDefs2.map(d => d.file).filter(Boolean));
|
|
3643
3803
|
const callerImports = index.importGraph.get(filePath);
|
|
3644
|
-
|
|
3804
|
+
// The caller's OWN file never counts as an import-edge hit
|
|
3805
|
+
// (fix #294): an import cycle (flask json/__init__ →
|
|
3806
|
+
// wrappers → json/__init__) would launder same-file
|
|
3807
|
+
// membership into "import edge" evidence, bypassing the
|
|
3808
|
+
// same-file clause's receiver guard below. Same-file
|
|
3809
|
+
// evidence is governed there, not here.
|
|
3810
|
+
let importEdgeLink = !!(callerImports && setSome(callerImports,
|
|
3811
|
+
imp => imp !== filePath && targetFiles2.has(imp)));
|
|
3645
3812
|
// Check one level of re-exports (barrel files) for import evidence
|
|
3646
3813
|
if (!importEdgeLink && callerImports) {
|
|
3647
3814
|
for (const imp of callerImports) {
|
|
3815
|
+
if (imp === filePath) continue;
|
|
3648
3816
|
const transImports = index.importGraph.get(imp);
|
|
3649
|
-
if (transImports && setSome(transImports,
|
|
3817
|
+
if (transImports && setSome(transImports,
|
|
3818
|
+
ti => ti !== filePath && targetFiles2.has(ti))) {
|
|
3650
3819
|
importEdgeLink = true;
|
|
3651
3820
|
break;
|
|
3652
3821
|
}
|
|
@@ -3842,6 +4011,21 @@ function findCallers(index, name, options = {}) {
|
|
|
3842
4011
|
});
|
|
3843
4012
|
continue;
|
|
3844
4013
|
}
|
|
4014
|
+
// Trait-declaration pin (fix #296, serde-as_cast-
|
|
4015
|
+
// measured): `u64::as_cast` / `Limb::as_cast` /
|
|
4016
|
+
// `Self::Unsigned::as_cast` under the trait's own
|
|
4017
|
+
// method pin dispatch through the pinned slot —
|
|
4018
|
+
// macro-generated impls are invisible to the
|
|
4019
|
+
// index, so the receiver not being a target type
|
|
4020
|
+
// proves nothing. Route visible, never exclude.
|
|
4021
|
+
const traitVia2 = _traitDeclPinImplementorRoute(
|
|
4022
|
+
index, fileEntry, pathReceiverSegment, targetDefs2);
|
|
4023
|
+
if (traitVia2) {
|
|
4024
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
4025
|
+
dispatchVia: `${pathReceiverSegment} — ${traitVia2} implementor`,
|
|
4026
|
+
});
|
|
4027
|
+
continue;
|
|
4028
|
+
}
|
|
3845
4029
|
recordExcluded(filePath, call.line, 'path-type-mismatch');
|
|
3846
4030
|
continue;
|
|
3847
4031
|
}
|
|
@@ -4250,6 +4434,42 @@ function findCallers(index, name, options = {}) {
|
|
|
4250
4434
|
});
|
|
4251
4435
|
continue;
|
|
4252
4436
|
}
|
|
4437
|
+
// Module-rooted dotted receiver (fix #294, flask-
|
|
4438
|
+
// measured: `flask.json.load(out)` — receiverRoot
|
|
4439
|
+
// resolves to a MODULE import binding, so the call
|
|
4440
|
+
// dispatches through a module attribute the field-hop
|
|
4441
|
+
// machinery cannot type. Single project-wide ownership
|
|
4442
|
+
// of the method name is not evidence about a module
|
|
4443
|
+
// attribute's value: demote-only, visible, attributed
|
|
4444
|
+
// via the dotted path. A root the hop DID type is
|
|
4445
|
+
// handled above (knownDispatchType); a from-import
|
|
4446
|
+
// root counts only when the resolver PROVED it a
|
|
4447
|
+
// submodule (#224 — a from-import name may be a
|
|
4448
|
+
// plain symbol, never assume).
|
|
4449
|
+
if (!typeQualifiedReceiver && !knownDispatchType &&
|
|
4450
|
+
call.receiverRoot && !call.receiverRootType &&
|
|
4451
|
+
langTraits(fileEntry.language)?.typeSystem === 'structural') {
|
|
4452
|
+
const rootBinding = (fileEntry.importBindings || []).find(b =>
|
|
4453
|
+
b && b.name === call.receiverRoot);
|
|
4454
|
+
const rootIsModule = !!rootBinding && (
|
|
4455
|
+
rootBinding.kind === 'import' ||
|
|
4456
|
+
!!_submoduleReceiverModule(index, fileEntry, call.receiverRoot));
|
|
4457
|
+
if (rootIsModule) {
|
|
4458
|
+
const field = call.receiverField ? `.${call.receiverField}` : '';
|
|
4459
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
4460
|
+
dispatchVia: `${call.receiverRoot}${field} — module attribute`,
|
|
4461
|
+
// Structured marker (outcome-eval-measured,
|
|
4462
|
+
// 2026-08-19): the site's name binds the
|
|
4463
|
+
// module's export surface, not the pinned
|
|
4464
|
+
// def — rename policies defer these like
|
|
4465
|
+
// external contracts (renaming the line
|
|
4466
|
+
// edits whatever the module exports under
|
|
4467
|
+
// the name, which the engine could not pin).
|
|
4468
|
+
moduleAttribute: true,
|
|
4469
|
+
});
|
|
4470
|
+
continue;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4253
4473
|
// Unshadowed builtin-global receiver (fix #232):
|
|
4254
4474
|
// JSON.parse/console.log resolve on the host object,
|
|
4255
4475
|
// never a same-named project method. A lexical binding
|
|
@@ -4926,7 +5146,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
4926
5146
|
};
|
|
4927
5147
|
|
|
4928
5148
|
let siteOrdinal = -1;
|
|
4929
|
-
for (
|
|
5149
|
+
for (let call of calls) {
|
|
4930
5150
|
siteOrdinal++;
|
|
4931
5151
|
const siteId = siteOrdinal;
|
|
4932
5152
|
// Filter to calls within this function's scope
|
|
@@ -4948,6 +5168,30 @@ function findCallees(index, definition, options = {}) {
|
|
|
4948
5168
|
continue;
|
|
4949
5169
|
}
|
|
4950
5170
|
|
|
5171
|
+
if (language === 'cpp' && call.macroArguments) {
|
|
5172
|
+
const macroDisposition = _cppMacroTargetDisposition(
|
|
5173
|
+
index, def.file, call, index.symbols.get(call.name) || []);
|
|
5174
|
+
if (macroDisposition?.kind === 'other') {
|
|
5175
|
+
noteSite(siteId, 'excluded', 'macro-requalified', call);
|
|
5176
|
+
continue;
|
|
5177
|
+
}
|
|
5178
|
+
if (macroDisposition?.kind === 'uncertain') {
|
|
5179
|
+
if (collectAccount) {
|
|
5180
|
+
noteUnverified(siteId, call, 'macro-expansion', {
|
|
5181
|
+
uncertaintyClass: 'compile-time-dispatch',
|
|
5182
|
+
dispatchFamily: `${call.name} macro expansion`,
|
|
5183
|
+
});
|
|
5184
|
+
}
|
|
5185
|
+
continue;
|
|
5186
|
+
}
|
|
5187
|
+
if (macroDisposition?.kind === 'qualified') {
|
|
5188
|
+
call = macroDisposition.qualifier === 'global'
|
|
5189
|
+
? { ...call, globalQualified: true }
|
|
5190
|
+
: { ...call, isMethod: true, isPathCall: true,
|
|
5191
|
+
receiver: macroDisposition.qualifier };
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
|
|
4951
5195
|
// C# extension methods are statically resolved compiler calls,
|
|
4952
5196
|
// despite using instance-call syntax. Resolve them before the
|
|
4953
5197
|
// ordinary receiver-owner path: the receiver type matches the
|
|
@@ -5027,6 +5271,18 @@ function findCallees(index, definition, options = {}) {
|
|
|
5027
5271
|
const directReceiverFlow = mayNeedDirectReceiverFlow(call)
|
|
5028
5272
|
? _lookupReturnTypeFlow(flowMap(), call)
|
|
5029
5273
|
: undefined;
|
|
5274
|
+
// Callee-side twin of the structural caller gate (#222(4)). A
|
|
5275
|
+
// local receiver whose nearest producer was examined but could
|
|
5276
|
+
// not be typed (`C2 = decorator(Base); value = C2()`) has unknown
|
|
5277
|
+
// runtime identity. Never let unique project-wide spelling turn
|
|
5278
|
+
// that into an exact callee; keep the site visible and conserved.
|
|
5279
|
+
if (collectAccount && mayNeedDirectReceiverFlow(call) &&
|
|
5280
|
+
!directReceiverFlow && _receiverAssignedUntyped(flowMap(), call)) {
|
|
5281
|
+
noteUnverified(siteId, call, 'possible-dispatch', {
|
|
5282
|
+
dispatchVia: 'local receiver',
|
|
5283
|
+
});
|
|
5284
|
+
continue;
|
|
5285
|
+
}
|
|
5030
5286
|
|
|
5031
5287
|
// Declared-field receiver hop (fix #231 — callee-side parity
|
|
5032
5288
|
// with the caller side's #202/#219): `tm.service.Save()` /
|
|
@@ -5679,8 +5935,18 @@ function findCallees(index, definition, options = {}) {
|
|
|
5679
5935
|
const shadowsBuiltin = !call.receiver && (
|
|
5680
5936
|
fileEntry?.importBindings?.some(b => b.name === call.name) ||
|
|
5681
5937
|
fileEntry?.bindings?.some(b => b.name === call.name));
|
|
5682
|
-
|
|
5683
|
-
|
|
5938
|
+
// A method call on a receiver TYPED to a project class is
|
|
5939
|
+
// evidence for a project method, not the builtin (fix #300,
|
|
5940
|
+
// attrs callee arm: `c2.classmethod()` on constructor-typed
|
|
5941
|
+
// C2Slots was routed external because `classmethod` sits in the
|
|
5942
|
+
// Python builtin set — the #238 self-shaped exemption's typed-
|
|
5943
|
+
// receiver twin). The receiver-type routing below decides.
|
|
5944
|
+
const typedProjectReceiver = call.isMethod && call.receiverType &&
|
|
5945
|
+
typeof call.receiverType === 'string' &&
|
|
5946
|
+
(index.symbols.get(call.receiverType) || [])
|
|
5947
|
+
.some(d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
5948
|
+
if (!selfShaped && !typedProjectReceiver && !call.receiverIsModule &&
|
|
5949
|
+
!shadowsBuiltin && index.isKeyword(call.name, language)) {
|
|
5684
5950
|
noteSite(siteId, 'external', null, call);
|
|
5685
5951
|
continue;
|
|
5686
5952
|
}
|
|
@@ -5789,8 +6055,17 @@ function findCallees(index, definition, options = {}) {
|
|
|
5789
6055
|
// name through the same #217 export-chain discipline used for
|
|
5790
6056
|
// module receivers. Only intercept when an explicit binding of
|
|
5791
6057
|
// this name exists; ordinary locals continue to lexical binding.
|
|
5792
|
-
|
|
5793
|
-
|
|
6058
|
+
// fix #300 (itertools-measured): the gate covers every language
|
|
6059
|
+
// where a bare call cannot denote a method (#220(4)) — Rust
|
|
6060
|
+
// `use itertools::free::merge_join_by;` owns the bare name, and
|
|
6061
|
+
// canonical-order def selection was confirming the TRAIT METHOD
|
|
6062
|
+
// (lib.rs Itertools::merge_join_by) instead of the imported free
|
|
6063
|
+
// fn. Go item-level import bindings don't exist (package-
|
|
6064
|
+
// qualified imports only), so the route is a structural+Rust
|
|
6065
|
+
// change in practice; Java (bareCallReachesMethods) keeps its
|
|
6066
|
+
// implicit-this/static-import machinery.
|
|
6067
|
+
if (!call.isMethod && !call.receiver && !call.isConstructor &&
|
|
6068
|
+
!langTraits(language)?.bareCallReachesMethods) {
|
|
5794
6069
|
const importRoute = _calleeStructuralImportedNameRoute(index, fileEntry, call, language);
|
|
5795
6070
|
if (importRoute) {
|
|
5796
6071
|
if (importRoute.matches?.length) {
|
|
@@ -5820,6 +6095,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
5820
6095
|
let isUncertain = call.uncertain;
|
|
5821
6096
|
let uncertainReason = null; // account-mode reason for the unverified bucket
|
|
5822
6097
|
let compilerResolvedBare = false;
|
|
6098
|
+
// A bare call can never bind a METHOD def in languages without
|
|
6099
|
+
// implicit-this (fix #300, #220(4) on the callee side) — drives
|
|
6100
|
+
// the bindings filter below and the finalization kind rule.
|
|
6101
|
+
const bareKindFiltered = !call.isMethod && !call.receiver &&
|
|
6102
|
+
!call.isConstructor &&
|
|
6103
|
+
!langTraits(language)?.bareCallReachesMethods;
|
|
5823
6104
|
if (!call.bindingId && language === 'cpp' &&
|
|
5824
6105
|
!call.isMethod && !call.receiver && !call.isConstructor) {
|
|
5825
6106
|
// C++ namespace/free-function lookup is declaration-order
|
|
@@ -5892,6 +6173,16 @@ function findCallees(index, definition, options = {}) {
|
|
|
5892
6173
|
if (call.isConstructor) {
|
|
5893
6174
|
bindings = bindings.filter(binding => binding.type !== 'field');
|
|
5894
6175
|
}
|
|
6176
|
+
// A bare call can never bind a METHOD def (fix #300, the
|
|
6177
|
+
// caller side's #220(4)/#222(3) filter brought to the callee
|
|
6178
|
+
// direction): inside Itertools::merge_join_by, the body's
|
|
6179
|
+
// `merge_join_by(self, other, cmp_fn)` bound the ENCLOSING
|
|
6180
|
+
// trait method through the file bindings table and confirmed
|
|
6181
|
+
// a self-edge — the compiler's target is the free fn. Java
|
|
6182
|
+
// (bareCallReachesMethods: implicit-this) keeps its bindings.
|
|
6183
|
+
if (bareKindFiltered) {
|
|
6184
|
+
bindings = bindings.filter(binding => binding.type !== 'method');
|
|
6185
|
+
}
|
|
5895
6186
|
// Method call with no binding for the method name:
|
|
5896
6187
|
// Different strategies by language family:
|
|
5897
6188
|
if (bindings.length === 0 && call.isMethod) {
|
|
@@ -5903,7 +6194,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
5903
6194
|
// CacheService.set. Builtin-typed receivers are host calls;
|
|
5904
6195
|
// a receiver typed to a project class resolves to that class
|
|
5905
6196
|
// (or an ancestor defining the method), never by bare name.
|
|
5906
|
-
const route = _calleeReceiverTypeRoute(index, call, localTypes, language
|
|
6197
|
+
const route = _calleeReceiverTypeRoute(index, call, localTypes, language, {
|
|
6198
|
+
file: def.file,
|
|
6199
|
+
line: call.line,
|
|
6200
|
+
scopeStart: def.startLine,
|
|
6201
|
+
scopeEnd: def.endLine,
|
|
6202
|
+
});
|
|
5907
6203
|
if (route?.external) {
|
|
5908
6204
|
noteSite(siteId, 'external', null, call);
|
|
5909
6205
|
continue;
|
|
@@ -6298,6 +6594,10 @@ function findCallees(index, definition, options = {}) {
|
|
|
6298
6594
|
name: effectiveName,
|
|
6299
6595
|
bindingId: bindingResolved,
|
|
6300
6596
|
count: 1,
|
|
6597
|
+
// Name-keyed entries from bare calls carry the #220(4)
|
|
6598
|
+
// kind rule into finalization (fix #300): the resolver
|
|
6599
|
+
// there must never select a method-kind def for them.
|
|
6600
|
+
...(bareKindFiltered && !bindingResolved && { bareNonMethod: true }),
|
|
6301
6601
|
...(call.isConstructor && { isConstructor: true }),
|
|
6302
6602
|
...(collectAccount && {
|
|
6303
6603
|
sites: [call.line],
|
|
@@ -6484,14 +6784,24 @@ function findCallees(index, definition, options = {}) {
|
|
|
6484
6784
|
const cFamilyVisibleFiles = ['c', 'cpp'].includes(language)
|
|
6485
6785
|
? _cppVisibleFiles(index, def.file) : null;
|
|
6486
6786
|
|
|
6487
|
-
for (const { name: calleeName, bindingId, count, isConstructor, sites, siteIds, isFunctionReference } of callees.values()) {
|
|
6787
|
+
for (const { name: calleeName, bindingId, count, isConstructor, sites, siteIds, isFunctionReference, bareNonMethod } of callees.values()) {
|
|
6488
6788
|
const claimSites = (bucket, reason) => {
|
|
6489
6789
|
if (!collectAccount || !siteIds) return;
|
|
6490
6790
|
for (let i = 0; i < siteIds.length; i++) {
|
|
6491
6791
|
noteSite(siteIds[i], bucket, reason, { name: calleeName, line: sites[i] });
|
|
6492
6792
|
}
|
|
6493
6793
|
};
|
|
6494
|
-
|
|
6794
|
+
let symbols = index.symbols.get(calleeName);
|
|
6795
|
+
if (symbols && symbols.length > 0 && bareNonMethod) {
|
|
6796
|
+
// fix #300 (#220(4), callee finalization): a bare call's
|
|
6797
|
+
// candidate set never includes METHOD defs — without this the
|
|
6798
|
+
// same-file priority selected the enclosing trait method for
|
|
6799
|
+
// `merge_join_by(self, other, cmp_fn)` inside its own trait
|
|
6800
|
+
// wrapper, fabricating a self-edge over the imported free fn.
|
|
6801
|
+
const nonMethod = symbols.filter(s =>
|
|
6802
|
+
!(s.className || s.receiver) || NON_CALLABLE_TYPES.has(s.type));
|
|
6803
|
+
if (nonMethod.length > 0) symbols = nonMethod;
|
|
6804
|
+
}
|
|
6495
6805
|
if (!symbols || symbols.length === 0) {
|
|
6496
6806
|
// Name not in the symbol table — external library, builtin, or
|
|
6497
6807
|
// unindexed code. Visible in the callee account, not an edge.
|
|
@@ -6921,6 +7231,22 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
6921
7231
|
};
|
|
6922
7232
|
for (const call of calls) {
|
|
6923
7233
|
if (!call.assignedTo) continue;
|
|
7234
|
+
// For-loop iteration provenance (fix #294, flask-measured: `for ep in
|
|
7235
|
+
// importlib.metadata.entry_points(...)` — ep.load() confirmed
|
|
7236
|
+
// JSONProvider.load via single-owner while ep is an external
|
|
7237
|
+
// EntryPoint). The loop variable holds an ELEMENT of the producer's
|
|
7238
|
+
// result — the return annotation types the container, never the
|
|
7239
|
+
// element, so assignedIter records NEVER type positively. External
|
|
7240
|
+
// module-qualified producers stamp external provenance (the
|
|
7241
|
+
// #220(6)/#222(4) demote-only rail: blocks single-owner confirmation,
|
|
7242
|
+
// never excludes). Bare-call and untyped producers add nothing —
|
|
7243
|
+
// sorted()/filter() wrappers routinely yield project values, and
|
|
7244
|
+
// demoting those has no measured evidence.
|
|
7245
|
+
if (call.assignedIter) {
|
|
7246
|
+
const via = _iterExternalProducerVia(index, fileEntry, call);
|
|
7247
|
+
if (via) routeUnknownAssignment(call, via);
|
|
7248
|
+
continue;
|
|
7249
|
+
}
|
|
6924
7250
|
const delegatedUnwrapAssignment = language === 'rust' &&
|
|
6925
7251
|
call.receiverCall && calls.some(candidate =>
|
|
6926
7252
|
candidate !== call &&
|
|
@@ -7017,7 +7343,13 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7017
7343
|
let returnType, fromFile, selfClass, returnedFunctionResult, returnDefinition;
|
|
7018
7344
|
const builtinCallReturn = !nominal && language === 'python'
|
|
7019
7345
|
? _pythonBuiltinCallReturnType(index, fileEntry, call) : null;
|
|
7020
|
-
if (
|
|
7346
|
+
if (call.localValueCall && call.returnTypeHint) {
|
|
7347
|
+
// Lexical function values are deliberately absent from the
|
|
7348
|
+
// project symbol table, but their declared result remains exact
|
|
7349
|
+
// local compiler evidence (Go: `f := func() (*A, *B)`).
|
|
7350
|
+
returnType = call.returnTypeHint;
|
|
7351
|
+
fromFile = filePath;
|
|
7352
|
+
} else if (builtinCallReturn) {
|
|
7021
7353
|
returnType = builtinCallReturn;
|
|
7022
7354
|
} else if (!nominal && language === 'python' && !call.isMethod &&
|
|
7023
7355
|
!call.receiver && call.name === 'open' && !call.localShadow &&
|
|
@@ -7486,11 +7818,63 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7486
7818
|
});
|
|
7487
7819
|
continue;
|
|
7488
7820
|
}
|
|
7821
|
+
// Go multi-return assignments have one compiler-declared type per
|
|
7822
|
+
// LHS position. Record every named target, including assignments
|
|
7823
|
+
// whose first position is `_`; treating only element zero left later
|
|
7824
|
+
// receivers permanently ambiguous (`route, router := split(...)`).
|
|
7825
|
+
if (language === 'go' && call.assignedTuple &&
|
|
7826
|
+
Array.isArray(call.assignedTupleTargets) &&
|
|
7827
|
+
call.assignedTupleTargets.length > 0) {
|
|
7828
|
+
const scope = call.enclosingFunction
|
|
7829
|
+
? `${call.enclosingFunction.startLine}` : '';
|
|
7830
|
+
if (!map) map = new Map();
|
|
7831
|
+
for (const target of call.assignedTupleTargets) {
|
|
7832
|
+
const key = `${scope}:${target.name}`;
|
|
7833
|
+
if (!map.has(key)) map.set(key, []);
|
|
7834
|
+
const base = { line: call.line, start: call.callStart };
|
|
7835
|
+
const parsed = _returnTypeNameNominal(returnType, language, {
|
|
7836
|
+
tuple: true,
|
|
7837
|
+
tupleIndex: target.index,
|
|
7838
|
+
selfClass,
|
|
7839
|
+
index,
|
|
7840
|
+
originFile: fromFile || filePath,
|
|
7841
|
+
});
|
|
7842
|
+
if (!parsed) {
|
|
7843
|
+
const uncertainVia = _rejectedNominalFlowVia(
|
|
7844
|
+
returnType, language, {
|
|
7845
|
+
tuple: true,
|
|
7846
|
+
tupleIndex: target.index,
|
|
7847
|
+
});
|
|
7848
|
+
map.get(key).push(uncertainVia
|
|
7849
|
+
? { ...base, externalVia: uncertainVia }
|
|
7850
|
+
: { ...base, invalidated: true });
|
|
7851
|
+
continue;
|
|
7852
|
+
}
|
|
7853
|
+
if (parsed.qualifier) {
|
|
7854
|
+
const producerEntry =
|
|
7855
|
+
index.files.get(fromFile || filePath) || fileEntry;
|
|
7856
|
+
const qualified = _goQualifiedReceiverType(
|
|
7857
|
+
index, producerEntry, parsed.qualifier, parsed.name);
|
|
7858
|
+
if (qualified && qualified.kind !== 'project') {
|
|
7859
|
+
map.get(key).push({ ...base, externalVia: qualified.via });
|
|
7860
|
+
continue;
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
const origin = _resolveFlowTypeOrigin(
|
|
7864
|
+
index, fromFile || filePath, parsed.name, parsed.qualifier);
|
|
7865
|
+
map.get(key).push(origin?.fromFile
|
|
7866
|
+
? { ...base, type: parsed.name, fromFile: origin.fromFile }
|
|
7867
|
+
: { ...base, invalidated: true });
|
|
7868
|
+
}
|
|
7869
|
+
continue;
|
|
7870
|
+
}
|
|
7871
|
+
|
|
7489
7872
|
let typeName, entryFromFile;
|
|
7490
7873
|
if (nominal) {
|
|
7491
7874
|
const parsed = _returnTypeNameNominal(returnType, language, {
|
|
7492
7875
|
unwrapped: call.assignedUnwrap,
|
|
7493
7876
|
tuple: call.assignedTuple,
|
|
7877
|
+
tupleIndex: call.assignedTupleIndex,
|
|
7494
7878
|
selfClass,
|
|
7495
7879
|
index,
|
|
7496
7880
|
originFile: fromFile || filePath,
|
|
@@ -7804,8 +8188,11 @@ function _returnTypeNameNominal(text, language, opts = {}) {
|
|
|
7804
8188
|
if (!opts.tuple) return undefined;
|
|
7805
8189
|
const inner = t.slice(1, -1);
|
|
7806
8190
|
if (inner.includes('func(') || inner.includes('func (')) return undefined;
|
|
7807
|
-
const
|
|
7808
|
-
|
|
8191
|
+
const position = Number.isInteger(opts.tupleIndex)
|
|
8192
|
+
? opts.tupleIndex : 0;
|
|
8193
|
+
const item = _splitTopLevelGenericArgs(inner)[position]?.trim();
|
|
8194
|
+
if (!item) return undefined;
|
|
8195
|
+
const parts = item.split(/\s+/);
|
|
7809
8196
|
t = parts[parts.length - 1]; // named return `n int` → int
|
|
7810
8197
|
} else if (opts.tuple) {
|
|
7811
8198
|
return undefined; // v, err := f() needs a multi-return producer
|
|
@@ -7896,8 +8283,10 @@ function _rejectedNominalFlowVia(text, language, opts = {}) {
|
|
|
7896
8283
|
let raw = text.trim();
|
|
7897
8284
|
if (language === 'go' && raw.startsWith('(')) {
|
|
7898
8285
|
if (!opts.tuple) return null;
|
|
7899
|
-
const
|
|
7900
|
-
|
|
8286
|
+
const position = Number.isInteger(opts.tupleIndex)
|
|
8287
|
+
? opts.tupleIndex : 0;
|
|
8288
|
+
const item = _splitTopLevelGenericArgs(raw.slice(1, -1))[position]?.trim();
|
|
8289
|
+
raw = item ? item.split(/\s+/).pop() : '';
|
|
7901
8290
|
}
|
|
7902
8291
|
const norm = _normalizeFieldTypeName(raw, language);
|
|
7903
8292
|
if (!norm) return null;
|
|
@@ -7948,6 +8337,51 @@ function _rustBindingResolvedFiles(index, fileEntry, filePath, binding) {
|
|
|
7948
8337
|
return resolvedFiles;
|
|
7949
8338
|
}
|
|
7950
8339
|
|
|
8340
|
+
/**
|
|
8341
|
+
* Resolve a Rust `use path::Type as LocalType` alias to the indexed type it
|
|
8342
|
+
* names. Import aliases are value/type names, not Rust `type` declarations,
|
|
8343
|
+
* so they do not appear in the symbol table and cannot use `_pureAliasBase`.
|
|
8344
|
+
* Require every same-local-name binding to resolve to one identical type/file;
|
|
8345
|
+
* a resolver gap or scope collision remains unknown rather than guessed.
|
|
8346
|
+
*/
|
|
8347
|
+
function _rustImportedTypeIdentity(index, filePath, localName) {
|
|
8348
|
+
const fileEntry = index.files.get(filePath);
|
|
8349
|
+
if (fileEntry?.language !== 'rust') return null;
|
|
8350
|
+
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
8351
|
+
(binding.name === localName || binding.alias === localName) &&
|
|
8352
|
+
String(binding.module || '').includes('::'));
|
|
8353
|
+
if (bindings.length === 0) return null;
|
|
8354
|
+
const identities = [];
|
|
8355
|
+
for (const binding of bindings) {
|
|
8356
|
+
const parts = String(binding.module).split('::').filter(Boolean);
|
|
8357
|
+
const original = parts[parts.length - 1];
|
|
8358
|
+
if (!original || original === localName) return null;
|
|
8359
|
+
const definitions = (index.symbols.get(original) || []).filter(definition =>
|
|
8360
|
+
IDENTITY_TYPE_KINDS.has(definition.type) && definition.file);
|
|
8361
|
+
if (definitions.length === 0) return null;
|
|
8362
|
+
const starts = _rustBindingResolvedFiles(
|
|
8363
|
+
index, fileEntry, filePath, binding);
|
|
8364
|
+
if (starts.size === 0) return null;
|
|
8365
|
+
let reachable = definitions.filter(definition => [...starts].some(start =>
|
|
8366
|
+
definition.file === start ||
|
|
8367
|
+
_nameBindingReaches(
|
|
8368
|
+
index, start, original, new Set([definition.file])) === 'yes'));
|
|
8369
|
+
if (reachable.length === 0) {
|
|
8370
|
+
reachable = definitions.filter(definition => [...starts].some(start =>
|
|
8371
|
+
definition.file === start ||
|
|
8372
|
+
_importReaches(index, start, new Set([definition.file]))));
|
|
8373
|
+
}
|
|
8374
|
+
const files = new Set(reachable.map(definition => definition.file));
|
|
8375
|
+
if (files.size !== 1) return null;
|
|
8376
|
+
identities.push({ type: original, fromFile: [...files][0] });
|
|
8377
|
+
}
|
|
8378
|
+
if (new Set(identities.map(identity => identity.type)).size !== 1 ||
|
|
8379
|
+
new Set(identities.map(identity => identity.fromFile)).size !== 1) {
|
|
8380
|
+
return null;
|
|
8381
|
+
}
|
|
8382
|
+
return identities[0];
|
|
8383
|
+
}
|
|
8384
|
+
|
|
7951
8385
|
/**
|
|
7952
8386
|
* Pin a flow type name to its defining file from the PRODUCER's scope
|
|
7953
8387
|
* (fix #207 — the #206 identity lesson applied to annotations: `Builder` in
|
|
@@ -8785,6 +9219,58 @@ function _submoduleReceiverModule(index, fileEntry, receiverName) {
|
|
|
8785
9219
|
return null;
|
|
8786
9220
|
}
|
|
8787
9221
|
|
|
9222
|
+
/**
|
|
9223
|
+
* Does a module-qualified member reference (`pkg.name`) reach the pinned
|
|
9224
|
+
* definition files? This is the value-position counterpart of structural
|
|
9225
|
+
* module-call routing. It deliberately accepts only parser-proven module
|
|
9226
|
+
* imports (or Python from-imports resolved as actual submodules), so an
|
|
9227
|
+
* imported class/value named `pkg` cannot borrow a module's exports.
|
|
9228
|
+
*
|
|
9229
|
+
* Returns `yes`, `no`, `unknown`, or null when the receiver is not a modeled
|
|
9230
|
+
* module binding.
|
|
9231
|
+
*/
|
|
9232
|
+
function _moduleAttributeBindingReaches(index, filePath, receiverName, name,
|
|
9233
|
+
targetFiles, maxDepth = 8) {
|
|
9234
|
+
const fileEntry = index.files.get(filePath);
|
|
9235
|
+
if (!fileEntry || !receiverName || !name ||
|
|
9236
|
+
langTraits(fileEntry.language)?.typeSystem !== 'structural') return null;
|
|
9237
|
+
|
|
9238
|
+
const starts = new Set();
|
|
9239
|
+
let matchedBinding = false;
|
|
9240
|
+
let unknown = false;
|
|
9241
|
+
const submoduleRel = _submoduleReceiverModule(index, fileEntry, receiverName);
|
|
9242
|
+
if (submoduleRel) {
|
|
9243
|
+
matchedBinding = true;
|
|
9244
|
+
starts.add(path.isAbsolute(submoduleRel)
|
|
9245
|
+
? submoduleRel : path.join(index.root, submoduleRel));
|
|
9246
|
+
}
|
|
9247
|
+
|
|
9248
|
+
for (const binding of (fileEntry.importBindings || [])) {
|
|
9249
|
+
if (binding.name !== receiverName && binding.alias !== receiverName) continue;
|
|
9250
|
+
// Python `import pkg [as p]` is a module binding. A `from pkg import
|
|
9251
|
+
// Value` binding is not, unless _submoduleReceiverModule proved that
|
|
9252
|
+
// Value is itself a project submodule above.
|
|
9253
|
+
if (fileEntry.language === 'python' && binding.kind !== 'import') continue;
|
|
9254
|
+
matchedBinding = true;
|
|
9255
|
+
const rel = fileEntry.moduleResolved?.[binding.module];
|
|
9256
|
+
if (!rel) {
|
|
9257
|
+
unknown = true;
|
|
9258
|
+
continue;
|
|
9259
|
+
}
|
|
9260
|
+
starts.add(path.isAbsolute(rel) ? rel : path.join(index.root, rel));
|
|
9261
|
+
}
|
|
9262
|
+
|
|
9263
|
+
if (!matchedBinding) return null;
|
|
9264
|
+
for (const start of starts) {
|
|
9265
|
+
const verdict = _nameBindingReaches(
|
|
9266
|
+
index, start, name, targetFiles, maxDepth);
|
|
9267
|
+
if (verdict === 'yes') return 'yes';
|
|
9268
|
+
if (verdict === 'unknown') unknown = true;
|
|
9269
|
+
}
|
|
9270
|
+
if (unknown || starts.size === 0) return 'unknown';
|
|
9271
|
+
return 'no';
|
|
9272
|
+
}
|
|
9273
|
+
|
|
8788
9274
|
/**
|
|
8789
9275
|
* Bounded-depth reachability over the import graph: can `fromAbs` reach any
|
|
8790
9276
|
* target file through re-export/import chains? Barrel hierarchies routinely
|
|
@@ -8912,6 +9398,30 @@ function _isEnclosingGenericParam(index, filePath, line, typeName) {
|
|
|
8912
9398
|
return false;
|
|
8913
9399
|
}
|
|
8914
9400
|
|
|
9401
|
+
/**
|
|
9402
|
+
* Generic receivers normally have runtime-unknown ownership. For a trait
|
|
9403
|
+
* declaration pin, an explicit `F: Float` bound is nevertheless exact
|
|
9404
|
+
* compiler evidence that `f.exponent()` invokes Float's method slot (not any
|
|
9405
|
+
* one concrete implementation). Resolve the bound to the pinned trait file;
|
|
9406
|
+
* a same-named external or sibling trait remains unknown.
|
|
9407
|
+
*/
|
|
9408
|
+
function _genericParamTraitTarget(index, filePath, line, typeName, targetDefs) {
|
|
9409
|
+
const enclosing = index.findEnclosingFunction(filePath, line, true);
|
|
9410
|
+
const bounds = enclosing?.genericBounds?.[typeName];
|
|
9411
|
+
if (!Array.isArray(bounds) || bounds.length === 0) return null;
|
|
9412
|
+
for (const target of targetDefs) {
|
|
9413
|
+
const owner = target.className ||
|
|
9414
|
+
(target.receiver || '').replace(/^[*&]\s*/, '');
|
|
9415
|
+
if (!owner || !bounds.includes(owner)) continue;
|
|
9416
|
+
const ownsTraitDeclaration = (index.symbols.get(owner) || []).some(definition =>
|
|
9417
|
+
definition.type === 'trait' && definition.file === target.file);
|
|
9418
|
+
if (!ownsTraitDeclaration) continue;
|
|
9419
|
+
const origin = _resolveFlowTypeOrigin(index, filePath, owner);
|
|
9420
|
+
if (origin?.fromFile === target.file) return owner;
|
|
9421
|
+
}
|
|
9422
|
+
return null;
|
|
9423
|
+
}
|
|
9424
|
+
|
|
8915
9425
|
/**
|
|
8916
9426
|
* Receiver-type identity guard shared by the parser-typed branch and the
|
|
8917
9427
|
* local-inference fallback: a name is NOT usable as type identity when it is
|
|
@@ -9108,7 +9618,29 @@ function _resolveReceiverTypeIdentity(index, filePath, knownType, targetDefs, li
|
|
|
9108
9618
|
* CustomCommand extends click.Command), while parallel package versions may
|
|
9109
9619
|
* reuse every class name (zod v3/v4 ZodArray -> ZodType).
|
|
9110
9620
|
*/
|
|
9111
|
-
|
|
9621
|
+
/**
|
|
9622
|
+
* Scope-correct same-file type-def selection (fix #300, attrs-measured):
|
|
9623
|
+
* when a type name has SEVERAL class-kind defs in one file (each test
|
|
9624
|
+
* function defining its own local `class C2Slots(...)`), the def the call
|
|
9625
|
+
* site actually sees is the one declared inside the call's own enclosing
|
|
9626
|
+
* function before the call line — Python/JS lexical scoping. Returns that
|
|
9627
|
+
* def, or null when the shape doesn't apply (single def, no scope info, no
|
|
9628
|
+
* local declaration in range) — null means "keep existing behavior".
|
|
9629
|
+
*/
|
|
9630
|
+
function _scopedSameFileTypeDef(index, name, file, site) {
|
|
9631
|
+
if (!site || site.line == null ||
|
|
9632
|
+
site.scopeStart == null || site.scopeEnd == null) return null;
|
|
9633
|
+
const defs = (index.symbols.get(name) || []).filter(d =>
|
|
9634
|
+
IDENTITY_TYPE_KINDS.has(d.type) && d.file === file);
|
|
9635
|
+
if (defs.length <= 1) return null;
|
|
9636
|
+
const scoped = defs.filter(d =>
|
|
9637
|
+
d.startLine >= site.scopeStart && d.startLine <= site.scopeEnd &&
|
|
9638
|
+
d.startLine <= site.line);
|
|
9639
|
+
if (scoped.length === 0) return null;
|
|
9640
|
+
return scoped.reduce((a, b) => (b.startLine > a.startLine ? b : a));
|
|
9641
|
+
}
|
|
9642
|
+
|
|
9643
|
+
function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, targetDefs, qualifier, site) {
|
|
9112
9644
|
const origin = _resolveFlowTypeOrigin(index, originFile, knownType, qualifier);
|
|
9113
9645
|
if (!origin?.fromFile) return 'unknown';
|
|
9114
9646
|
const targetOwners = new Set(targetDefs
|
|
@@ -9181,7 +9713,16 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
9181
9713
|
return { name: parent, file: parentFile };
|
|
9182
9714
|
};
|
|
9183
9715
|
|
|
9184
|
-
|
|
9716
|
+
// Scope-correct first hop (fix #300): when the receiver's type name has
|
|
9717
|
+
// several same-file class defs, seed the walk with the def the call site
|
|
9718
|
+
// lexically sees — its per-def extends entry, never a sibling def's.
|
|
9719
|
+
// Guard: the site's line ranges describe the CALL file — they only
|
|
9720
|
+
// select defs when the type actually resolves there (an imported type's
|
|
9721
|
+
// defining file has unrelated line geometry).
|
|
9722
|
+
const scopedFirst = (site && site.file === origin.fromFile)
|
|
9723
|
+
? _scopedSameFileTypeDef(index, knownType, origin.fromFile, site) : null;
|
|
9724
|
+
const queue = [{ name: knownType, file: origin.fromFile,
|
|
9725
|
+
defStartLine: scopedFirst?.startLine }];
|
|
9185
9726
|
const visited = new Set();
|
|
9186
9727
|
let sawUnresolved = direct === 'unknown';
|
|
9187
9728
|
while (queue.length > 0 && visited.size < 128) {
|
|
@@ -9193,7 +9734,9 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
9193
9734
|
// ancestor slot. In that case ancestry is evidence for the other
|
|
9194
9735
|
// definition, not the pinned one.
|
|
9195
9736
|
if (overridesPinnedSlot(cur.name, cur.file)) continue;
|
|
9196
|
-
const parents =
|
|
9737
|
+
const parents = cur.defStartLine != null
|
|
9738
|
+
? (index._getInheritanceParentsAt?.(cur.name, cur.file, cur.defStartLine) || [])
|
|
9739
|
+
: (index._getInheritanceParents(cur.name, cur.file) || []);
|
|
9197
9740
|
for (const parent of parents) {
|
|
9198
9741
|
const edge = parentOrigin(parent, cur.file);
|
|
9199
9742
|
const verdict = ownerIdentity(edge.name, edge.file);
|
|
@@ -9206,6 +9749,50 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
9206
9749
|
return sawUnresolved ? 'unknown' : 'other';
|
|
9207
9750
|
}
|
|
9208
9751
|
|
|
9752
|
+
/**
|
|
9753
|
+
* Trait-declaration pin gate (fix #296, serde-as_cast-measured): when the
|
|
9754
|
+
* pinned target is a TRAIT's own method declaration, a path call on a
|
|
9755
|
+
* concrete NON-target receiver (`u64::as_cast`, `Limb::as_cast`,
|
|
9756
|
+
* `Self::Unsigned::as_cast`) is not evidence against the edge — any type
|
|
9757
|
+
* (external primitives, macro-generated impls UCN cannot index, aliases)
|
|
9758
|
+
* can implement a project trait, and the call dispatches through the pinned
|
|
9759
|
+
* slot. Returns the trait's name when the gate applies: the caller routes
|
|
9760
|
+
* possible-dispatch "via <Recv> — trait implementor" instead of excluding
|
|
9761
|
+
* path-type-mismatch. Two refusals keep the exclusion sound elsewhere:
|
|
9762
|
+
* impl-member pins (a foreign-type path call binds a DIFFERENT slot member
|
|
9763
|
+
* — static dispatch, exclusion correct), and receivers resolving to a
|
|
9764
|
+
* project type whose only same-name members provably belong to another
|
|
9765
|
+
* surface (inherent members / other traits — Rust resolves inherent first).
|
|
9766
|
+
*/
|
|
9767
|
+
function _traitDeclPinImplementorRoute(index, fileEntry, receiverSegment, targetDefs) {
|
|
9768
|
+
if (langTraits(fileEntry.language)?.typeQualifiedCallStyle !== 'path') return null;
|
|
9769
|
+
let traitName = null;
|
|
9770
|
+
for (const td of targetDefs || []) {
|
|
9771
|
+
if (!td.className || td.traitName) continue;
|
|
9772
|
+
const classDefs = index.symbols.get(td.className) || [];
|
|
9773
|
+
if (classDefs.some(d => d.type === 'trait' && d.file === td.file)) {
|
|
9774
|
+
traitName = td.className;
|
|
9775
|
+
break;
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
if (!traitName) return null;
|
|
9779
|
+
// Receiver resolving to a project type: its indexed same-name members
|
|
9780
|
+
// decide. An inherent member or a different trait's impl owns the call
|
|
9781
|
+
// (keep the exclusion); a member implementing THIS trait — or no indexed
|
|
9782
|
+
// member at all (macro-generated impls are invisible) — routes visible.
|
|
9783
|
+
const recvTypeDefs = (index.symbols.get(receiverSegment) || [])
|
|
9784
|
+
.filter(d => IDENTITY_TYPE_KINDS.has(d.type));
|
|
9785
|
+
if (recvTypeDefs.length > 0) {
|
|
9786
|
+
const targetName = targetDefs[0] && targetDefs[0].name;
|
|
9787
|
+
const members = (index.symbols.get(targetName) || [])
|
|
9788
|
+
.filter(d => d.className === receiverSegment);
|
|
9789
|
+
if (members.length > 0 && members.every(m => m.traitName !== traitName)) {
|
|
9790
|
+
return null;
|
|
9791
|
+
}
|
|
9792
|
+
}
|
|
9793
|
+
return traitName;
|
|
9794
|
+
}
|
|
9795
|
+
|
|
9209
9796
|
/**
|
|
9210
9797
|
* Is typeName an ancestor (transitively) of any target definition's class?
|
|
9211
9798
|
* Used by receiver-class disambiguation: a receiver typed as a SUPERTYPE of
|
|
@@ -9437,6 +10024,38 @@ function _closeCallableIdentityGroup(index, targetDefs, definitions) {
|
|
|
9437
10024
|
expanded.push(candidate);
|
|
9438
10025
|
}
|
|
9439
10026
|
}
|
|
10027
|
+
|
|
10028
|
+
// C++ full specializations (`template <> bool f<bool>(...)`) are the SAME
|
|
10029
|
+
// compiler symbol as their primary template: name lookup finds the
|
|
10030
|
+
// template, and the specialization is selected by substitution, never by
|
|
10031
|
+
// overload resolution (fix #299). Closure requires exactly ONE primary
|
|
10032
|
+
// template with the owner identity — with several primaries the compiler
|
|
10033
|
+
// matches the specialization by signature substitution, which is not
|
|
10034
|
+
// grep-reliability evidence, so the group refuses and sites stay visible.
|
|
10035
|
+
for (const target of targetDefs) {
|
|
10036
|
+
if (cFamilyLanguage(target) !== 'cpp') continue;
|
|
10037
|
+
if (!target.isSpecialization && !target.templateDependent) continue;
|
|
10038
|
+
const related = definitions.filter(d =>
|
|
10039
|
+
!NON_CALLABLE_TYPES.has(d.type) &&
|
|
10040
|
+
cFamilyLanguage(d) === 'cpp' && sameOwner(target, d) &&
|
|
10041
|
+
(d.isSpecialization || (d.templateDependent && !d.isSignature)));
|
|
10042
|
+
const primaries = related.filter(d => !d.isSpecialization);
|
|
10043
|
+
const specializations = related.filter(d => d.isSpecialization);
|
|
10044
|
+
if (primaries.length !== 1 || specializations.length === 0) continue;
|
|
10045
|
+
if (!related.some(d => d === target ||
|
|
10046
|
+
(d.file === target.file && d.startLine === target.startLine))) {
|
|
10047
|
+
continue;
|
|
10048
|
+
}
|
|
10049
|
+
const primary = primaries[0];
|
|
10050
|
+
for (const candidate of related) {
|
|
10051
|
+
if (candidate !== primary &&
|
|
10052
|
+
!includesDeclaration(candidate, primary)) continue;
|
|
10053
|
+
if (targetDefs.includes(candidate) ||
|
|
10054
|
+
(expanded && expanded.includes(candidate))) continue;
|
|
10055
|
+
if (!expanded) expanded = [...targetDefs];
|
|
10056
|
+
expanded.push(candidate);
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
9440
10059
|
return expanded || targetDefs;
|
|
9441
10060
|
}
|
|
9442
10061
|
|
|
@@ -9578,6 +10197,40 @@ function _goQualifierNamesImport(index, fieldFile, qualifier) {
|
|
|
9578
10197
|
// wins so import "k8s.io/client-go/kubernetes/scheme" prefers a def in
|
|
9579
10198
|
// .../kubernetes/scheme/ over .../kubeadm/scheme/). Extracted for reuse:
|
|
9580
10199
|
// the parser marks some package calls isMethod:false (fix #268).
|
|
10200
|
+
/**
|
|
10201
|
+
* External-producer attribution for a for-loop iterable call (fix #294).
|
|
10202
|
+
* Only module-qualified producers count: the module boundary is the
|
|
10203
|
+
* externality evidence (`importlib.metadata.entry_points(...)`,
|
|
10204
|
+
* `os.walk(...)`). Same externality test as #209/#222 — relative or
|
|
10205
|
+
* project-ish modules are resolver gaps, never externality evidence.
|
|
10206
|
+
* Returns the attribution string or null.
|
|
10207
|
+
*/
|
|
10208
|
+
function _iterExternalProducerVia(index, fileEntry, call) {
|
|
10209
|
+
if (!fileEntry || langTraits(fileEntry.language)?.typeSystem === 'nominal') return null;
|
|
10210
|
+
const externalModule = (mod) => {
|
|
10211
|
+
if (!mod || mod.startsWith('.')) return false;
|
|
10212
|
+
if (fileEntry.moduleResolved?.[mod]) return false;
|
|
10213
|
+
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
10214
|
+
return !(firstSeg && _projectTopLevelNames(index).has(firstSeg));
|
|
10215
|
+
};
|
|
10216
|
+
if (call.isMethod && call.receiverIsModule && call.receiver) {
|
|
10217
|
+
const binding = _structuralModuleBindings(fileEntry, call)[0];
|
|
10218
|
+
if (binding && externalModule(String(binding.module))) {
|
|
10219
|
+
return `${call.receiver}.${call.name}`;
|
|
10220
|
+
}
|
|
10221
|
+
return null;
|
|
10222
|
+
}
|
|
10223
|
+
if (call.receiverRoot && !call.receiverRootType) {
|
|
10224
|
+
const binding = (fileEntry.importBindings || []).find(b =>
|
|
10225
|
+
b && b.name === call.receiverRoot && b.kind === 'import');
|
|
10226
|
+
if (binding && externalModule(String(binding.module))) {
|
|
10227
|
+
const field = call.receiverField ? `.${call.receiverField}` : '';
|
|
10228
|
+
return `${call.receiverRoot}${field}.${call.name}`;
|
|
10229
|
+
}
|
|
10230
|
+
}
|
|
10231
|
+
return null;
|
|
10232
|
+
}
|
|
10233
|
+
|
|
9581
10234
|
function _structuralModuleBindings(fileEntry, call) {
|
|
9582
10235
|
if (call?.receiverModuleSpecifier) {
|
|
9583
10236
|
return [{
|
|
@@ -9904,7 +10557,8 @@ function _calleeOverloadSelect(index, call, matches, language) {
|
|
|
9904
10557
|
applicable = _preferFixedArityOverloads(
|
|
9905
10558
|
index, call, applicable, language);
|
|
9906
10559
|
if (applicable.length > 1) {
|
|
9907
|
-
const mostSpecific = _javaMostSpecificOverload(
|
|
10560
|
+
const mostSpecific = _javaMostSpecificOverload(
|
|
10561
|
+
index, applicable, call, language);
|
|
9908
10562
|
if (mostSpecific) return { match: mostSpecific };
|
|
9909
10563
|
}
|
|
9910
10564
|
}
|
|
@@ -10145,7 +10799,10 @@ function _declaredFieldType(index, rootType, fieldName, language, info, rootName
|
|
|
10145
10799
|
return null;
|
|
10146
10800
|
}
|
|
10147
10801
|
}
|
|
10148
|
-
const
|
|
10802
|
+
const localType = _normalizeFieldTypeName(rawText, language);
|
|
10803
|
+
const importedIdentity = language === 'rust' && f.file && localType
|
|
10804
|
+
? _rustImportedTypeIdentity(index, f.file, localType) : null;
|
|
10805
|
+
const t = importedIdentity?.type || localType;
|
|
10149
10806
|
if (t) normalized.add(t);
|
|
10150
10807
|
else return null; // any un-normalizable declaration → no evidence
|
|
10151
10808
|
}
|
|
@@ -10171,8 +10828,13 @@ function _declaredFieldType(index, rootType, fieldName, language, info, rootName
|
|
|
10171
10828
|
const qualifier = language === 'java'
|
|
10172
10829
|
? _javaNestedTypeQualifier(rawText) : undefined;
|
|
10173
10830
|
if (qualifier) namespaces.add(qualifier);
|
|
10174
|
-
const
|
|
10175
|
-
|
|
10831
|
+
const localType = _normalizeFieldTypeName(rawText, language);
|
|
10832
|
+
const importedIdentity = language === 'rust' && localType
|
|
10833
|
+
? _rustImportedTypeIdentity(index, field.file, localType) : null;
|
|
10834
|
+
const origin = importedIdentity?.type === typeName
|
|
10835
|
+
? importedIdentity
|
|
10836
|
+
: _resolveFlowTypeOrigin(
|
|
10837
|
+
index, field.file, typeName, qualifier);
|
|
10176
10838
|
if (!origin?.fromFile) {
|
|
10177
10839
|
complete = false;
|
|
10178
10840
|
break;
|
|
@@ -10381,7 +11043,14 @@ function _namespaceContainedDef(index, fileEntry, callFileAbs, receiverName, cal
|
|
|
10381
11043
|
if (nsDefs.length === 0) return null;
|
|
10382
11044
|
const candidates = restrictDefs ||
|
|
10383
11045
|
(index.symbols.get(calleeName) || []).filter(s => !NON_CALLABLE_TYPES.has(s.type));
|
|
11046
|
+
// Self-containment guard (fix #300, itertools-measured): when receiver
|
|
11047
|
+
// and callee share one name (`powerset::powerset`), the candidate list IS
|
|
11048
|
+
// the nsDefs list — a body-less `mod powerset;` declaration (range 1..1)
|
|
11049
|
+
// "contains" itself and was returned as the "other definition", excluding
|
|
11050
|
+
// the compiler-true module-qualified call. A container is never its own
|
|
11051
|
+
// contained member.
|
|
10384
11052
|
const contained = candidates.filter(d => nsDefs.some(ns =>
|
|
11053
|
+
ns !== d &&
|
|
10385
11054
|
ns.file === d.file && ns.startLine <= d.startLine &&
|
|
10386
11055
|
(ns.endLine ?? Infinity) >= (d.endLine ?? d.startLine)));
|
|
10387
11056
|
if (contained.length === 0) return null;
|
|
@@ -10596,9 +11265,28 @@ function _calleeLanguageCompatible(index, def, callerLanguage) {
|
|
|
10596
11265
|
* receiver typed Child legitimately reaches methods defined on Base (the
|
|
10597
11266
|
* #198 ancestor rule, callee direction). Includes the type itself.
|
|
10598
11267
|
*/
|
|
10599
|
-
function _receiverTypeAncestors(index, typeName, maxHops = 6) {
|
|
11268
|
+
function _receiverTypeAncestors(index, typeName, maxHops = 6, scopeRef = null) {
|
|
10600
11269
|
const seen = new Set([typeName]);
|
|
10601
11270
|
let frontier = [typeName];
|
|
11271
|
+
// Scope-correct FIRST hop (fix #300, attrs callee arm): with several
|
|
11272
|
+
// same-file same-name class defs (function-local test subclasses), the
|
|
11273
|
+
// name-granular parent lookup returns the first def's parents for all of
|
|
11274
|
+
// them — the callee walk then lands on the WRONG base's method. Seed
|
|
11275
|
+
// hop 0 from the def the call site lexically sees.
|
|
11276
|
+
if (scopeRef?.file) {
|
|
11277
|
+
const scoped = _scopedSameFileTypeDef(index, typeName, scopeRef.file, scopeRef);
|
|
11278
|
+
if (scoped) {
|
|
11279
|
+
const parents = index._getInheritanceParentsAt?.(
|
|
11280
|
+
typeName, scopeRef.file, scoped.startLine) || [];
|
|
11281
|
+
const next = [];
|
|
11282
|
+
for (const p of parents) {
|
|
11283
|
+
const pName = typeof p === 'string' ? p : p?.name;
|
|
11284
|
+
if (pName && !seen.has(pName)) { seen.add(pName); next.push(pName); }
|
|
11285
|
+
}
|
|
11286
|
+
frontier = next;
|
|
11287
|
+
maxHops -= 1;
|
|
11288
|
+
}
|
|
11289
|
+
}
|
|
10602
11290
|
for (let hop = 0; hop < maxHops && frontier.length; hop++) {
|
|
10603
11291
|
const next = [];
|
|
10604
11292
|
for (const cls of frontier) {
|
|
@@ -10624,7 +11312,7 @@ function _receiverTypeAncestors(index, typeName, maxHops = 6) {
|
|
|
10624
11312
|
* never confirmed by bare-name resolution
|
|
10625
11313
|
* null — receiver type unknown; existing heuristics decide
|
|
10626
11314
|
*/
|
|
10627
|
-
function _calleeReceiverTypeRoute(index, call, localTypes, language) {
|
|
11315
|
+
function _calleeReceiverTypeRoute(index, call, localTypes, language, scopeRef = null) {
|
|
10628
11316
|
const raw = call.receiverType || localTypes?.get(call.receiver);
|
|
10629
11317
|
if (!raw || typeof raw !== 'string') return null;
|
|
10630
11318
|
const head = _structuralTypeHead(raw, { index, language }) || raw;
|
|
@@ -10634,7 +11322,7 @@ function _calleeReceiverTypeRoute(index, call, localTypes, language) {
|
|
|
10634
11322
|
_calleeLanguageCompatible(index, d, language));
|
|
10635
11323
|
let matches = defs.filter(d => d.className === head || d.className === norm);
|
|
10636
11324
|
if (matches.length === 0 && defs.length > 0) {
|
|
10637
|
-
const ancestors = _receiverTypeAncestors(index, head);
|
|
11325
|
+
const ancestors = _receiverTypeAncestors(index, head, 6, scopeRef);
|
|
10638
11326
|
matches = defs.filter(d => ancestors.has(d.className));
|
|
10639
11327
|
}
|
|
10640
11328
|
if (matches.length === 1) return { resolve: matches[0] };
|
|
@@ -10710,7 +11398,12 @@ function _calleeSingleOwnerMatch(index, def, fileEntry, call, name, language, fl
|
|
|
10710
11398
|
const head = _structuralTypeHead(call.receiverType, { index, language }) || call.receiverType;
|
|
10711
11399
|
const norm = head;
|
|
10712
11400
|
if (head !== owner && norm !== owner &&
|
|
10713
|
-
!_receiverTypeAncestors(index, head
|
|
11401
|
+
!_receiverTypeAncestors(index, head, 6, {
|
|
11402
|
+
file: def.file,
|
|
11403
|
+
line: call.line,
|
|
11404
|
+
scopeStart: def.startLine,
|
|
11405
|
+
scopeEnd: def.endLine,
|
|
11406
|
+
}).has(owner)) return null;
|
|
10714
11407
|
}
|
|
10715
11408
|
if (traits?.typeSystem === 'nominal' && call.argCount != null &&
|
|
10716
11409
|
!_callArityCompatible(call, ownerDefs, language)) return null;
|
|
@@ -10925,6 +11618,28 @@ const JAVA_FINAL_REFERENCE_TYPES = new Set([
|
|
|
10925
11618
|
'Integer', 'Long', 'Float', 'Double', 'Void',
|
|
10926
11619
|
]);
|
|
10927
11620
|
|
|
11621
|
+
// Complete reference-supertype sets for JDK value types (fix #299D): these
|
|
11622
|
+
// types are final (or effectively closed) with a fixed, fully-known
|
|
11623
|
+
// ancestry, so an argument statically typed as one of them can bind ONLY a
|
|
11624
|
+
// parameter type in this list (plus Object/generics, handled earlier). A
|
|
11625
|
+
// project class shadowing one of these simple names disables the denial —
|
|
11626
|
+
// the guard is tDefs.length === 0 at the use site. Modern-JDK marker
|
|
11627
|
+
// interfaces (Constable, ConstantDesc) are included so their params never
|
|
11628
|
+
// get falsely denied.
|
|
11629
|
+
const JAVA_PLATFORM_VALUE_ANCESTRY = new Map([
|
|
11630
|
+
['Integer', ['Number', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11631
|
+
['Long', ['Number', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11632
|
+
['Short', ['Number', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11633
|
+
['Byte', ['Number', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11634
|
+
['Float', ['Number', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11635
|
+
['Double', ['Number', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11636
|
+
['Character', ['Comparable', 'Serializable', 'Constable']],
|
|
11637
|
+
['Boolean', ['Comparable', 'Serializable', 'Constable']],
|
|
11638
|
+
['String', ['CharSequence', 'Comparable', 'Serializable', 'Constable', 'ConstantDesc']],
|
|
11639
|
+
['BigInteger', ['Number', 'Comparable', 'Serializable']],
|
|
11640
|
+
['BigDecimal', ['Number', 'Comparable', 'Serializable']],
|
|
11641
|
+
]);
|
|
11642
|
+
|
|
10928
11643
|
// Which parameter types a call-site literal kind can bind (Java overload
|
|
10929
11644
|
// resolution: identity, widening, boxing — plus the boxed types' interfaces).
|
|
10930
11645
|
// Anything not provably incompatible MATCHES: only certainty excludes.
|
|
@@ -10936,8 +11651,37 @@ const JAVA_KIND_TYPES = {
|
|
|
10936
11651
|
float: ['float', 'double', 'Float', 'Number', 'Comparable', 'Serializable'],
|
|
10937
11652
|
double: ['double', 'Double', 'Number', 'Comparable', 'Serializable'],
|
|
10938
11653
|
boolean: ['boolean', 'Boolean', 'Comparable', 'Serializable'],
|
|
11654
|
+
// Not literal kinds (no short/byte literals in Java) — these entries
|
|
11655
|
+
// serve the value-typed conversion checks (fix #299D).
|
|
11656
|
+
short: ['short', 'int', 'long', 'float', 'double', 'Short', 'Number', 'Comparable', 'Serializable'],
|
|
11657
|
+
byte: ['byte', 'short', 'int', 'long', 'float', 'double', 'Byte', 'Number', 'Comparable', 'Serializable'],
|
|
10939
11658
|
};
|
|
10940
11659
|
|
|
11660
|
+
// Unboxing is defined for exactly these wrapper types (JLS 5.1.8).
|
|
11661
|
+
const JAVA_WRAPPER_PRIMITIVES = new Map([
|
|
11662
|
+
['Integer', 'int'], ['Long', 'long'], ['Short', 'short'], ['Byte', 'byte'],
|
|
11663
|
+
['Character', 'char'], ['Boolean', 'boolean'],
|
|
11664
|
+
['Float', 'float'], ['Double', 'double'],
|
|
11665
|
+
]);
|
|
11666
|
+
|
|
11667
|
+
// Declared types of the JDK numeric-constant fields (fix #299D): exact
|
|
11668
|
+
// compiler contracts, consulted only when no project field shadows the
|
|
11669
|
+
// owner/field pair.
|
|
11670
|
+
const JAVA_PLATFORM_FIELD_TYPES = new Map([
|
|
11671
|
+
['Float.NaN', 'float'], ['Float.POSITIVE_INFINITY', 'float'],
|
|
11672
|
+
['Float.NEGATIVE_INFINITY', 'float'], ['Float.MIN_VALUE', 'float'],
|
|
11673
|
+
['Float.MAX_VALUE', 'float'], ['Float.MIN_NORMAL', 'float'],
|
|
11674
|
+
['Double.NaN', 'double'], ['Double.POSITIVE_INFINITY', 'double'],
|
|
11675
|
+
['Double.NEGATIVE_INFINITY', 'double'], ['Double.MIN_VALUE', 'double'],
|
|
11676
|
+
['Double.MAX_VALUE', 'double'], ['Double.MIN_NORMAL', 'double'],
|
|
11677
|
+
['Integer.MAX_VALUE', 'int'], ['Integer.MIN_VALUE', 'int'],
|
|
11678
|
+
['Long.MAX_VALUE', 'long'], ['Long.MIN_VALUE', 'long'],
|
|
11679
|
+
['Short.MAX_VALUE', 'short'], ['Short.MIN_VALUE', 'short'],
|
|
11680
|
+
['Byte.MAX_VALUE', 'byte'], ['Byte.MIN_VALUE', 'byte'],
|
|
11681
|
+
['Character.MAX_VALUE', 'char'], ['Character.MIN_VALUE', 'char'],
|
|
11682
|
+
['Boolean.TRUE', 'Boolean'], ['Boolean.FALSE', 'Boolean'],
|
|
11683
|
+
]);
|
|
11684
|
+
|
|
10941
11685
|
// Exact subset of C# implicit conversions for compiler-owned closed types.
|
|
10942
11686
|
// Returns null when user-defined conversions or external ancestry could
|
|
10943
11687
|
// matter. A false verdict is therefore exclusion-grade evidence.
|
|
@@ -10995,6 +11739,19 @@ function _csharpKnownTypeAssignable(actualRaw, expectedRaw) {
|
|
|
10995
11739
|
// name-based guesses. They close the common portable-AST gap where overload
|
|
10996
11740
|
// resolution depends on the return type of a library call.
|
|
10997
11741
|
const JAVA_PLATFORM_METHOD_RETURNS = new Map([
|
|
11742
|
+
// Exact JDK factory contracts (fix #299D): valueOf on the boxed and
|
|
11743
|
+
// arbitrary-precision types returns the owner type itself.
|
|
11744
|
+
['java.math.BigInteger#valueOf', 'java.math.BigInteger'],
|
|
11745
|
+
['java.math.BigDecimal#valueOf', 'java.math.BigDecimal'],
|
|
11746
|
+
['java.lang.Integer#valueOf', 'java.lang.Integer'],
|
|
11747
|
+
['java.lang.Long#valueOf', 'java.lang.Long'],
|
|
11748
|
+
['java.lang.Short#valueOf', 'java.lang.Short'],
|
|
11749
|
+
['java.lang.Byte#valueOf', 'java.lang.Byte'],
|
|
11750
|
+
['java.lang.Float#valueOf', 'java.lang.Float'],
|
|
11751
|
+
['java.lang.Double#valueOf', 'java.lang.Double'],
|
|
11752
|
+
['java.lang.Boolean#valueOf', 'java.lang.Boolean'],
|
|
11753
|
+
['java.lang.Character#valueOf', 'java.lang.Character'],
|
|
11754
|
+
['java.lang.String#valueOf', 'java.lang.String'],
|
|
10998
11755
|
['java.lang.Object#getClass', 'java.lang.Class'],
|
|
10999
11756
|
['java.lang.Class#getEnclosingClass', 'java.lang.Class'],
|
|
11000
11757
|
['java.lang.Class#getDeclaringClass', 'java.lang.Class'],
|
|
@@ -11244,7 +12001,13 @@ function _javaOwnerFieldType(index, owner, fieldName) {
|
|
|
11244
12001
|
definition.className === ownerSimple &&
|
|
11245
12002
|
(definition.type === 'field' || definition.memberType === 'field') &&
|
|
11246
12003
|
definition.fieldType);
|
|
11247
|
-
if (fields.length === 0)
|
|
12004
|
+
if (fields.length === 0) {
|
|
12005
|
+
// JDK constant-field contracts (fix #299D): Float.NaN is a float,
|
|
12006
|
+
// Boolean.TRUE a Boolean. Project fields, checked above, always
|
|
12007
|
+
// outrank the platform table.
|
|
12008
|
+
return JAVA_PLATFORM_FIELD_TYPES.get(`${ownerSimple}.${fieldName}`) ||
|
|
12009
|
+
null;
|
|
12010
|
+
}
|
|
11248
12011
|
|
|
11249
12012
|
const types = new Set();
|
|
11250
12013
|
for (const field of fields) {
|
|
@@ -11359,6 +12122,27 @@ function _javaArgKindMatches(index, kind, paramType, language) {
|
|
|
11359
12122
|
if (tSimple === bare) return true;
|
|
11360
12123
|
const platformAssignable = _javaPlatformAssignable(t, bare);
|
|
11361
12124
|
if (platformAssignable !== null) return platformAssignable;
|
|
12125
|
+
// A primitive argument type has closed conversion targets (fix
|
|
12126
|
+
// #299D): identity, widening, its own box, and the box's interfaces
|
|
12127
|
+
// — exactly the literal-kind table. This must precede the
|
|
12128
|
+
// final-reference denial below, which is boxing-blind (char DOES
|
|
12129
|
+
// bind a Character parameter). No project class can shadow a
|
|
12130
|
+
// primitive name, so no tDefs guard is needed.
|
|
12131
|
+
if (language === 'java' && JAVA_PRIMITIVES.has(tSimple) &&
|
|
12132
|
+
JAVA_KIND_TYPES[tSimple]) {
|
|
12133
|
+
return JAVA_KIND_TYPES[tSimple].includes(bare);
|
|
12134
|
+
}
|
|
12135
|
+
// A reference-typed argument reaches a primitive parameter only
|
|
12136
|
+
// through unboxing, defined for exactly the eight wrapper types
|
|
12137
|
+
// (then widening). Any other reference type — project, JDK, or
|
|
12138
|
+
// unknown — provably cannot bind a primitive parameter (fix #299D).
|
|
12139
|
+
if (language === 'java' && JAVA_PRIMITIVES.has(bare) &&
|
|
12140
|
+
!JAVA_PRIMITIVES.has(tSimple) && !/^[A-Z][0-9]?$/.test(tSimple)) {
|
|
12141
|
+
const unboxed = JAVA_WRAPPER_PRIMITIVES.get(tSimple);
|
|
12142
|
+
return unboxed != null &&
|
|
12143
|
+
(unboxed === bare ||
|
|
12144
|
+
JAVA_KIND_TYPES[unboxed]?.includes(bare) === true);
|
|
12145
|
+
}
|
|
11362
12146
|
// These java.lang types are final. A statically known different type
|
|
11363
12147
|
// can never bind their overload, even when the argument type's own
|
|
11364
12148
|
// ancestry ends in external Object and is therefore incomplete.
|
|
@@ -11381,6 +12165,14 @@ function _javaArgKindMatches(index, kind, paramType, language) {
|
|
|
11381
12165
|
d.modifiers?.includes('sealed'))) {
|
|
11382
12166
|
return false;
|
|
11383
12167
|
}
|
|
12168
|
+
if (language === 'java' && tDefs.length === 0) {
|
|
12169
|
+
// JDK value types have a complete, closed ancestry (fix #299D):
|
|
12170
|
+
// an Integer-typed argument can never bind add(JsonElement).
|
|
12171
|
+
// The no-project-def guard keeps a project class shadowing the
|
|
12172
|
+
// simple name in normal resolution.
|
|
12173
|
+
const valueAncestry = JAVA_PLATFORM_VALUE_ANCESTRY.get(tSimple);
|
|
12174
|
+
if (valueAncestry) return valueAncestry.includes(bare);
|
|
12175
|
+
}
|
|
11384
12176
|
if (tDefs.length === 0) return true; // external arg type — unknowable
|
|
11385
12177
|
const asTarget = [{ className: tSimple, file: tDefs[0].file }];
|
|
11386
12178
|
if (_isDispatchAncestor(index, bare, asTarget)) return true;
|
|
@@ -11400,6 +12192,7 @@ function _cppTypeCategory(type) {
|
|
|
11400
12192
|
const unqualified = original
|
|
11401
12193
|
.replace(/\b(const|volatile|constexpr|typename|struct|class)\b/g, ' ')
|
|
11402
12194
|
.replace(/&&|\.\.\.|[&*]/g, ' ')
|
|
12195
|
+
.replace(/\[[^\]]*\]/g, ' ')
|
|
11403
12196
|
.replace(/\s+/g, ' ')
|
|
11404
12197
|
.trim();
|
|
11405
12198
|
const headText = unqualified.split('<')[0].trim();
|
|
@@ -11519,7 +12312,8 @@ function _cppArgKindMatches(kind, paramType) {
|
|
|
11519
12312
|
if (kind === 'null') {
|
|
11520
12313
|
return !['number', 'bool', 'character'].includes(expected.kind);
|
|
11521
12314
|
}
|
|
11522
|
-
if (kind.startsWith('type:') || kind.startsWith('call:')
|
|
12315
|
+
if (kind.startsWith('type:') || kind.startsWith('call:') ||
|
|
12316
|
+
kind.startsWith('bcall:')) {
|
|
11523
12317
|
const actualType = kind.slice(kind.indexOf(':') + 1);
|
|
11524
12318
|
const actual = _cppTypeCategory(actualType);
|
|
11525
12319
|
if (actual.head && expected.head && actual.head === expected.head) return true;
|
|
@@ -11634,7 +12428,7 @@ function _javaTypeAtLeastAsSpecific(index, subType, superType, subDef) {
|
|
|
11634
12428
|
// when every argument position is at least as specific as every competing
|
|
11635
12429
|
// candidate and at least one position is strictly more specific. Unknown
|
|
11636
12430
|
// relationships stay ambiguous.
|
|
11637
|
-
function _javaMostSpecificOverload(index, applicable, call) {
|
|
12431
|
+
function _javaMostSpecificOverload(index, applicable, call, language) {
|
|
11638
12432
|
const argCount = call.argCount;
|
|
11639
12433
|
if (!Number.isInteger(argCount) || applicable.length < 2) return null;
|
|
11640
12434
|
// Compiler-visible exact static types outrank candidates whose external
|
|
@@ -11670,6 +12464,64 @@ function _javaMostSpecificOverload(index, applicable, call) {
|
|
|
11670
12464
|
if (winners.length === 1) return winners[0].definition;
|
|
11671
12465
|
}
|
|
11672
12466
|
}
|
|
12467
|
+
// JLS 15.12.2 phase discipline (fix #299D): when EVERY argument's static
|
|
12468
|
+
// type is a known primitive, strict-invocation candidates (identity or
|
|
12469
|
+
// primitive widening, fixed arity — phase 1) exclude boxing candidates
|
|
12470
|
+
// (phase 2) from the race entirely; among widening targets the narrowest
|
|
12471
|
+
// is most specific (an int literal binds value(long), not value(float)/
|
|
12472
|
+
// value(double)/value(Number)). Varargs params are phase 3 and never
|
|
12473
|
+
// count as strict. Any non-primitive or unknown position refuses — phase
|
|
12474
|
+
// membership must be provable for every argument.
|
|
12475
|
+
if (language === 'java' && Array.isArray(call.argKinds) &&
|
|
12476
|
+
call.argKinds.length >= argCount && argCount > 0) {
|
|
12477
|
+
const primOf = (kind) => {
|
|
12478
|
+
if (!kind || typeof kind !== 'string') return null;
|
|
12479
|
+
if (JAVA_PRIMITIVES.has(kind)) return kind; // literal kinds
|
|
12480
|
+
const staticType = _javaStaticTypeForKind(index, kind);
|
|
12481
|
+
const simple = staticType ? staticType.split('.').pop() : null;
|
|
12482
|
+
return simple && JAVA_PRIMITIVES.has(simple) ? simple : null;
|
|
12483
|
+
};
|
|
12484
|
+
const prims = [];
|
|
12485
|
+
for (let i = 0; i < argCount; i++) prims.push(primOf(call.argKinds[i]));
|
|
12486
|
+
if (prims.every(Boolean)) {
|
|
12487
|
+
const strictAt = (prim, definition, i) => {
|
|
12488
|
+
const param = _javaParamAt(definition, i, call);
|
|
12489
|
+
if (!param || param.rest) return false;
|
|
12490
|
+
const bare = _javaBareParamType(param);
|
|
12491
|
+
return bare !== null && JAVA_PRIMITIVES.has(bare) &&
|
|
12492
|
+
(prim === bare ||
|
|
12493
|
+
JAVA_KIND_TYPES[prim]?.includes(bare) === true);
|
|
12494
|
+
};
|
|
12495
|
+
const strict = applicable.filter(definition => {
|
|
12496
|
+
for (let i = 0; i < argCount; i++) {
|
|
12497
|
+
if (!strictAt(prims[i], definition, i)) return false;
|
|
12498
|
+
}
|
|
12499
|
+
return true;
|
|
12500
|
+
});
|
|
12501
|
+
if (strict.length === 1) return strict[0];
|
|
12502
|
+
if (strict.length > 1) {
|
|
12503
|
+
const dominatesPrim = (a, b) => {
|
|
12504
|
+
let strictly = false;
|
|
12505
|
+
for (let i = 0; i < argCount; i++) {
|
|
12506
|
+
const at = _javaBareParamType(_javaParamAt(a, i, call));
|
|
12507
|
+
const bt = _javaBareParamType(_javaParamAt(b, i, call));
|
|
12508
|
+
if (at === bt) continue;
|
|
12509
|
+
if (!JAVA_PRIMITIVES.has(bt) ||
|
|
12510
|
+
JAVA_KIND_TYPES[at]?.includes(bt) !== true) {
|
|
12511
|
+
return false;
|
|
12512
|
+
}
|
|
12513
|
+
strictly = true;
|
|
12514
|
+
}
|
|
12515
|
+
return strictly;
|
|
12516
|
+
};
|
|
12517
|
+
const winners = strict.filter(a =>
|
|
12518
|
+
strict.every(b => a === b || dominatesPrim(a, b)));
|
|
12519
|
+
if (winners.length === 1) return winners[0];
|
|
12520
|
+
return null; // strict phase engaged but unranked — visible
|
|
12521
|
+
}
|
|
12522
|
+
// no strict candidate: boxing phase — general ranking decides
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
11673
12525
|
const dominates = (a, b) => {
|
|
11674
12526
|
let strict = false;
|
|
11675
12527
|
for (let i = 0; i < argCount; i++) {
|
|
@@ -11831,16 +12683,142 @@ function _cppQualifiedPathOwnsTarget(index, callerFile, call, targetDefs) {
|
|
|
11831
12683
|
return targetDefs.some(definition => {
|
|
11832
12684
|
if (!definition.file) return false;
|
|
11833
12685
|
const namespace = String(definition.namespace || '');
|
|
11834
|
-
if (namespace
|
|
11835
|
-
namespace
|
|
11836
|
-
|
|
11837
|
-
|
|
12686
|
+
if (namespace) {
|
|
12687
|
+
// An AST-recorded namespace is stronger than the portable-header
|
|
12688
|
+
// path heuristic. `fmt::vformat_to` cannot name
|
|
12689
|
+
// `detail::vformat_to`; only the exact namespace (or the same
|
|
12690
|
+
// namespace expressed relative to an enclosing scope) owns it.
|
|
12691
|
+
return namespace === call.receiver ||
|
|
12692
|
+
String(call.receiver).endsWith(`::${namespace}`) ||
|
|
12693
|
+
namespace.endsWith(`::${call.receiver}`);
|
|
11838
12694
|
}
|
|
11839
12695
|
const relative = path.relative(index.root, definition.file);
|
|
11840
12696
|
return relative.split(path.sep).includes(namespaceRoot);
|
|
11841
12697
|
});
|
|
11842
12698
|
}
|
|
11843
12699
|
|
|
12700
|
+
function _cppMacroParamOutcomes(index, callerFile, macroName, argIndex,
|
|
12701
|
+
depth = 0, seen = new Set()) {
|
|
12702
|
+
if (!macroName || depth > 8) return new Set(['unknown']);
|
|
12703
|
+
let cache;
|
|
12704
|
+
let cacheKey;
|
|
12705
|
+
if (depth === 0) {
|
|
12706
|
+
if (!index._cppMacroParamOutcomesCache) {
|
|
12707
|
+
Object.defineProperty(index, '_cppMacroParamOutcomesCache', {
|
|
12708
|
+
value: new Map(),
|
|
12709
|
+
configurable: true,
|
|
12710
|
+
});
|
|
12711
|
+
}
|
|
12712
|
+
cache = index._cppMacroParamOutcomesCache;
|
|
12713
|
+
cacheKey = `${callerFile}\0${macroName}\0${argIndex}`;
|
|
12714
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
12715
|
+
}
|
|
12716
|
+
const key = `${macroName}:${argIndex}`;
|
|
12717
|
+
if (seen.has(key)) return new Set(['unknown']);
|
|
12718
|
+
const nextSeen = new Set(seen);
|
|
12719
|
+
nextSeen.add(key);
|
|
12720
|
+
const visible = _cppVisibleFiles(index, callerFile);
|
|
12721
|
+
const definitions = (index.symbols.get(macroName) || []).filter(definition =>
|
|
12722
|
+
definition.type === 'macro' && definition.functionLike !== false &&
|
|
12723
|
+
definition.file && visible.has(definition.file));
|
|
12724
|
+
if (definitions.length === 0) {
|
|
12725
|
+
const result = new Set(['unknown']);
|
|
12726
|
+
if (cache) cache.set(cacheKey, result);
|
|
12727
|
+
return result;
|
|
12728
|
+
}
|
|
12729
|
+
const outcomes = new Set();
|
|
12730
|
+
for (const definition of definitions) {
|
|
12731
|
+
const effects = (definition.macroParamEffects || [])
|
|
12732
|
+
.filter(effect => effect.paramIndex === argIndex);
|
|
12733
|
+
if (effects.length === 0) {
|
|
12734
|
+
outcomes.add('preserve');
|
|
12735
|
+
continue;
|
|
12736
|
+
}
|
|
12737
|
+
for (const effect of effects) {
|
|
12738
|
+
if (effect.kind === 'qualified' && effect.qualifier) {
|
|
12739
|
+
outcomes.add(`qualified:${effect.qualifier}`);
|
|
12740
|
+
} else if (effect.kind === 'forwarded' && effect.macro &&
|
|
12741
|
+
Number.isInteger(effect.argIndex)) {
|
|
12742
|
+
for (const outcome of _cppMacroParamOutcomes(
|
|
12743
|
+
index, callerFile, effect.macro, effect.argIndex,
|
|
12744
|
+
depth + 1, nextSeen)) {
|
|
12745
|
+
outcomes.add(outcome);
|
|
12746
|
+
}
|
|
12747
|
+
} else {
|
|
12748
|
+
outcomes.add('unknown');
|
|
12749
|
+
}
|
|
12750
|
+
}
|
|
12751
|
+
}
|
|
12752
|
+
const result = outcomes.size > 0 ? outcomes : new Set(['unknown']);
|
|
12753
|
+
if (cache) cache.set(cacheKey, result);
|
|
12754
|
+
return result;
|
|
12755
|
+
}
|
|
12756
|
+
|
|
12757
|
+
function _cppMacroQualifierCanNameTarget(qualifier, targetDefs) {
|
|
12758
|
+
if (!qualifier || !Array.isArray(targetDefs) || targetDefs.length === 0) {
|
|
12759
|
+
return false;
|
|
12760
|
+
}
|
|
12761
|
+
if (qualifier === 'global') {
|
|
12762
|
+
return targetDefs.some(definition =>
|
|
12763
|
+
!definition.className && !definition.receiver && !definition.namespace);
|
|
12764
|
+
}
|
|
12765
|
+
return targetDefs.some(definition => {
|
|
12766
|
+
const owner = definition.className || definition.receiver;
|
|
12767
|
+
const namespace = definition.namespace;
|
|
12768
|
+
return owner === qualifier || namespace === qualifier ||
|
|
12769
|
+
String(owner || '').endsWith(`::${qualifier}`) ||
|
|
12770
|
+
String(namespace || '').endsWith(`::${qualifier}`);
|
|
12771
|
+
});
|
|
12772
|
+
}
|
|
12773
|
+
|
|
12774
|
+
/**
|
|
12775
|
+
* Whether a macro transforms this source call's argument into a qualified
|
|
12776
|
+
* callable. Replacement-list effects are parser-derived and followed through
|
|
12777
|
+
* forwarding macros; conditional definitions are unioned, so disagreement
|
|
12778
|
+
* routes visible rather than becoming false exclusion evidence.
|
|
12779
|
+
*/
|
|
12780
|
+
function _cppMacroTargetDisposition(index, callerFile, call, targetDefs) {
|
|
12781
|
+
if (!Array.isArray(call?.macroArguments) ||
|
|
12782
|
+
call.macroArguments.length === 0) return null;
|
|
12783
|
+
let uncertain = false;
|
|
12784
|
+
let qualified = null;
|
|
12785
|
+
let qualifiedAway = false;
|
|
12786
|
+
for (const wrapper of call.macroArguments) {
|
|
12787
|
+
const outcomes = _cppMacroParamOutcomes(
|
|
12788
|
+
index, callerFile, wrapper.name, wrapper.argIndex);
|
|
12789
|
+
const qualifiers = [...outcomes]
|
|
12790
|
+
.filter(outcome => outcome.startsWith('qualified:'))
|
|
12791
|
+
.map(outcome => outcome.slice('qualified:'.length));
|
|
12792
|
+
if (qualifiers.length === 0) continue;
|
|
12793
|
+
const targetMatches = qualifiers.map(qualifier =>
|
|
12794
|
+
_cppMacroQualifierCanNameTarget(qualifier, targetDefs));
|
|
12795
|
+
const canNameTarget = targetMatches.some(Boolean);
|
|
12796
|
+
const allNameTarget = targetMatches.every(Boolean);
|
|
12797
|
+
const hasUnknown = outcomes.has('unknown') || outcomes.has('preserve');
|
|
12798
|
+
if (canNameTarget) {
|
|
12799
|
+
const unique = new Set(qualifiers);
|
|
12800
|
+
if (!hasUnknown && allNameTarget && unique.size === 1) {
|
|
12801
|
+
const qualifier = [...unique][0];
|
|
12802
|
+
if (qualified && qualified !== qualifier) uncertain = true;
|
|
12803
|
+
else qualified = qualifier;
|
|
12804
|
+
} else {
|
|
12805
|
+
// Multiple conditional definitions, a preserve path, or a
|
|
12806
|
+
// mixture of target and non-target qualifiers means the
|
|
12807
|
+
// preprocessor can change identity by build configuration.
|
|
12808
|
+
uncertain = true;
|
|
12809
|
+
}
|
|
12810
|
+
continue;
|
|
12811
|
+
}
|
|
12812
|
+
if (!hasUnknown) qualifiedAway = true;
|
|
12813
|
+
else uncertain = true;
|
|
12814
|
+
}
|
|
12815
|
+
if (uncertain || (qualified && qualifiedAway)) {
|
|
12816
|
+
return { kind: 'uncertain' };
|
|
12817
|
+
}
|
|
12818
|
+
if (qualifiedAway) return { kind: 'other' };
|
|
12819
|
+
return qualified ? { kind: 'qualified', qualifier: qualified } : null;
|
|
12820
|
+
}
|
|
12821
|
+
|
|
11844
12822
|
function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
11845
12823
|
const targetOwners = new Set(targetDefs.map(d => d.className).filter(Boolean));
|
|
11846
12824
|
const targetLanguage = targetDefs[0]?.file && index.files.get(targetDefs[0].file)?.language;
|
|
@@ -11867,12 +12845,22 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11867
12845
|
targetDefs.every(d => !d.className && !d.receiver) &&
|
|
11868
12846
|
_cppTargetVisibleFrom(index, callerFile, targetDefs)) {
|
|
11869
12847
|
const visible = _cppVisibleFiles(index, callerFile);
|
|
11870
|
-
const
|
|
12848
|
+
const pathCanName = definition => {
|
|
12849
|
+
if (!call.isPathCall || !call.receiver) return true;
|
|
12850
|
+
const namespace = String(definition.namespace || '');
|
|
12851
|
+
// Missing namespace metadata is not negative evidence: namespace
|
|
12852
|
+
// macros (FMT_BEGIN_NAMESPACE and peers) are intentionally opaque
|
|
12853
|
+
// to the portable tree. A recorded namespace, however, must match
|
|
12854
|
+
// the qualifier's exact/relative namespace identity.
|
|
12855
|
+
if (!namespace) return true;
|
|
12856
|
+
return namespace === call.receiver ||
|
|
12857
|
+
String(call.receiver).endsWith(`::${namespace}`) ||
|
|
12858
|
+
namespace.endsWith(`::${call.receiver}`);
|
|
12859
|
+
};
|
|
11871
12860
|
family = definitions.filter(d =>
|
|
11872
12861
|
!NON_CALLABLE_TYPES.has(d.type) &&
|
|
11873
12862
|
!d.className && !d.receiver &&
|
|
11874
|
-
(
|
|
11875
|
-
pinnedKeys.has(`${d.file}:${d.startLine}`)) &&
|
|
12863
|
+
pathCanName(d) &&
|
|
11876
12864
|
(visible.has(d.file) ||
|
|
11877
12865
|
pinnedKeys.has(`${d.file}:${d.startLine}`)));
|
|
11878
12866
|
} else {
|
|
@@ -11908,6 +12896,17 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11908
12896
|
// constrained templates can share an identical function signature while
|
|
11909
12897
|
// remaining distinct compiler overloads.
|
|
11910
12898
|
const pinSigs = new Set(targetDefs.map(typeSig).filter(s => s !== null));
|
|
12899
|
+
// Occupied dispatch slots (fix #299D): an ancestor def whose signature
|
|
12900
|
+
// matches ANY family member of the descendant class is that member's
|
|
12901
|
+
// override/hiding slot — the same bindable method, never an extra
|
|
12902
|
+
// sibling. Without this, JsonTreeWriter's fully-overridden value(...)
|
|
12903
|
+
// family double-counted every JsonWriter overload and the exact-type
|
|
12904
|
+
// winner was never unique (two value(String) "candidates").
|
|
12905
|
+
const familySigs = new Set(pinSigs);
|
|
12906
|
+
for (const d of family) {
|
|
12907
|
+
const sig = typeSig(d);
|
|
12908
|
+
if (sig !== null) familySigs.add(sig);
|
|
12909
|
+
}
|
|
11911
12910
|
const pinFile = targetDefs.find(d => d.file)?.file;
|
|
11912
12911
|
const seenCls = new Set(targetOwners);
|
|
11913
12912
|
const queue = [...targetOwners];
|
|
@@ -11924,13 +12923,42 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11924
12923
|
for (const d of definitions) {
|
|
11925
12924
|
if (NON_CALLABLE_TYPES.has(d.type) || d.className !== pName) continue;
|
|
11926
12925
|
const sig = typeSig(d);
|
|
11927
|
-
if (sig !== null &&
|
|
12926
|
+
if (sig !== null && familySigs.has(sig)) continue; // occupied slot
|
|
12927
|
+
if (sig !== null) familySigs.add(sig);
|
|
11928
12928
|
family.push(d);
|
|
11929
12929
|
}
|
|
11930
12930
|
}
|
|
11931
12931
|
}
|
|
12932
|
+
if (targetLanguage === 'cpp' && call.isPathCall && family.length > 0 &&
|
|
12933
|
+
!family.some(definition =>
|
|
12934
|
+
pinnedKeys.has(`${definition.file}:${definition.startLine}`))) {
|
|
12935
|
+
// The qualifier selects a modeled namespace family that does not
|
|
12936
|
+
// contain the pin (`fmt::vformat_to` versus
|
|
12937
|
+
// `detail::vformat_to`). This is exact negative identity evidence
|
|
12938
|
+
// even when that family has only one overload.
|
|
12939
|
+
return 'other-overload';
|
|
12940
|
+
}
|
|
11932
12941
|
if (family.length <= 1) return null;
|
|
11933
12942
|
if (family.every(d => pinnedKeys.has(`${d.file}:${d.startLine}`))) return null;
|
|
12943
|
+
// Producer-return argument typing (fix #299B): a bare-identifier
|
|
12944
|
+
// producer (`paint(tint(3))`) types its argument position from the
|
|
12945
|
+
// project's declared return type — the #199/#207 return-type-flow rail
|
|
12946
|
+
// at the argument site. Resolution demands a unique project identity
|
|
12947
|
+
// with full return-text agreement; anything less keeps the opaque
|
|
12948
|
+
// `bcall:` kind (matches everything, no decision either way).
|
|
12949
|
+
if (targetLanguage === 'cpp' && Array.isArray(call.argKinds) &&
|
|
12950
|
+
call.argKinds.some(kind => typeof kind === 'string' &&
|
|
12951
|
+
kind.startsWith('bcall:'))) {
|
|
12952
|
+
const resolvedKinds = call.argKinds.map(kind => {
|
|
12953
|
+
if (typeof kind !== 'string' || !kind.startsWith('bcall:')) return kind;
|
|
12954
|
+
const producerReturn = _cppBareProducerReturnType(
|
|
12955
|
+
index, kind.slice('bcall:'.length));
|
|
12956
|
+
return producerReturn ? `type:${producerReturn}` : kind;
|
|
12957
|
+
});
|
|
12958
|
+
if (resolvedKinds.some((kind, i) => kind !== call.argKinds[i])) {
|
|
12959
|
+
call = { ...call, argKinds: resolvedKinds };
|
|
12960
|
+
}
|
|
12961
|
+
}
|
|
11934
12962
|
let applicable = family.filter(d => _overloadApplicable(index, call, d));
|
|
11935
12963
|
if (applicable.length === 0) return null; // shape fits nothing we model — no claim
|
|
11936
12964
|
if (targetLanguage === 'java' || targetLanguage === 'csharp') {
|
|
@@ -11939,7 +12967,8 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11939
12967
|
}
|
|
11940
12968
|
const mostSpecific = (targetLanguage === 'java' ||
|
|
11941
12969
|
targetLanguage === 'csharp')
|
|
11942
|
-
? _javaMostSpecificOverload(index, applicable, call)
|
|
12970
|
+
? _javaMostSpecificOverload(index, applicable, call, targetLanguage)
|
|
12971
|
+
: null;
|
|
11943
12972
|
if (mostSpecific) {
|
|
11944
12973
|
return pinnedKeys.has(`${mostSpecific.file}:${mostSpecific.startLine}`)
|
|
11945
12974
|
? null : 'other-overload';
|
|
@@ -11951,6 +12980,13 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11951
12980
|
return null;
|
|
11952
12981
|
}
|
|
11953
12982
|
if (applicable.length === 1) return null; // uniquely the pinned overload
|
|
12983
|
+
if (targetLanguage === 'cpp') {
|
|
12984
|
+
const winner = _cppExactOverloadWinner(call, applicable);
|
|
12985
|
+
if (winner) {
|
|
12986
|
+
return pinnedKeys.has(`${winner.file}:${winner.startLine}`)
|
|
12987
|
+
? null : 'other-overload';
|
|
12988
|
+
}
|
|
12989
|
+
}
|
|
11954
12990
|
const compileTimeDispatch = targetLanguage === 'cpp';
|
|
11955
12991
|
const templateOnly = compileTimeDispatch &&
|
|
11956
12992
|
applicable.every(definition => definition.templateDependent);
|
|
@@ -11966,6 +13002,121 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11966
13002
|
};
|
|
11967
13003
|
}
|
|
11968
13004
|
|
|
13005
|
+
// C++ exact-static-type overload selection (fix #299C): when every argument
|
|
13006
|
+
// position carries a concrete static type (`type:X` from declared locals or
|
|
13007
|
+
// casts) and exactly one NON-template candidate matches every position at
|
|
13008
|
+
// Exact Match rank, the compiler selects it — identity conversion outranks
|
|
13009
|
+
// every other conversion sequence, and non-template beats template on the
|
|
13010
|
+
// tie-break. Template winners are refused (their param text can name their
|
|
13011
|
+
// own template parameters, and SFINAE can disable them invisibly); literal
|
|
13012
|
+
// and `expr` kinds refuse the whole selection (integer literals rank equally
|
|
13013
|
+
// against sibling integer widths). Exactness is deliberately narrower than
|
|
13014
|
+
// the compiler's Exact Match class: by-value top-level const is ignored and
|
|
13015
|
+
// `const X&` binding accepted, but non-const refs (cast rvalues can't bind),
|
|
13016
|
+
// rvalue refs, and pointer-pointee qualification changes all refuse — a
|
|
13017
|
+
// missed exact match keeps the site visible, a wrong one would exclude a
|
|
13018
|
+
// true caller.
|
|
13019
|
+
function _cppNormalizeTypeText(text) {
|
|
13020
|
+
if (!text) return null;
|
|
13021
|
+
return String(text).replace(/\s+/g, ' ')
|
|
13022
|
+
.replace(/\s*([*&])\s*/g, ' $1').trim() || null;
|
|
13023
|
+
}
|
|
13024
|
+
|
|
13025
|
+
function _cppStripTopLevelConst(text) {
|
|
13026
|
+
if (!text || /[*&]$/.test(text)) return text;
|
|
13027
|
+
return text.replace(/^const /, '').replace(/^volatile /, '');
|
|
13028
|
+
}
|
|
13029
|
+
|
|
13030
|
+
function _cppExactParamMatch(paramType, argType) {
|
|
13031
|
+
const param = _cppNormalizeTypeText(paramType);
|
|
13032
|
+
const arg = _cppStripTopLevelConst(_cppNormalizeTypeText(argType));
|
|
13033
|
+
if (!param || !arg) return false;
|
|
13034
|
+
return param === arg ||
|
|
13035
|
+
_cppStripTopLevelConst(param) === arg ||
|
|
13036
|
+
param === `const ${arg} &`;
|
|
13037
|
+
}
|
|
13038
|
+
|
|
13039
|
+
// Resolve a bare-identifier producer name to its declared return type
|
|
13040
|
+
// (fix #299B). Discipline mirrors the #199/#207 return-type-flow rails:
|
|
13041
|
+
// every callable def of the name project-wide must live in a C-family file
|
|
13042
|
+
// and agree on ONE full normalized return text. Type defs sharing the name
|
|
13043
|
+
// refuse (the bare call may be a constructor), template producers refuse
|
|
13044
|
+
// (their substituted return is instantiation-dependent), `auto` without a
|
|
13045
|
+
// recorded trailing type refuses, and return-less defs are ignored only
|
|
13046
|
+
// when they are declarations whose parameter shape matches a return-bearing
|
|
13047
|
+
// def — a C++ declaration's return type must match its definition, so a
|
|
13048
|
+
// ret-less signature there is a parse gap on the SAME entity, never a
|
|
13049
|
+
// hidden disagreeing overload.
|
|
13050
|
+
function _cppBareProducerReturnType(index, producerName) {
|
|
13051
|
+
if (!producerName) return null;
|
|
13052
|
+
const defs = (index.symbols.get(producerName) || []);
|
|
13053
|
+
if (defs.length === 0) return null;
|
|
13054
|
+
const paramKey = (def) => Array.isArray(def.paramsStructured)
|
|
13055
|
+
? def.paramsStructured.map(param =>
|
|
13056
|
+
_cppNormalizeTypeText(param?.type) || '?').join('\0')
|
|
13057
|
+
: null;
|
|
13058
|
+
const returnBearing = [];
|
|
13059
|
+
const returnLess = [];
|
|
13060
|
+
for (const def of defs) {
|
|
13061
|
+
const language = index.files.get(def.file)?.language;
|
|
13062
|
+
if (language !== 'c' && language !== 'cpp') return null;
|
|
13063
|
+
if (NON_CALLABLE_TYPES.has(def.type)) return null; // constructor risk
|
|
13064
|
+
if (def.isSpecialization) continue; // same entity as its primary
|
|
13065
|
+
if (def.returnType) returnBearing.push(def);
|
|
13066
|
+
else returnLess.push(def);
|
|
13067
|
+
}
|
|
13068
|
+
if (returnBearing.length === 0) return null;
|
|
13069
|
+
for (const def of returnLess) {
|
|
13070
|
+
if (!def.isSignature) return null;
|
|
13071
|
+
const key = paramKey(def);
|
|
13072
|
+
if (key === null ||
|
|
13073
|
+
!returnBearing.some(other => paramKey(other) === key)) {
|
|
13074
|
+
return null;
|
|
13075
|
+
}
|
|
13076
|
+
}
|
|
13077
|
+
let agreed = null;
|
|
13078
|
+
for (const def of returnBearing) {
|
|
13079
|
+
if (def.templateDependent) return null;
|
|
13080
|
+
const returnText = _cppNormalizeTypeText(def.returnType);
|
|
13081
|
+
if (!returnText || /^auto\b/.test(returnText)) return null;
|
|
13082
|
+
if (agreed === null) agreed = returnText;
|
|
13083
|
+
else if (agreed !== returnText) return null;
|
|
13084
|
+
}
|
|
13085
|
+
return agreed;
|
|
13086
|
+
}
|
|
13087
|
+
|
|
13088
|
+
function _cppExactOverloadWinner(call, applicable) {
|
|
13089
|
+
const kinds = call.argKinds;
|
|
13090
|
+
if (!Array.isArray(kinds) || !Number.isInteger(call.argCount) ||
|
|
13091
|
+
call.argCount === 0 || kinds.length < call.argCount) return null;
|
|
13092
|
+
const argTypes = [];
|
|
13093
|
+
for (let i = 0; i < call.argCount; i++) {
|
|
13094
|
+
const kind = kinds[i];
|
|
13095
|
+
if (typeof kind !== 'string' || !kind.startsWith('type:')) return null;
|
|
13096
|
+
const argType = kind.slice('type:'.length);
|
|
13097
|
+
if (!argType) return null;
|
|
13098
|
+
argTypes.push(argType);
|
|
13099
|
+
}
|
|
13100
|
+
let winner = null;
|
|
13101
|
+
for (const def of applicable) {
|
|
13102
|
+
if (def.templateDependent || def.isSpecialization) continue;
|
|
13103
|
+
const ps = def.paramsStructured;
|
|
13104
|
+
if (!Array.isArray(ps) || ps.length < call.argCount) continue;
|
|
13105
|
+
if (ps.some(param => param && (param.rest || param.variadic))) continue;
|
|
13106
|
+
let exact = true;
|
|
13107
|
+
for (let i = 0; i < call.argCount; i++) {
|
|
13108
|
+
if (!_cppExactParamMatch(ps[i]?.type, argTypes[i])) {
|
|
13109
|
+
exact = false;
|
|
13110
|
+
break;
|
|
13111
|
+
}
|
|
13112
|
+
}
|
|
13113
|
+
if (!exact) continue;
|
|
13114
|
+
if (winner) return null; // two exact candidates — refuse
|
|
13115
|
+
winner = def;
|
|
13116
|
+
}
|
|
13117
|
+
return winner;
|
|
13118
|
+
}
|
|
13119
|
+
|
|
11969
13120
|
/**
|
|
11970
13121
|
* Build the target type set for receiver-class disambiguation: target
|
|
11971
13122
|
* classes/receivers + their non-overriding subtypes (transitively). A Child
|
|
@@ -12611,6 +13762,31 @@ function _builtinMethodReturnType(language, receiverType, methodName) {
|
|
|
12611
13762
|
* the PRODUCER's scope (_resolveFlowTypeOrigin). External producer packages
|
|
12612
13763
|
* and reject-set returns stay untyped — no evidence either way.
|
|
12613
13764
|
*/
|
|
13765
|
+
function _goBuiltinChainedReceiverType(index, fileEntry, filePath, call) {
|
|
13766
|
+
if (fileEntry?.language !== 'go' || !call.receiverCallResultType) return null;
|
|
13767
|
+
const type = call.receiverCallResultType;
|
|
13768
|
+
const qualifier = call.receiverCallResultTypeQualifier;
|
|
13769
|
+
if (qualifier) {
|
|
13770
|
+
const qualified = _goQualifiedReceiverType(
|
|
13771
|
+
index, fileEntry, qualifier, type);
|
|
13772
|
+
if (!qualified) return null;
|
|
13773
|
+
if (qualified.kind !== 'project') {
|
|
13774
|
+
return {
|
|
13775
|
+
externalVia: qualified.via,
|
|
13776
|
+
externalConcrete: true,
|
|
13777
|
+
};
|
|
13778
|
+
}
|
|
13779
|
+
if (qualified.defs.length === 0 ||
|
|
13780
|
+
new Set(qualified.defs.map(definition => definition.file)).size !== 1) {
|
|
13781
|
+
return null;
|
|
13782
|
+
}
|
|
13783
|
+
return { type, fromFile: qualified.defs[0].file };
|
|
13784
|
+
}
|
|
13785
|
+
const origin = _resolveFlowTypeOrigin(index, filePath, type);
|
|
13786
|
+
if (!origin?.fromFile) return null;
|
|
13787
|
+
return { type, fromFile: origin.fromFile };
|
|
13788
|
+
}
|
|
13789
|
+
|
|
12614
13790
|
function _nominalChainedReceiverType(index, call, fileEntry, filePath) {
|
|
12615
13791
|
const language = fileEntry.language;
|
|
12616
13792
|
const defs = (index.symbols.get(call.receiverCall) || [])
|
|
@@ -13296,18 +14472,26 @@ function _rustClosureReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
13296
14472
|
function _typeOfCallResultFold(index, fileEntry, filePath, record, ctx, consumerAwaited) {
|
|
13297
14473
|
if (ctx.memo.has(record)) return ctx.memo.get(record);
|
|
13298
14474
|
// Real builder APIs routinely exceed 64 hops (clap's benchmark command
|
|
13299
|
-
// has ~160). Keep a generous hard safety bound
|
|
13300
|
-
//
|
|
13301
|
-
//
|
|
13302
|
-
|
|
14475
|
+
// has ~160). Keep a generous hard safety bound. A cycle/depth refusal is
|
|
14476
|
+
// CONTEXTUAL (it depends on the visiting path), so any null whose subtree
|
|
14477
|
+
// tripped the bound must not be cached — caching it poisoned shorter
|
|
14478
|
+
// suffix queries later in the same operation. A null with NO trip in its
|
|
14479
|
+
// subtree is universal (the producer shape is untypeable regardless of
|
|
14480
|
+
// path) and IS cached: without that, an untypeable chain re-walks its
|
|
14481
|
+
// whole producer prefix per consumer (clap `unwrap`: 76s -> linear).
|
|
14482
|
+
if (ctx.visiting.has(record) || ctx.visiting.size > 256) {
|
|
14483
|
+
ctx.foldTrips = (ctx.foldTrips || 0) + 1;
|
|
14484
|
+
return null;
|
|
14485
|
+
}
|
|
13303
14486
|
ctx.visiting.add(record);
|
|
14487
|
+
const tripsBefore = ctx.foldTrips || 0;
|
|
13304
14488
|
let out;
|
|
13305
14489
|
try {
|
|
13306
14490
|
out = _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, consumerAwaited);
|
|
13307
14491
|
} finally {
|
|
13308
14492
|
ctx.visiting.delete(record);
|
|
13309
14493
|
}
|
|
13310
|
-
if (out) ctx.memo.set(record, out);
|
|
14494
|
+
if (out || (ctx.foldTrips || 0) === tripsBefore) ctx.memo.set(record, out ?? null);
|
|
13311
14495
|
return out;
|
|
13312
14496
|
}
|
|
13313
14497
|
|
|
@@ -13469,7 +14653,8 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
13469
14653
|
// to the one-hop project-wide agreement rule when the receiver stays
|
|
13470
14654
|
// untyped.
|
|
13471
14655
|
if (record.isMethod) {
|
|
13472
|
-
let rt =
|
|
14656
|
+
let rt = _goBuiltinChainedReceiverType(
|
|
14657
|
+
index, fileEntry, filePath, record);
|
|
13473
14658
|
if (record.receiverType && !record.receiverIsChainRoot) {
|
|
13474
14659
|
const origin = nominal
|
|
13475
14660
|
? _resolveFlowTypeOrigin(index, filePath, record.receiverType,
|
|
@@ -13870,4 +15055,4 @@ function findCallbackUsages(index, name) {
|
|
|
13870
15055
|
return usages;
|
|
13871
15056
|
}
|
|
13872
15057
|
|
|
13873
|
-
module.exports = { getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup };
|
|
15058
|
+
module.exports = { getCachedCalls, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable };
|