ucn 5.0.6 → 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 +12 -5
- package/.claude/skills/ucn/references/commands.md +2 -2
- package/.claude/skills/ucn/references/trust-contract.md +3 -2
- package/README.md +31 -14
- package/core/analysis.js +66 -1
- package/core/bridge.js +2 -1
- package/core/cache.js +64 -8
- package/core/callers.js +1722 -90
- package/core/graph-build.js +6 -0
- package/core/index-ir.js +16 -5
- package/core/ir.js +50 -3
- package/core/output/analysis.js +25 -0
- package/core/output/refactoring.js +1 -1
- package/core/output/shared.js +2 -2
- 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 +288 -21
- package/languages/python.js +443 -97
- package/languages/rust.js +87 -4
- package/languages/utils.js +11 -0
- package/mcp/server.js +100 -104
- package/mcp/stdio-server.js +296 -0
- package/package.json +10 -8
package/core/callers.js
CHANGED
|
@@ -148,6 +148,54 @@ function _javaPackageKey(relativePath) {
|
|
|
148
148
|
return path.posix.dirname(packagePath);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Resolve a capitalized Java receiver NAME through the file's static imports
|
|
153
|
+
* to a static FIELD's declared type (fix #286c, jsoup-measured: `import
|
|
154
|
+
* static ...SimpleBufferedInput.BufferPool` binds `BufferPool.borrow()` to
|
|
155
|
+
* the SoftPool-typed field — javac resolves single-static-imports ahead of
|
|
156
|
+
* the capitalized static-call reading). Returns the declared type HEAD, or
|
|
157
|
+
* null when the name isn't a static-imported field (a static import naming a
|
|
158
|
+
* nested TYPE keeps type-qualified semantics), the field has no usable
|
|
159
|
+
* declared type, or the type head is package-qualified (unpinnable here —
|
|
160
|
+
* the #268(4a) qualified-field discipline).
|
|
161
|
+
*/
|
|
162
|
+
function _javaStaticImportedFieldType(index, fileEntry, receiverName) {
|
|
163
|
+
if (!receiverName || !fileEntry) return null;
|
|
164
|
+
// Single-static-imports only: the import record cannot distinguish a
|
|
165
|
+
// STATIC wildcard (`import static Owner.*` — imports fields) from a
|
|
166
|
+
// package wildcard (`import com.ex.*` — types only), and typing a
|
|
167
|
+
// receiver from a field javac never binds could exclude a true edge.
|
|
168
|
+
const candidates = [];
|
|
169
|
+
for (const im of (fileEntry.importBindings || [])) {
|
|
170
|
+
const mod = String(im.module || '');
|
|
171
|
+
if (im.name === receiverName && mod.endsWith('.' + receiverName)) {
|
|
172
|
+
const rel = fileEntry.moduleResolved && fileEntry.moduleResolved[mod];
|
|
173
|
+
if (rel) candidates.push(rel);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const rel of candidates) {
|
|
178
|
+
const entry = index.files.get(path.join(index.root, rel));
|
|
179
|
+
if (!entry) continue;
|
|
180
|
+
// The import binding shape also matches plain class imports
|
|
181
|
+
// (`import com.ex.BufferPool`): any resolved TYPE of the name —
|
|
182
|
+
// top-level or nested — keeps static-call semantics.
|
|
183
|
+
const namedType = (entry.symbols || []).some(s =>
|
|
184
|
+
s.name === receiverName &&
|
|
185
|
+
['class', 'interface', 'enum', 'record', 'annotation'].includes(s.type));
|
|
186
|
+
if (namedType) return null;
|
|
187
|
+
const field = (entry.symbols || []).find(s =>
|
|
188
|
+
s.name === receiverName && s.className &&
|
|
189
|
+
(s.type === 'field' || s.memberType === 'field') &&
|
|
190
|
+
(s.modifiers || []).includes('static'));
|
|
191
|
+
if (!field) continue;
|
|
192
|
+
const head = String(field.fieldType || '').replace(/<[\s\S]*$/, '').trim();
|
|
193
|
+
if (!head || head.includes('.') || head.endsWith('[]')) return null;
|
|
194
|
+
return head;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
151
199
|
function _javaConstructorDisposition(index, filePath, fileEntry, call, targetDefs) {
|
|
152
200
|
const typeKinds = new Set(['class', 'record', 'enum']);
|
|
153
201
|
const targets = (targetDefs || []).filter(d => typeKinds.has(d.type));
|
|
@@ -271,6 +319,8 @@ function findCallers(index, name, options = {}) {
|
|
|
271
319
|
const typeQueryTargets = options.targetDefinitions || definitions;
|
|
272
320
|
const targetIsTypeQuery = typeQueryTargets.length > 0 &&
|
|
273
321
|
typeQueryTargets.every(target => IDENTITY_TYPE_KINDS.has(target.type));
|
|
322
|
+
const targetDefinitionFiles = new Set(
|
|
323
|
+
typeQueryTargets.map(definition => definition.file).filter(Boolean));
|
|
274
324
|
|
|
275
325
|
// Possible-dispatch tiering inputs (nominal contract surface) — all fixed
|
|
276
326
|
// per query, computed lazily once. targetTypes mirrors the receiver-class
|
|
@@ -435,7 +485,8 @@ function findCallers(index, name, options = {}) {
|
|
|
435
485
|
const moduleFile = path.join(index.root, rel);
|
|
436
486
|
if (!aliasTargetFiles.has(moduleFile)) continue;
|
|
437
487
|
const exported = index.files.get(moduleFile)?.exportDetails || [];
|
|
438
|
-
if (!exported.some(e => e.type === 'module.exports' &&
|
|
488
|
+
if (!exported.some(e => e.type === 'module.exports' &&
|
|
489
|
+
e.defaultLike && (e.localName || e.name) === name)) continue;
|
|
439
490
|
if (!importAliasLocals.has(fp)) importAliasLocals.set(fp, new Set());
|
|
440
491
|
importAliasLocals.get(fp).add(b.alias || b.name);
|
|
441
492
|
}
|
|
@@ -557,6 +608,8 @@ function findCallers(index, name, options = {}) {
|
|
|
557
608
|
try {
|
|
558
609
|
const calls = getCachedCalls(index, filePath);
|
|
559
610
|
if (!calls) continue;
|
|
611
|
+
const structuralLanguage =
|
|
612
|
+
langTraits(fileEntry.language)?.typeSystem === 'structural';
|
|
560
613
|
|
|
561
614
|
for (let call of calls) {
|
|
562
615
|
// Skip if not matching our target name (also check alias resolution)
|
|
@@ -607,6 +660,35 @@ function findCallers(index, name, options = {}) {
|
|
|
607
660
|
continue;
|
|
608
661
|
}
|
|
609
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
|
+
|
|
610
692
|
// Static member syntax carries two compiler symbols:
|
|
611
693
|
// `JValue.Compare(...)` calls Compare and references the
|
|
612
694
|
// JValue type. Class/type queries promise usages rather than
|
|
@@ -1127,8 +1209,10 @@ function findCallers(index, name, options = {}) {
|
|
|
1127
1209
|
} };
|
|
1128
1210
|
foldCtxCache.set(filePath, foldCtx);
|
|
1129
1211
|
}
|
|
1130
|
-
const flowEntry =
|
|
1131
|
-
index, fileEntry, filePath, call
|
|
1212
|
+
const flowEntry = _goBuiltinChainedReceiverType(
|
|
1213
|
+
index, fileEntry, filePath, call) ||
|
|
1214
|
+
_foldChainedReceiverType(
|
|
1215
|
+
index, fileEntry, filePath, call, foldCtx) ||
|
|
1132
1216
|
_nominalChainedReceiverType(
|
|
1133
1217
|
index, call, fileEntry, filePath);
|
|
1134
1218
|
if (flowEntry && flowEntry.externalVia) {
|
|
@@ -1359,7 +1443,50 @@ function findCallers(index, name, options = {}) {
|
|
|
1359
1443
|
if (td.className) targetTypes.add(td.className);
|
|
1360
1444
|
if (td.receiver) targetTypes.add(td.receiver.replace(/^\*/, ''));
|
|
1361
1445
|
}
|
|
1362
|
-
|
|
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 &&
|
|
1363
1490
|
!targetTypes.has(call.receiverType)) {
|
|
1364
1491
|
// Raw-set mismatch — check the CLOSED set (aliases +
|
|
1365
1492
|
// non-overriding subtypes incl. Go embedding) before
|
|
@@ -1792,6 +1919,21 @@ function findCallers(index, name, options = {}) {
|
|
|
1792
1919
|
options.targetDefinitions || definitions);
|
|
1793
1920
|
if (resolvedByExtensionMethod) receiverTypeValidated = true;
|
|
1794
1921
|
}
|
|
1922
|
+
// A static-imported FIELD shadows the capitalized static-
|
|
1923
|
+
// qualifier reading (fix #286c, jsoup-measured): javac binds
|
|
1924
|
+
// `BufferPool.borrow()` to the SoftPool-typed field imported
|
|
1925
|
+
// by `import static ...SimpleBufferedInput.BufferPool`, never
|
|
1926
|
+
// to a class named BufferPool. The declared type is compiler-
|
|
1927
|
+
// true — feed it to the normal nominal receiver physics.
|
|
1928
|
+
if (call.isMethod && !call.receiverType && call.receiver &&
|
|
1929
|
+
fileEntry.language === 'java' && /^[A-Z]/.test(call.receiver)) {
|
|
1930
|
+
const staticFieldType = _javaStaticImportedFieldType(
|
|
1931
|
+
index, fileEntry, call.receiver);
|
|
1932
|
+
if (staticFieldType) {
|
|
1933
|
+
call = { ...call, receiverType: staticFieldType,
|
|
1934
|
+
receiverIsTypeQualified: false };
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1795
1937
|
if (collectAccount && call.isMethod && !call.receiverType &&
|
|
1796
1938
|
fileEntry.language === 'java' && /^[A-Z]/.test(call.receiver || '')) {
|
|
1797
1939
|
// Java permits inherited static methods to be invoked
|
|
@@ -2242,6 +2384,16 @@ function findCallers(index, name, options = {}) {
|
|
|
2242
2384
|
}
|
|
2243
2385
|
}
|
|
2244
2386
|
|
|
2387
|
+
// Resolve this before the generic untyped-field guard below:
|
|
2388
|
+
// `api.nested.run()` is syntactically a field receiver, but an
|
|
2389
|
+
// exported namespace chain can make both hops compiler-exact.
|
|
2390
|
+
const recvExportedNamespace = (!call.receiverIsModule && call.isMethod &&
|
|
2391
|
+
(call.receiver || call.receiverRoot) &&
|
|
2392
|
+
structuralLanguage)
|
|
2393
|
+
? _importedNamespaceMemberOwnership(
|
|
2394
|
+
index, fileEntry, call, targetDefinitionFiles)
|
|
2395
|
+
: null;
|
|
2396
|
+
|
|
2245
2397
|
// Go package-owned value receiver (`io.Discard.Write`). The
|
|
2246
2398
|
// imported package owns `Discard`; file/package method-name
|
|
2247
2399
|
// bindings cannot identify its concrete type. Keep the edge
|
|
@@ -2263,7 +2415,7 @@ function findCallers(index, name, options = {}) {
|
|
|
2263
2415
|
// allowing a same-file `query` definition to claim it.
|
|
2264
2416
|
if (collectAccount && call.isMethod && call.receiverField &&
|
|
2265
2417
|
!call.receiverType && !fieldHopType && !fieldDispatchType &&
|
|
2266
|
-
!resolvedBySameClass &&
|
|
2418
|
+
!resolvedBySameClass && !recvExportedNamespace &&
|
|
2267
2419
|
['javascript', 'typescript', 'tsx', 'html'].includes(
|
|
2268
2420
|
fileEntry.language)) {
|
|
2269
2421
|
routeUnverified(
|
|
@@ -2383,15 +2535,27 @@ function findCallers(index, name, options = {}) {
|
|
|
2383
2535
|
}
|
|
2384
2536
|
const inherited = _isAncestorOfTargetClass(
|
|
2385
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));
|
|
2386
2544
|
// A derived same-name method hides inherited
|
|
2387
2545
|
// overloads until it proves inapplicable.
|
|
2388
2546
|
// Receiver-blind bindings may therefore be
|
|
2389
2547
|
// overruled only when static argument evidence
|
|
2390
2548
|
// accepts this inherited target and rejects the
|
|
2391
|
-
// 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).
|
|
2392
2555
|
return inherited &&
|
|
2393
2556
|
_overloadApplicable(index, call, d) &&
|
|
2394
|
-
!_overloadApplicable(index, call, boundDef)
|
|
2557
|
+
!_overloadApplicable(index, call, boundDef) &&
|
|
2558
|
+
!derivedApplicableSibling;
|
|
2395
2559
|
})) ||
|
|
2396
2560
|
(fileEntry.language === 'cpp' &&
|
|
2397
2561
|
!boundDef.className && !boundDef.receiver &&
|
|
@@ -2553,7 +2717,9 @@ function findCallers(index, name, options = {}) {
|
|
|
2553
2717
|
continue;
|
|
2554
2718
|
}
|
|
2555
2719
|
const resolvedAbs = path.join(index.root, rel);
|
|
2556
|
-
const verdict =
|
|
2720
|
+
const verdict = b.defaultLike
|
|
2721
|
+
? _defaultBindingReaches(index, resolvedAbs, tFiles)
|
|
2722
|
+
: _nameBindingReaches(index, resolvedAbs, b.name, tFiles);
|
|
2557
2723
|
if (verdict === 'yes') { reaches = true; break; }
|
|
2558
2724
|
if (verdict === 'unknown') undetermined = true;
|
|
2559
2725
|
}
|
|
@@ -2731,12 +2897,18 @@ function findCallers(index, name, options = {}) {
|
|
|
2731
2897
|
const recvSubmoduleRel = (!call.receiverIsModule && call.isMethod && call.receiver &&
|
|
2732
2898
|
langTraits(fileEntry.language)?.typeSystem === 'structural')
|
|
2733
2899
|
? _submoduleReceiverModule(index, fileEntry, call.receiver) : null;
|
|
2900
|
+
// A named/default import can itself be an exported namespace
|
|
2901
|
+
// object: `import { z } from './index'; z.string()`, where
|
|
2902
|
+
// index does `import * as z from './api'; export { z }`.
|
|
2903
|
+
// Preserve that compiler-exact namespace identity without
|
|
2904
|
+
// treating arbitrary imported object values as modules.
|
|
2734
2905
|
|
|
2735
2906
|
// Module receiver: httpx.get() / ns.helper() dispatches to a
|
|
2736
2907
|
// module export — it can never be a CLASS METHOD call. Applies
|
|
2737
2908
|
// only when every target is a class method; standalone-function
|
|
2738
2909
|
// and class (constructor) targets keep flowing on import evidence.
|
|
2739
|
-
if (!bindingId && !resolvedBySameClass && call.isMethod &&
|
|
2910
|
+
if ((!bindingId || recvExportedNamespace) && !resolvedBySameClass && call.isMethod &&
|
|
2911
|
+
(call.receiverIsModule || recvExportedNamespace) &&
|
|
2740
2912
|
langTraits(fileEntry.language)?.typeSystem === 'structural' &&
|
|
2741
2913
|
targetDefs.length > 0 && targetDefs.every(d => d.className)) {
|
|
2742
2914
|
isUncertain = true;
|
|
@@ -2758,30 +2930,54 @@ function findCallers(index, name, options = {}) {
|
|
|
2758
2930
|
// binding (name-level — the file importing the target for
|
|
2759
2931
|
// other names proves nothing): binding module external →
|
|
2760
2932
|
// excluded; resolves to a project file that doesn't reach a
|
|
2761
|
-
// target
|
|
2762
|
-
//
|
|
2933
|
+
// target by a definitive name-level `no` → excluded as another
|
|
2934
|
+
// definition; an unknown chain stays visible (deep barrels and
|
|
2935
|
+
// dynamic CJS surfaces can exceed the modeled ownership);
|
|
2763
2936
|
// unresolved-but-project-looking → visible (resolver gap).
|
|
2764
|
-
if (!bindingId && !resolvedBySameClass && call.isMethod &&
|
|
2765
|
-
(call.receiverIsModule || recvSubmoduleRel) &&
|
|
2937
|
+
if ((!bindingId || recvExportedNamespace) && !resolvedBySameClass && call.isMethod &&
|
|
2938
|
+
(call.receiverIsModule || recvSubmoduleRel || recvExportedNamespace) &&
|
|
2766
2939
|
langTraits(fileEntry.language)?.typeSystem === 'structural') {
|
|
2767
|
-
const recvBindings =
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2940
|
+
const recvBindings = recvExportedNamespace
|
|
2941
|
+
? [] : _structuralModuleBindings(fileEntry, call);
|
|
2942
|
+
const tFiles = targetDefinitionFiles;
|
|
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) {
|
|
2952
|
+
let reaches = recvExportedNamespace?.verdict === 'yes';
|
|
2953
|
+
let projectish = !!recvExportedNamespace;
|
|
2954
|
+
let undetermined = recvExportedNamespace?.verdict === 'unknown';
|
|
2955
|
+
let resolvedBindings = recvExportedNamespace ? 1 : 0;
|
|
2956
|
+
let definitiveOtherBindings = recvExportedNamespace?.verdict === 'no' ? 1 : 0;
|
|
2772
2957
|
for (const b of recvBindings) {
|
|
2773
|
-
|
|
2774
|
-
|
|
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]);
|
|
2775
2969
|
if (!rel) {
|
|
2776
2970
|
const mod = String(b.module);
|
|
2777
2971
|
const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
|
|
2778
2972
|
if (mod.startsWith('.') ||
|
|
2779
2973
|
(firstSeg && _projectTopLevelNames(index).has(firstSeg))) {
|
|
2780
2974
|
projectish = true;
|
|
2975
|
+
undetermined = true;
|
|
2781
2976
|
}
|
|
2782
2977
|
continue;
|
|
2783
2978
|
}
|
|
2784
2979
|
projectish = true;
|
|
2980
|
+
resolvedBindings++;
|
|
2785
2981
|
const resolvedAbs = path.join(index.root, rel);
|
|
2786
2982
|
// Name-level ownership (fix #217 applied to module
|
|
2787
2983
|
// receivers — zod family D): `z._default(...)` asks
|
|
@@ -2791,17 +2987,29 @@ function findCallers(index, name, options = {}) {
|
|
|
2791
2987
|
// chase is definitive only on fully-modeled ESM/
|
|
2792
2988
|
// Python surfaces — 'unknown' (CJS, stars, module
|
|
2793
2989
|
// assignments) falls back to file-level reach.
|
|
2794
|
-
const
|
|
2990
|
+
const ambiguousCjsMember =
|
|
2991
|
+
_cjsMemberOwnershipAmbiguous(index, resolvedAbs, call.name);
|
|
2992
|
+
const verdict = ambiguousCjsMember
|
|
2993
|
+
? 'unknown'
|
|
2994
|
+
: _nameBindingReaches(index, resolvedAbs, call.name, tFiles);
|
|
2795
2995
|
if (verdict === 'yes' ||
|
|
2796
|
-
(verdict === 'unknown' &&
|
|
2996
|
+
(verdict === 'unknown' && !ambiguousCjsMember &&
|
|
2997
|
+
_importReaches(index, resolvedAbs, tFiles))) {
|
|
2797
2998
|
reaches = true; break;
|
|
2798
2999
|
}
|
|
3000
|
+
if (verdict === 'no') definitiveOtherBindings++;
|
|
3001
|
+
else undetermined = true;
|
|
2799
3002
|
}
|
|
2800
3003
|
if (!reaches) {
|
|
2801
3004
|
if (!projectish) {
|
|
2802
3005
|
recordExcluded(filePath, call.line, 'external-package');
|
|
2803
3006
|
continue;
|
|
2804
3007
|
}
|
|
3008
|
+
if (!undetermined && resolvedBindings > 0 &&
|
|
3009
|
+
definitiveOtherBindings === resolvedBindings) {
|
|
3010
|
+
recordExcluded(filePath, call.line, 'other-definition-import');
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
2805
3013
|
if (collectAccount) {
|
|
2806
3014
|
routeUnverified(filePath, fileEntry, call, 'no-import-link', calledAs);
|
|
2807
3015
|
continue;
|
|
@@ -2979,7 +3187,8 @@ function findCallers(index, name, options = {}) {
|
|
|
2979
3187
|
}
|
|
2980
3188
|
if (knownType && !BUILTIN_RECEIVER_TYPES.has(knownType) &&
|
|
2981
3189
|
_isGenericParamReceiverType(index, filePath, call.line, knownType)) {
|
|
2982
|
-
knownType =
|
|
3190
|
+
knownType = _genericParamTraitTarget(
|
|
3191
|
+
index, filePath, call.line, knownType, targetDefs);
|
|
2983
3192
|
}
|
|
2984
3193
|
if (knownType) {
|
|
2985
3194
|
const explicitInterfaceTarget = fileEntry.language === 'csharp' &&
|
|
@@ -3077,7 +3286,15 @@ function findCallers(index, name, options = {}) {
|
|
|
3077
3286
|
if (sameNameTypeDefs.length > 1 ||
|
|
3078
3287
|
(call.receiverTypeQualifier && call.receiverTypeFlowFile)) {
|
|
3079
3288
|
const identity = _resolveStructuralFlowTypeIdentity(
|
|
3080
|
-
index, call.receiverTypeFlowFile || filePath, knownType, targetDefs
|
|
3289
|
+
index, call.receiverTypeFlowFile || filePath, knownType, targetDefs,
|
|
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
|
+
});
|
|
3081
3298
|
if (identity === 'other') {
|
|
3082
3299
|
receiverTypeValidated = false;
|
|
3083
3300
|
} else if (identity === 'unknown') {
|
|
@@ -3093,7 +3310,15 @@ function findCallers(index, name, options = {}) {
|
|
|
3093
3310
|
// exact declaration and walk its file-scoped
|
|
3094
3311
|
// ancestry before treating it as unrelated.
|
|
3095
3312
|
const identity = _resolveStructuralFlowTypeIdentity(
|
|
3096
|
-
index, call.receiverTypeFlowFile || filePath, knownType, targetDefs
|
|
3313
|
+
index, call.receiverTypeFlowFile || filePath, knownType, targetDefs,
|
|
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
|
+
});
|
|
3097
3322
|
if (identity === 'target') {
|
|
3098
3323
|
receiverTypeValidated = true;
|
|
3099
3324
|
} else if (identity === 'unknown') {
|
|
@@ -3387,6 +3612,31 @@ function findCallers(index, name, options = {}) {
|
|
|
3387
3612
|
// resolution above owns it.
|
|
3388
3613
|
if (call.isPathCall && /^[A-Z]/.test(receiverSegment) &&
|
|
3389
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
|
+
}
|
|
3390
3640
|
isUncertain = true;
|
|
3391
3641
|
typeMismatch = true;
|
|
3392
3642
|
if (collectAccount) {
|
|
@@ -3403,7 +3653,9 @@ function findCallers(index, name, options = {}) {
|
|
|
3403
3653
|
const t = d.className || (d.receiver && d.receiver.replace(/^\*/, ''));
|
|
3404
3654
|
if (t && !targetTypes.has(t)) nonTargetClasses.add(t);
|
|
3405
3655
|
}
|
|
3406
|
-
const
|
|
3656
|
+
const matchesOtherExact = [...nonTargetClasses].some(cn => cn === receiverSegment);
|
|
3657
|
+
const matchesOther = matchesOtherExact ||
|
|
3658
|
+
[...nonTargetClasses].some(cn => cn.toLowerCase() === receiverLower);
|
|
3407
3659
|
if (matchesOther) {
|
|
3408
3660
|
isUncertain = true;
|
|
3409
3661
|
typeMismatch = true;
|
|
@@ -3423,6 +3675,21 @@ function findCallers(index, name, options = {}) {
|
|
|
3423
3675
|
});
|
|
3424
3676
|
continue;
|
|
3425
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
|
+
}
|
|
3426
3693
|
recordExcluded(filePath, call.line, 'receiver-other-class');
|
|
3427
3694
|
continue;
|
|
3428
3695
|
}
|
|
@@ -3534,12 +3801,21 @@ function findCallers(index, name, options = {}) {
|
|
|
3534
3801
|
const targetDefs2 = options.targetDefinitions || definitions;
|
|
3535
3802
|
const targetFiles2 = new Set(targetDefs2.map(d => d.file).filter(Boolean));
|
|
3536
3803
|
const callerImports = index.importGraph.get(filePath);
|
|
3537
|
-
|
|
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)));
|
|
3538
3812
|
// Check one level of re-exports (barrel files) for import evidence
|
|
3539
3813
|
if (!importEdgeLink && callerImports) {
|
|
3540
3814
|
for (const imp of callerImports) {
|
|
3815
|
+
if (imp === filePath) continue;
|
|
3541
3816
|
const transImports = index.importGraph.get(imp);
|
|
3542
|
-
if (transImports && setSome(transImports,
|
|
3817
|
+
if (transImports && setSome(transImports,
|
|
3818
|
+
ti => ti !== filePath && targetFiles2.has(ti))) {
|
|
3543
3819
|
importEdgeLink = true;
|
|
3544
3820
|
break;
|
|
3545
3821
|
}
|
|
@@ -3735,6 +4011,21 @@ function findCallers(index, name, options = {}) {
|
|
|
3735
4011
|
});
|
|
3736
4012
|
continue;
|
|
3737
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
|
+
}
|
|
3738
4029
|
recordExcluded(filePath, call.line, 'path-type-mismatch');
|
|
3739
4030
|
continue;
|
|
3740
4031
|
}
|
|
@@ -4073,7 +4364,8 @@ function findCallers(index, name, options = {}) {
|
|
|
4073
4364
|
// module-ownership block above already routed the ones
|
|
4074
4365
|
// whose module doesn't reach the target. Submodule
|
|
4075
4366
|
// receivers (fix #224) are module receivers too.
|
|
4076
|
-
if (call.isMethod && !call.receiverIsModule &&
|
|
4367
|
+
if (call.isMethod && !call.receiverIsModule &&
|
|
4368
|
+
!recvSubmoduleRel && !call.moduleOwnedPath) {
|
|
4077
4369
|
const tTypes = dispatchTargetTypes(targetDefs2);
|
|
4078
4370
|
const typeQualifiedReceiver = !!(call.receiver && tTypes.has(call.receiver));
|
|
4079
4371
|
const knownDispatchType = call.receiverType ||
|
|
@@ -4142,6 +4434,42 @@ function findCallers(index, name, options = {}) {
|
|
|
4142
4434
|
});
|
|
4143
4435
|
continue;
|
|
4144
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
|
+
}
|
|
4145
4473
|
// Unshadowed builtin-global receiver (fix #232):
|
|
4146
4474
|
// JSON.parse/console.log resolve on the host object,
|
|
4147
4475
|
// never a same-named project method. A lexical binding
|
|
@@ -4153,6 +4481,22 @@ function findCallers(index, name, options = {}) {
|
|
|
4153
4481
|
(index.symbols.get(call.receiver) || []).length === 0 &&
|
|
4154
4482
|
!fileEntry.bindings?.some(b => b.name === call.receiver) &&
|
|
4155
4483
|
!call.receiverMemberAssigned) {
|
|
4484
|
+
// A candidate target that IS a member assignment
|
|
4485
|
+
// onto this same global (fix #286a, fastify-
|
|
4486
|
+
// measured: `console.log = () => {}` in a test's
|
|
4487
|
+
// beforeEach) patches the host object process-
|
|
4488
|
+
// wide — the assignment's own file cannot scope
|
|
4489
|
+
// it, so every `console.log(...)` site MAY
|
|
4490
|
+
// dispatch into the project def. Demote-only:
|
|
4491
|
+
// visible possible-dispatch, never confirmed,
|
|
4492
|
+
// never excluded.
|
|
4493
|
+
if (targetDefs2.some(d => d.memberAssigned &&
|
|
4494
|
+
d.assignedReceiver === call.receiver)) {
|
|
4495
|
+
routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
|
|
4496
|
+
dispatchVia: `${call.receiver} — builtin global patched in project`,
|
|
4497
|
+
});
|
|
4498
|
+
continue;
|
|
4499
|
+
}
|
|
4156
4500
|
recordExcluded(filePath, call.line, 'external-package');
|
|
4157
4501
|
continue;
|
|
4158
4502
|
}
|
|
@@ -4802,7 +5146,7 @@ function findCallees(index, definition, options = {}) {
|
|
|
4802
5146
|
};
|
|
4803
5147
|
|
|
4804
5148
|
let siteOrdinal = -1;
|
|
4805
|
-
for (
|
|
5149
|
+
for (let call of calls) {
|
|
4806
5150
|
siteOrdinal++;
|
|
4807
5151
|
const siteId = siteOrdinal;
|
|
4808
5152
|
// Filter to calls within this function's scope
|
|
@@ -4824,6 +5168,30 @@ function findCallees(index, definition, options = {}) {
|
|
|
4824
5168
|
continue;
|
|
4825
5169
|
}
|
|
4826
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
|
+
|
|
4827
5195
|
// C# extension methods are statically resolved compiler calls,
|
|
4828
5196
|
// despite using instance-call syntax. Resolve them before the
|
|
4829
5197
|
// ordinary receiver-owner path: the receiver type matches the
|
|
@@ -4879,11 +5247,15 @@ function findCallees(index, definition, options = {}) {
|
|
|
4879
5247
|
|
|
4880
5248
|
// Parser-proven lexical shadow: a parameter/local named `option`
|
|
4881
5249
|
// cannot call an imported/project `option`. A nested local
|
|
4882
|
-
// function
|
|
4883
|
-
//
|
|
5250
|
+
// function — or a nested CLASS, whose bare name constructs it
|
|
5251
|
+
// (fix #286f, flask-measured: `class Foo` inside test_custom_tag,
|
|
5252
|
+
// `Foo("bar")` excluded local-shadow while the oracle pins the
|
|
5253
|
+
// nested class) — is the safe exception and remains eligible for
|
|
5254
|
+
// exact same-file resolution below.
|
|
4884
5255
|
if (call.localShadow && !call.resolvedName && !call.resolvedNames) {
|
|
4885
5256
|
const localTarget = (index.symbols.get(call.name) || []).some(s =>
|
|
4886
|
-
s.file === def.file &&
|
|
5257
|
+
s.file === def.file &&
|
|
5258
|
+
(s.type === 'class' || !NON_CALLABLE_TYPES.has(s.type)) &&
|
|
4887
5259
|
s.startLine >= def.startLine && s.endLine <= def.endLine &&
|
|
4888
5260
|
s.startLine <= call.line);
|
|
4889
5261
|
if (!localTarget) {
|
|
@@ -4899,6 +5271,18 @@ function findCallees(index, definition, options = {}) {
|
|
|
4899
5271
|
const directReceiverFlow = mayNeedDirectReceiverFlow(call)
|
|
4900
5272
|
? _lookupReturnTypeFlow(flowMap(), call)
|
|
4901
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
|
+
}
|
|
4902
5286
|
|
|
4903
5287
|
// Declared-field receiver hop (fix #231 — callee-side parity
|
|
4904
5288
|
// with the caller side's #202/#219): `tm.service.Save()` /
|
|
@@ -5551,8 +5935,18 @@ function findCallees(index, definition, options = {}) {
|
|
|
5551
5935
|
const shadowsBuiltin = !call.receiver && (
|
|
5552
5936
|
fileEntry?.importBindings?.some(b => b.name === call.name) ||
|
|
5553
5937
|
fileEntry?.bindings?.some(b => b.name === call.name));
|
|
5554
|
-
|
|
5555
|
-
|
|
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)) {
|
|
5556
5950
|
noteSite(siteId, 'external', null, call);
|
|
5557
5951
|
continue;
|
|
5558
5952
|
}
|
|
@@ -5661,8 +6055,17 @@ function findCallees(index, definition, options = {}) {
|
|
|
5661
6055
|
// name through the same #217 export-chain discipline used for
|
|
5662
6056
|
// module receivers. Only intercept when an explicit binding of
|
|
5663
6057
|
// this name exists; ordinary locals continue to lexical binding.
|
|
5664
|
-
|
|
5665
|
-
|
|
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) {
|
|
5666
6069
|
const importRoute = _calleeStructuralImportedNameRoute(index, fileEntry, call, language);
|
|
5667
6070
|
if (importRoute) {
|
|
5668
6071
|
if (importRoute.matches?.length) {
|
|
@@ -5692,6 +6095,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
5692
6095
|
let isUncertain = call.uncertain;
|
|
5693
6096
|
let uncertainReason = null; // account-mode reason for the unverified bucket
|
|
5694
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;
|
|
5695
6104
|
if (!call.bindingId && language === 'cpp' &&
|
|
5696
6105
|
!call.isMethod && !call.receiver && !call.isConstructor) {
|
|
5697
6106
|
// C++ namespace/free-function lookup is declaration-order
|
|
@@ -5764,6 +6173,16 @@ function findCallees(index, definition, options = {}) {
|
|
|
5764
6173
|
if (call.isConstructor) {
|
|
5765
6174
|
bindings = bindings.filter(binding => binding.type !== 'field');
|
|
5766
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
|
+
}
|
|
5767
6186
|
// Method call with no binding for the method name:
|
|
5768
6187
|
// Different strategies by language family:
|
|
5769
6188
|
if (bindings.length === 0 && call.isMethod) {
|
|
@@ -5775,7 +6194,12 @@ function findCallees(index, definition, options = {}) {
|
|
|
5775
6194
|
// CacheService.set. Builtin-typed receivers are host calls;
|
|
5776
6195
|
// a receiver typed to a project class resolves to that class
|
|
5777
6196
|
// (or an ancestor defining the method), never by bare name.
|
|
5778
|
-
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
|
+
});
|
|
5779
6203
|
if (route?.external) {
|
|
5780
6204
|
noteSite(siteId, 'external', null, call);
|
|
5781
6205
|
continue;
|
|
@@ -6170,6 +6594,10 @@ function findCallees(index, definition, options = {}) {
|
|
|
6170
6594
|
name: effectiveName,
|
|
6171
6595
|
bindingId: bindingResolved,
|
|
6172
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 }),
|
|
6173
6601
|
...(call.isConstructor && { isConstructor: true }),
|
|
6174
6602
|
...(collectAccount && {
|
|
6175
6603
|
sites: [call.line],
|
|
@@ -6356,14 +6784,24 @@ function findCallees(index, definition, options = {}) {
|
|
|
6356
6784
|
const cFamilyVisibleFiles = ['c', 'cpp'].includes(language)
|
|
6357
6785
|
? _cppVisibleFiles(index, def.file) : null;
|
|
6358
6786
|
|
|
6359
|
-
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()) {
|
|
6360
6788
|
const claimSites = (bucket, reason) => {
|
|
6361
6789
|
if (!collectAccount || !siteIds) return;
|
|
6362
6790
|
for (let i = 0; i < siteIds.length; i++) {
|
|
6363
6791
|
noteSite(siteIds[i], bucket, reason, { name: calleeName, line: sites[i] });
|
|
6364
6792
|
}
|
|
6365
6793
|
};
|
|
6366
|
-
|
|
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
|
+
}
|
|
6367
6805
|
if (!symbols || symbols.length === 0) {
|
|
6368
6806
|
// Name not in the symbol table — external library, builtin, or
|
|
6369
6807
|
// unindexed code. Visible in the callee account, not an edge.
|
|
@@ -6793,6 +7231,22 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
6793
7231
|
};
|
|
6794
7232
|
for (const call of calls) {
|
|
6795
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
|
+
}
|
|
6796
7250
|
const delegatedUnwrapAssignment = language === 'rust' &&
|
|
6797
7251
|
call.receiverCall && calls.some(candidate =>
|
|
6798
7252
|
candidate !== call &&
|
|
@@ -6889,7 +7343,13 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
6889
7343
|
let returnType, fromFile, selfClass, returnedFunctionResult, returnDefinition;
|
|
6890
7344
|
const builtinCallReturn = !nominal && language === 'python'
|
|
6891
7345
|
? _pythonBuiltinCallReturnType(index, fileEntry, call) : null;
|
|
6892
|
-
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) {
|
|
6893
7353
|
returnType = builtinCallReturn;
|
|
6894
7354
|
} else if (!nominal && language === 'python' && !call.isMethod &&
|
|
6895
7355
|
!call.receiver && call.name === 'open' && !call.localShadow &&
|
|
@@ -7358,11 +7818,63 @@ function _buildReturnTypeFlowMap(index, filePath, calls) {
|
|
|
7358
7818
|
});
|
|
7359
7819
|
continue;
|
|
7360
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
|
+
|
|
7361
7872
|
let typeName, entryFromFile;
|
|
7362
7873
|
if (nominal) {
|
|
7363
7874
|
const parsed = _returnTypeNameNominal(returnType, language, {
|
|
7364
7875
|
unwrapped: call.assignedUnwrap,
|
|
7365
7876
|
tuple: call.assignedTuple,
|
|
7877
|
+
tupleIndex: call.assignedTupleIndex,
|
|
7366
7878
|
selfClass,
|
|
7367
7879
|
index,
|
|
7368
7880
|
originFile: fromFile || filePath,
|
|
@@ -7676,8 +8188,11 @@ function _returnTypeNameNominal(text, language, opts = {}) {
|
|
|
7676
8188
|
if (!opts.tuple) return undefined;
|
|
7677
8189
|
const inner = t.slice(1, -1);
|
|
7678
8190
|
if (inner.includes('func(') || inner.includes('func (')) return undefined;
|
|
7679
|
-
const
|
|
7680
|
-
|
|
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+/);
|
|
7681
8196
|
t = parts[parts.length - 1]; // named return `n int` → int
|
|
7682
8197
|
} else if (opts.tuple) {
|
|
7683
8198
|
return undefined; // v, err := f() needs a multi-return producer
|
|
@@ -7768,8 +8283,10 @@ function _rejectedNominalFlowVia(text, language, opts = {}) {
|
|
|
7768
8283
|
let raw = text.trim();
|
|
7769
8284
|
if (language === 'go' && raw.startsWith('(')) {
|
|
7770
8285
|
if (!opts.tuple) return null;
|
|
7771
|
-
const
|
|
7772
|
-
|
|
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() : '';
|
|
7773
8290
|
}
|
|
7774
8291
|
const norm = _normalizeFieldTypeName(raw, language);
|
|
7775
8292
|
if (!norm) return null;
|
|
@@ -7820,6 +8337,51 @@ function _rustBindingResolvedFiles(index, fileEntry, filePath, binding) {
|
|
|
7820
8337
|
return resolvedFiles;
|
|
7821
8338
|
}
|
|
7822
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
|
+
|
|
7823
8385
|
/**
|
|
7824
8386
|
* Pin a flow type name to its defining file from the PRODUCER's scope
|
|
7825
8387
|
* (fix #207 — the #206 identity lesson applied to annotations: `Builder` in
|
|
@@ -7834,7 +8396,7 @@ function _rustBindingResolvedFiles(index, fileEntry, filePath, binding) {
|
|
|
7834
8396
|
* trusted (a use/import of an external type can shadow it invisibly)
|
|
7835
8397
|
* - no project type def at all: external name — safe, can't conflate
|
|
7836
8398
|
*/
|
|
7837
|
-
function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier) {
|
|
8399
|
+
function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier = undefined) {
|
|
7838
8400
|
const opCache = index._opFlowTypeOriginCache;
|
|
7839
8401
|
const cacheKey = `${producerFile}\x00${typeName}\x00${qualifier || ''}`;
|
|
7840
8402
|
if (opCache?.has(cacheKey)) return opCache.get(cacheKey);
|
|
@@ -7898,6 +8460,36 @@ function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier) {
|
|
|
7898
8460
|
}
|
|
7899
8461
|
}
|
|
7900
8462
|
}
|
|
8463
|
+
// Structural module qualifier (fix #286e, flask-measured):
|
|
8464
|
+
// `app: flask.Flask` names the type through the imported module.
|
|
8465
|
+
// Resolve the module binding and pin the unique reachable
|
|
8466
|
+
// declaration — name-chase through modeled re-exports first, the
|
|
8467
|
+
// bounded file-level closure as fallback (the #277 rails). An
|
|
8468
|
+
// unresolvable qualifier is 'unknown', never proximity-guessed.
|
|
8469
|
+
if (fe && ['python', 'javascript', 'typescript', 'tsx'].includes(fe.language)) {
|
|
8470
|
+
const resolvedRels = new Set();
|
|
8471
|
+
const direct = fe.moduleResolved?.[qualifier];
|
|
8472
|
+
if (direct) resolvedRels.add(direct);
|
|
8473
|
+
for (const b of (fe.importBindings || [])) {
|
|
8474
|
+
if (b.name !== qualifier && b.alias !== qualifier) continue;
|
|
8475
|
+
const rel = fe.moduleResolved?.[b.module];
|
|
8476
|
+
if (rel) resolvedRels.add(rel);
|
|
8477
|
+
}
|
|
8478
|
+
for (const rel of resolvedRels) {
|
|
8479
|
+
const start = path.join(index.root, rel);
|
|
8480
|
+
const named = typeDefs.filter(d => d.file === start ||
|
|
8481
|
+
_nameBindingReaches(index, start, typeName, new Set([d.file])) === 'yes');
|
|
8482
|
+
if (new Set(named.map(d => d.file)).size === 1) {
|
|
8483
|
+
return finish({ fromFile: named[0].file });
|
|
8484
|
+
}
|
|
8485
|
+
const reachable = typeDefs.filter(d => d.file === start ||
|
|
8486
|
+
_importReaches(index, start, new Set([d.file])));
|
|
8487
|
+
if (new Set(reachable.map(d => d.file)).size === 1) {
|
|
8488
|
+
return finish({ fromFile: reachable[0].file });
|
|
8489
|
+
}
|
|
8490
|
+
}
|
|
8491
|
+
return finish(null);
|
|
8492
|
+
}
|
|
7901
8493
|
const inPkg = fe && _qualifiedProducerDefs(index, fe, qualifier, typeDefs);
|
|
7902
8494
|
if (inPkg && inPkg.length > 0 && new Set(inPkg.map(d => d.file)).size === 1) {
|
|
7903
8495
|
return finish({ fromFile: inPkg[0].file });
|
|
@@ -8276,12 +8868,24 @@ function _nameBindingReaches(index, startAbs, name, targetFiles, maxDepth = 4) {
|
|
|
8276
8868
|
// makes widgets.helper the local helper even if widgets imports a
|
|
8277
8869
|
// different helper several hops below. Parser-provided localName
|
|
8278
8870
|
// keeps this proof limited to syntactically-owned CJS exports;
|
|
8279
|
-
// dynamic assignments remain unknown
|
|
8871
|
+
// dynamic assignments remain unknown — including a COMPETING
|
|
8872
|
+
// record for the same name without static ownership
|
|
8873
|
+
// (`exports.run = run;` then `if (native) exports.run =
|
|
8874
|
+
// require('./native').run;`) and a whole-surface reassignment
|
|
8875
|
+
// (`module.exports = require('./impl')`): either one can rebind
|
|
8876
|
+
// the member at runtime, so the static record alone never proves
|
|
8877
|
+
// a dead end (fix #292).
|
|
8280
8878
|
const localExports = (fe.exportDetails || []).filter(e =>
|
|
8281
|
-
!e.source && (e.alias || e.name) === attr
|
|
8282
|
-
|
|
8283
|
-
.
|
|
8284
|
-
|
|
8879
|
+
!e.source && (e.alias || e.name) === attr);
|
|
8880
|
+
const staticOwner = localExports.some(e => e.localName &&
|
|
8881
|
+
(index.symbols.get(e.localName) || [])
|
|
8882
|
+
.some(definition => definition.file === abs &&
|
|
8883
|
+
!NON_CALLABLE_TYPES.has(definition.type)));
|
|
8884
|
+
const competingDynamic = localExports.some(e => !e.localName) ||
|
|
8885
|
+
(fe.exportDetails || []).some(e => !e.source &&
|
|
8886
|
+
e.defaultLike && e.type === 'module.exports' &&
|
|
8887
|
+
(e.alias || e.name) !== attr);
|
|
8888
|
+
if (staticOwner && !competingDynamic) {
|
|
8285
8889
|
return 'no';
|
|
8286
8890
|
}
|
|
8287
8891
|
|
|
@@ -8343,6 +8947,256 @@ function _nameBindingReaches(index, startAbs, name, targetFiles, maxDepth = 4) {
|
|
|
8343
8947
|
return unknown ? 'unknown' : 'no';
|
|
8344
8948
|
}
|
|
8345
8949
|
|
|
8950
|
+
/**
|
|
8951
|
+
* A CommonJS object export whose requested member has no statically-owned
|
|
8952
|
+
* local value cannot prove callable identity. For example,
|
|
8953
|
+
* `module.exports = { run: available ? nativeRun : fallback }` exposes the
|
|
8954
|
+
* local fallback only on one runtime branch. File-level import reachability
|
|
8955
|
+
* must not upgrade that conditional value to a confirmed edge.
|
|
8956
|
+
*/
|
|
8957
|
+
function _cjsMemberOwnershipAmbiguous(index, file, memberName) {
|
|
8958
|
+
const fe = index.files.get(file);
|
|
8959
|
+
// Both CJS assignment families count: `module.exports = { x: cond ? a : b }`
|
|
8960
|
+
// records type 'module.exports'; `exports.x = <dynamic>` records type
|
|
8961
|
+
// 'exports' (fix #292 — the sequential native/fallback feature-detect idiom).
|
|
8962
|
+
return !!fe && (fe.exportDetails || []).some(exp =>
|
|
8963
|
+
(exp.type === 'module.exports' || exp.type === 'exports') &&
|
|
8964
|
+
!exp.defaultLike && !exp.source &&
|
|
8965
|
+
(exp.alias || exp.name) === memberName && !exp.localName);
|
|
8966
|
+
}
|
|
8967
|
+
|
|
8968
|
+
/**
|
|
8969
|
+
* Resolve a member reached through an exported ESM namespace object.
|
|
8970
|
+
*
|
|
8971
|
+
* Supported exact shapes:
|
|
8972
|
+
* import * as api from './impl'; export { api };
|
|
8973
|
+
* import * as api from './impl'; export default api;
|
|
8974
|
+
* export * as api from './impl';
|
|
8975
|
+
* export { api } from './barrel'; // recursively, when api is one above
|
|
8976
|
+
*
|
|
8977
|
+
* A plain exported object/value is deliberately not recognized. The caller
|
|
8978
|
+
* must remain in the ordinary structural receiver tier unless the export path
|
|
8979
|
+
* proves that the value is a module namespace exotic object.
|
|
8980
|
+
*
|
|
8981
|
+
* @returns {{ verdict: 'yes'|'no'|'unknown' }|null}
|
|
8982
|
+
*/
|
|
8983
|
+
function _namespaceExportMemberReaches(
|
|
8984
|
+
index, startAbs, exportedName, memberName, targetFiles, maxDepth = 4,
|
|
8985
|
+
visited = new Set(), memberPath = []
|
|
8986
|
+
) {
|
|
8987
|
+
if (maxDepth < 0) return { verdict: 'unknown' };
|
|
8988
|
+
const stateKey = `${startAbs}\x00${exportedName}\x00${memberPath.join('.')}\x00${memberName}`;
|
|
8989
|
+
if (visited.has(stateKey)) return { verdict: 'unknown' };
|
|
8990
|
+
visited.add(stateKey);
|
|
8991
|
+
const fe = index.files.get(startAbs);
|
|
8992
|
+
if (!fe) return null;
|
|
8993
|
+
|
|
8994
|
+
let recognized = false;
|
|
8995
|
+
let unknown = false;
|
|
8996
|
+
let explicitExport = false;
|
|
8997
|
+
const followMember = moduleName => {
|
|
8998
|
+
const rel = fe.moduleResolved?.[moduleName];
|
|
8999
|
+
if (!rel) {
|
|
9000
|
+
unknown = true;
|
|
9001
|
+
return 'unknown';
|
|
9002
|
+
}
|
|
9003
|
+
const nextAbs = path.join(index.root, rel);
|
|
9004
|
+
if (memberPath.length > 0) {
|
|
9005
|
+
const nested = _namespaceExportMemberReaches(
|
|
9006
|
+
index, nextAbs, memberPath[0], memberName, targetFiles,
|
|
9007
|
+
maxDepth - 1, new Set(visited), memberPath.slice(1));
|
|
9008
|
+
// The outer value is proven to be a namespace, but its requested
|
|
9009
|
+
// field is an ordinary/unmodeled value rather than another proven
|
|
9010
|
+
// namespace. That is uncertainty, never exclusion evidence.
|
|
9011
|
+
return nested?.verdict || 'unknown';
|
|
9012
|
+
}
|
|
9013
|
+
return _nameBindingReaches(index, nextAbs, memberName, targetFiles, maxDepth - 1);
|
|
9014
|
+
};
|
|
9015
|
+
const absorb = verdict => {
|
|
9016
|
+
if (verdict === 'yes') return true;
|
|
9017
|
+
if (verdict === 'unknown') unknown = true;
|
|
9018
|
+
return false;
|
|
9019
|
+
};
|
|
9020
|
+
|
|
9021
|
+
for (const exp of (fe.exportDetails || [])) {
|
|
9022
|
+
// `export * as api from './impl'` is direct namespace identity.
|
|
9023
|
+
if (exp.type === 're-export-all' && exp.alias === exportedName) {
|
|
9024
|
+
explicitExport = true;
|
|
9025
|
+
recognized = true;
|
|
9026
|
+
if (absorb(followMember(exp.source))) return { verdict: 'yes' };
|
|
9027
|
+
continue;
|
|
9028
|
+
}
|
|
9029
|
+
|
|
9030
|
+
const exposed = exp.type === 'default' ? 'default' : (exp.alias || exp.name);
|
|
9031
|
+
if (exposed !== exportedName) continue;
|
|
9032
|
+
explicitExport = true;
|
|
9033
|
+
|
|
9034
|
+
// `export { api } from './barrel'`: the source-side value may itself
|
|
9035
|
+
// be a namespace export. Recurse under its source-side name.
|
|
9036
|
+
if (exp.type === 're-export' && exp.source) {
|
|
9037
|
+
const rel = fe.moduleResolved?.[exp.source];
|
|
9038
|
+
if (!rel) {
|
|
9039
|
+
unknown = true;
|
|
9040
|
+
recognized = true;
|
|
9041
|
+
continue;
|
|
9042
|
+
}
|
|
9043
|
+
const nested = _namespaceExportMemberReaches(
|
|
9044
|
+
index, path.join(index.root, rel), exp.name, memberName,
|
|
9045
|
+
targetFiles, maxDepth - 1, new Set(visited), memberPath);
|
|
9046
|
+
if (nested) {
|
|
9047
|
+
recognized = true;
|
|
9048
|
+
if (nested.verdict === 'yes') return { verdict: 'yes' };
|
|
9049
|
+
if (nested.verdict === 'unknown') unknown = true;
|
|
9050
|
+
}
|
|
9051
|
+
continue;
|
|
9052
|
+
}
|
|
9053
|
+
|
|
9054
|
+
// `import * as api ...; export { api }` / `export default api`.
|
|
9055
|
+
if (!exp.source && (exp.type === 'named' || exp.type === 'default')) {
|
|
9056
|
+
const localName = exp.name;
|
|
9057
|
+
const namespaceBindings = (fe.importBindings || []).filter(binding =>
|
|
9058
|
+
(binding.alias || binding.name) === localName && binding.kind === 'namespace');
|
|
9059
|
+
for (const binding of namespaceBindings) {
|
|
9060
|
+
recognized = true;
|
|
9061
|
+
if (absorb(followMember(binding.module))) return { verdict: 'yes' };
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
}
|
|
9065
|
+
|
|
9066
|
+
// `export * from './barrel'` forwards named namespace-object exports too.
|
|
9067
|
+
// Explicit local/named exports shadow star exports, so only chase stars
|
|
9068
|
+
// when this file has no explicit surface for the requested name. Default
|
|
9069
|
+
// is never forwarded by export-star.
|
|
9070
|
+
if (!explicitExport && exportedName !== 'default') {
|
|
9071
|
+
const starExports = (fe.exportDetails || []).filter(exp =>
|
|
9072
|
+
exp.type === 're-export-all' && !exp.alias && exp.source);
|
|
9073
|
+
const starVerdicts = [];
|
|
9074
|
+
for (const exp of starExports) {
|
|
9075
|
+
const rel = fe.moduleResolved?.[exp.source];
|
|
9076
|
+
if (!rel) {
|
|
9077
|
+
unknown = true;
|
|
9078
|
+
continue;
|
|
9079
|
+
}
|
|
9080
|
+
const nested = _namespaceExportMemberReaches(
|
|
9081
|
+
index, path.join(index.root, rel), exportedName, memberName,
|
|
9082
|
+
targetFiles, maxDepth - 1, new Set(visited), memberPath);
|
|
9083
|
+
if (!nested) continue;
|
|
9084
|
+
recognized = true;
|
|
9085
|
+
starVerdicts.push(nested.verdict);
|
|
9086
|
+
}
|
|
9087
|
+
if (starVerdicts.length > 0) {
|
|
9088
|
+
// With several export-star providers, another star may expose the
|
|
9089
|
+
// same name and make the ESM binding ambiguous. We currently do
|
|
9090
|
+
// not compute full `ResolveExport` sets, so multi-star barrels are
|
|
9091
|
+
// demotion-only even when one path reaches the target.
|
|
9092
|
+
if (starExports.length > 1) unknown = true;
|
|
9093
|
+
else if (starVerdicts.includes('yes')) return { verdict: 'yes' };
|
|
9094
|
+
if (starVerdicts.includes('unknown')) unknown = true;
|
|
9095
|
+
}
|
|
9096
|
+
}
|
|
9097
|
+
|
|
9098
|
+
if (!recognized) return null;
|
|
9099
|
+
return { verdict: unknown ? 'unknown' : 'no' };
|
|
9100
|
+
}
|
|
9101
|
+
|
|
9102
|
+
/**
|
|
9103
|
+
* Determine whether a structural receiver imported by name/default is a
|
|
9104
|
+
* statically exported namespace object, and if so whether its requested member
|
|
9105
|
+
* can reach the pinned target files. Multiple live bindings must agree before
|
|
9106
|
+
* a negative becomes exclusion-grade.
|
|
9107
|
+
*/
|
|
9108
|
+
function _importedNamespaceMemberOwnership(index, fileEntry, call, targetFiles) {
|
|
9109
|
+
const receiver = call.receiver || call.receiverRoot;
|
|
9110
|
+
const memberPath = call.receiver
|
|
9111
|
+
? [] : (call.receiverFields || (call.receiverField ? [call.receiverField] : []));
|
|
9112
|
+
const bindings = (fileEntry.importBindings || []).filter(binding =>
|
|
9113
|
+
(binding.alias || binding.name) === receiver &&
|
|
9114
|
+
(binding.kind === 'named' || binding.kind === 'default' || binding.kind === 'namespace'));
|
|
9115
|
+
if (bindings.length === 0) return null;
|
|
9116
|
+
|
|
9117
|
+
let recognized = 0;
|
|
9118
|
+
let unknown = false;
|
|
9119
|
+
for (const binding of bindings) {
|
|
9120
|
+
const rel = fileEntry.moduleResolved?.[binding.module];
|
|
9121
|
+
if (!rel) continue;
|
|
9122
|
+
const startAbs = path.join(index.root, rel);
|
|
9123
|
+
let result;
|
|
9124
|
+
if (binding.kind === 'namespace') {
|
|
9125
|
+
result = memberPath.length > 0
|
|
9126
|
+
? _namespaceExportMemberReaches(
|
|
9127
|
+
index, startAbs, memberPath[0], call.name, targetFiles,
|
|
9128
|
+
4, new Set(), memberPath.slice(1))
|
|
9129
|
+
: { verdict: _nameBindingReaches(index, startAbs, call.name, targetFiles) };
|
|
9130
|
+
} else {
|
|
9131
|
+
const exportedName = binding.kind === 'default' ? 'default' : binding.name;
|
|
9132
|
+
result = _namespaceExportMemberReaches(
|
|
9133
|
+
index, startAbs, exportedName, call.name, targetFiles,
|
|
9134
|
+
4, new Set(), memberPath);
|
|
9135
|
+
}
|
|
9136
|
+
if (!result) continue;
|
|
9137
|
+
recognized++;
|
|
9138
|
+
if (result.verdict === 'yes') return { verdict: 'yes' };
|
|
9139
|
+
if (result.verdict === 'unknown') unknown = true;
|
|
9140
|
+
}
|
|
9141
|
+
if (recognized === 0) return null;
|
|
9142
|
+
if (recognized !== bindings.length) unknown = true;
|
|
9143
|
+
return { verdict: unknown ? 'unknown' : 'no' };
|
|
9144
|
+
}
|
|
9145
|
+
|
|
9146
|
+
/**
|
|
9147
|
+
* Ownership chase for a CommonJS default-like require binding:
|
|
9148
|
+
* `const local = require('./module')`. The local binding name says nothing
|
|
9149
|
+
* about the exporting file's symbol; the direct `module.exports = value`
|
|
9150
|
+
* record does. A locally defined callable is a definitive dead end for a
|
|
9151
|
+
* target in another file, an imported value is chased, and dynamic values
|
|
9152
|
+
* remain unknown. This is exclusion-grade only when every live path is known.
|
|
9153
|
+
*/
|
|
9154
|
+
function _defaultBindingReaches(index, startAbs, targetFiles, maxDepth = 4, visited = new Set()) {
|
|
9155
|
+
if (targetFiles.has(startAbs)) return 'yes';
|
|
9156
|
+
if (maxDepth < 0 || visited.has(startAbs)) return 'unknown';
|
|
9157
|
+
visited.add(startAbs);
|
|
9158
|
+
const fe = index.files.get(startAbs);
|
|
9159
|
+
if (!fe) return 'unknown';
|
|
9160
|
+
|
|
9161
|
+
const defaults = (fe.exportDetails || []).filter(exp =>
|
|
9162
|
+
exp.type === 'module.exports' && exp.defaultLike);
|
|
9163
|
+
if (defaults.length === 0) return 'unknown';
|
|
9164
|
+
|
|
9165
|
+
let unknown = false;
|
|
9166
|
+
for (const exp of defaults) {
|
|
9167
|
+
const localName = exp.localName || exp.name;
|
|
9168
|
+
if (!localName) { unknown = true; continue; }
|
|
9169
|
+
// Without parser-proven syntactic ownership (localName), the record's
|
|
9170
|
+
// name falls back to the synthesized 'default' — which a DYNAMIC
|
|
9171
|
+
// reassignment (`module.exports = require('./impl')`) shares with an
|
|
9172
|
+
// earlier anonymous default. Only a local callable declared AT the
|
|
9173
|
+
// record's own line proves that this record is the local value
|
|
9174
|
+
// (fix #292 — a name-only match must never dead-end the live import
|
|
9175
|
+
// path of the competing dynamic record).
|
|
9176
|
+
const localCallable = (index.symbols.get(localName) || []).some(definition =>
|
|
9177
|
+
definition.file === startAbs && !NON_CALLABLE_TYPES.has(definition.type) &&
|
|
9178
|
+
(exp.localName || definition.startLine === exp.line ||
|
|
9179
|
+
(definition.startLine <= exp.line && definition.endLine >= exp.line)));
|
|
9180
|
+
if (localCallable) continue;
|
|
9181
|
+
|
|
9182
|
+
const bindings = (fe.importBindings || []).filter(binding =>
|
|
9183
|
+
binding.name === localName || binding.alias === localName);
|
|
9184
|
+
if (bindings.length === 0) { unknown = true; continue; }
|
|
9185
|
+
for (const binding of bindings) {
|
|
9186
|
+
const rel = fe.moduleResolved?.[binding.module];
|
|
9187
|
+
if (!rel) { unknown = true; continue; }
|
|
9188
|
+
const nextAbs = path.join(index.root, rel);
|
|
9189
|
+
const verdict = binding.defaultLike
|
|
9190
|
+
? _defaultBindingReaches(
|
|
9191
|
+
index, nextAbs, targetFiles, maxDepth - 1, new Set(visited))
|
|
9192
|
+
: _nameBindingReaches(index, nextAbs, binding.name, targetFiles, maxDepth - 1);
|
|
9193
|
+
if (verdict === 'yes') return 'yes';
|
|
9194
|
+
if (verdict === 'unknown') unknown = true;
|
|
9195
|
+
}
|
|
9196
|
+
}
|
|
9197
|
+
return unknown ? 'unknown' : 'no';
|
|
9198
|
+
}
|
|
9199
|
+
|
|
8346
9200
|
/**
|
|
8347
9201
|
* From-import submodule receivers (fix #224): `from . import jobs` binds
|
|
8348
9202
|
* jobs.py as a plain NAME — the parser can't mark it a module alias (a
|
|
@@ -8365,6 +9219,58 @@ function _submoduleReceiverModule(index, fileEntry, receiverName) {
|
|
|
8365
9219
|
return null;
|
|
8366
9220
|
}
|
|
8367
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
|
+
|
|
8368
9274
|
/**
|
|
8369
9275
|
* Bounded-depth reachability over the import graph: can `fromAbs` reach any
|
|
8370
9276
|
* target file through re-export/import chains? Barrel hierarchies routinely
|
|
@@ -8492,6 +9398,30 @@ function _isEnclosingGenericParam(index, filePath, line, typeName) {
|
|
|
8492
9398
|
return false;
|
|
8493
9399
|
}
|
|
8494
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
|
+
|
|
8495
9425
|
/**
|
|
8496
9426
|
* Receiver-type identity guard shared by the parser-typed branch and the
|
|
8497
9427
|
* local-inference fallback: a name is NOT usable as type identity when it is
|
|
@@ -8688,8 +9618,30 @@ function _resolveReceiverTypeIdentity(index, filePath, knownType, targetDefs, li
|
|
|
8688
9618
|
* CustomCommand extends click.Command), while parallel package versions may
|
|
8689
9619
|
* reuse every class name (zod v3/v4 ZodArray -> ZodType).
|
|
8690
9620
|
*/
|
|
8691
|
-
|
|
8692
|
-
|
|
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) {
|
|
9644
|
+
const origin = _resolveFlowTypeOrigin(index, originFile, knownType, qualifier);
|
|
8693
9645
|
if (!origin?.fromFile) return 'unknown';
|
|
8694
9646
|
const targetOwners = new Set(targetDefs
|
|
8695
9647
|
.map(d => d.className || (d.receiver && d.receiver.replace(/^\*/, '')))
|
|
@@ -8761,7 +9713,16 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
8761
9713
|
return { name: parent, file: parentFile };
|
|
8762
9714
|
};
|
|
8763
9715
|
|
|
8764
|
-
|
|
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 }];
|
|
8765
9726
|
const visited = new Set();
|
|
8766
9727
|
let sawUnresolved = direct === 'unknown';
|
|
8767
9728
|
while (queue.length > 0 && visited.size < 128) {
|
|
@@ -8773,7 +9734,9 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
8773
9734
|
// ancestor slot. In that case ancestry is evidence for the other
|
|
8774
9735
|
// definition, not the pinned one.
|
|
8775
9736
|
if (overridesPinnedSlot(cur.name, cur.file)) continue;
|
|
8776
|
-
const parents =
|
|
9737
|
+
const parents = cur.defStartLine != null
|
|
9738
|
+
? (index._getInheritanceParentsAt?.(cur.name, cur.file, cur.defStartLine) || [])
|
|
9739
|
+
: (index._getInheritanceParents(cur.name, cur.file) || []);
|
|
8777
9740
|
for (const parent of parents) {
|
|
8778
9741
|
const edge = parentOrigin(parent, cur.file);
|
|
8779
9742
|
const verdict = ownerIdentity(edge.name, edge.file);
|
|
@@ -8786,6 +9749,50 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
|
|
|
8786
9749
|
return sawUnresolved ? 'unknown' : 'other';
|
|
8787
9750
|
}
|
|
8788
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
|
+
|
|
8789
9796
|
/**
|
|
8790
9797
|
* Is typeName an ancestor (transitively) of any target definition's class?
|
|
8791
9798
|
* Used by receiver-class disambiguation: a receiver typed as a SUPERTYPE of
|
|
@@ -9017,6 +10024,38 @@ function _closeCallableIdentityGroup(index, targetDefs, definitions) {
|
|
|
9017
10024
|
expanded.push(candidate);
|
|
9018
10025
|
}
|
|
9019
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
|
+
}
|
|
9020
10059
|
return expanded || targetDefs;
|
|
9021
10060
|
}
|
|
9022
10061
|
|
|
@@ -9158,6 +10197,40 @@ function _goQualifierNamesImport(index, fieldFile, qualifier) {
|
|
|
9158
10197
|
// wins so import "k8s.io/client-go/kubernetes/scheme" prefers a def in
|
|
9159
10198
|
// .../kubernetes/scheme/ over .../kubeadm/scheme/). Extracted for reuse:
|
|
9160
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
|
+
|
|
9161
10234
|
function _structuralModuleBindings(fileEntry, call) {
|
|
9162
10235
|
if (call?.receiverModuleSpecifier) {
|
|
9163
10236
|
return [{
|
|
@@ -9352,13 +10425,29 @@ function _calleeExportDefinitions(index, startAbs, exposedName, language, call,
|
|
|
9352
10425
|
};
|
|
9353
10426
|
|
|
9354
10427
|
const details = fe.exportDetails || [];
|
|
10428
|
+
const localDetails = details.filter(e =>
|
|
10429
|
+
!e.source && (e.alias || e.name) === attr);
|
|
10430
|
+
const ambiguousCjsMember = localDetails.some(e =>
|
|
10431
|
+
(e.type === 'module.exports' || e.type === 'exports') &&
|
|
10432
|
+
!e.defaultLike && !e.localName);
|
|
9355
10433
|
const localExposed = fe.language === 'python' ||
|
|
9356
|
-
(fe.exports || []).includes(attr) ||
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
10434
|
+
(fe.exports || []).includes(attr) || localDetails.length > 0;
|
|
10435
|
+
if (ambiguousCjsMember) {
|
|
10436
|
+
// `module.exports = { run: condition ? native : fallback }`
|
|
10437
|
+
// exposes no single callable identity. The caller direction
|
|
10438
|
+
// routes this visible; trace-down must make the same abstention
|
|
10439
|
+
// instead of selecting a same-named local fallback.
|
|
10440
|
+
unknown = true;
|
|
10441
|
+
} else if (localExposed) {
|
|
10442
|
+
const localNames = new Set([attr]);
|
|
10443
|
+
for (const detail of localDetails) {
|
|
10444
|
+
if (detail.localName) localNames.add(detail.localName);
|
|
10445
|
+
}
|
|
10446
|
+
for (const localName of localNames) {
|
|
10447
|
+
for (const d of (index.symbols.get(localName) || [])) {
|
|
10448
|
+
if (d.file === abs && shapeMatches(d)) {
|
|
10449
|
+
matches.set(`${d.file}:${d.startLine}`, d);
|
|
10450
|
+
}
|
|
9362
10451
|
}
|
|
9363
10452
|
}
|
|
9364
10453
|
}
|
|
@@ -9368,8 +10457,9 @@ function _calleeExportDefinitions(index, startAbs, exposedName, language, call,
|
|
|
9368
10457
|
// never to namespace.member() routing.
|
|
9369
10458
|
if (options.allowDefaultExport && depth === 0) {
|
|
9370
10459
|
for (const e of details) {
|
|
9371
|
-
if (e.type !== 'module.exports' || !e.name) continue;
|
|
9372
|
-
|
|
10460
|
+
if (e.type !== 'module.exports' || !e.defaultLike || !e.name) continue;
|
|
10461
|
+
const localName = e.localName || e.name;
|
|
10462
|
+
for (const d of (index.symbols.get(localName) || [])) {
|
|
9373
10463
|
if (d.file === abs && shapeMatches(d)) {
|
|
9374
10464
|
matches.set(`${d.file}:${d.startLine}`, d);
|
|
9375
10465
|
}
|
|
@@ -9467,7 +10557,8 @@ function _calleeOverloadSelect(index, call, matches, language) {
|
|
|
9467
10557
|
applicable = _preferFixedArityOverloads(
|
|
9468
10558
|
index, call, applicable, language);
|
|
9469
10559
|
if (applicable.length > 1) {
|
|
9470
|
-
const mostSpecific = _javaMostSpecificOverload(
|
|
10560
|
+
const mostSpecific = _javaMostSpecificOverload(
|
|
10561
|
+
index, applicable, call, language);
|
|
9471
10562
|
if (mostSpecific) return { match: mostSpecific };
|
|
9472
10563
|
}
|
|
9473
10564
|
}
|
|
@@ -9708,7 +10799,10 @@ function _declaredFieldType(index, rootType, fieldName, language, info, rootName
|
|
|
9708
10799
|
return null;
|
|
9709
10800
|
}
|
|
9710
10801
|
}
|
|
9711
|
-
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;
|
|
9712
10806
|
if (t) normalized.add(t);
|
|
9713
10807
|
else return null; // any un-normalizable declaration → no evidence
|
|
9714
10808
|
}
|
|
@@ -9734,8 +10828,13 @@ function _declaredFieldType(index, rootType, fieldName, language, info, rootName
|
|
|
9734
10828
|
const qualifier = language === 'java'
|
|
9735
10829
|
? _javaNestedTypeQualifier(rawText) : undefined;
|
|
9736
10830
|
if (qualifier) namespaces.add(qualifier);
|
|
9737
|
-
const
|
|
9738
|
-
|
|
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);
|
|
9739
10838
|
if (!origin?.fromFile) {
|
|
9740
10839
|
complete = false;
|
|
9741
10840
|
break;
|
|
@@ -9944,7 +11043,14 @@ function _namespaceContainedDef(index, fileEntry, callFileAbs, receiverName, cal
|
|
|
9944
11043
|
if (nsDefs.length === 0) return null;
|
|
9945
11044
|
const candidates = restrictDefs ||
|
|
9946
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.
|
|
9947
11052
|
const contained = candidates.filter(d => nsDefs.some(ns =>
|
|
11053
|
+
ns !== d &&
|
|
9948
11054
|
ns.file === d.file && ns.startLine <= d.startLine &&
|
|
9949
11055
|
(ns.endLine ?? Infinity) >= (d.endLine ?? d.startLine)));
|
|
9950
11056
|
if (contained.length === 0) return null;
|
|
@@ -10159,9 +11265,28 @@ function _calleeLanguageCompatible(index, def, callerLanguage) {
|
|
|
10159
11265
|
* receiver typed Child legitimately reaches methods defined on Base (the
|
|
10160
11266
|
* #198 ancestor rule, callee direction). Includes the type itself.
|
|
10161
11267
|
*/
|
|
10162
|
-
function _receiverTypeAncestors(index, typeName, maxHops = 6) {
|
|
11268
|
+
function _receiverTypeAncestors(index, typeName, maxHops = 6, scopeRef = null) {
|
|
10163
11269
|
const seen = new Set([typeName]);
|
|
10164
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
|
+
}
|
|
10165
11290
|
for (let hop = 0; hop < maxHops && frontier.length; hop++) {
|
|
10166
11291
|
const next = [];
|
|
10167
11292
|
for (const cls of frontier) {
|
|
@@ -10187,7 +11312,7 @@ function _receiverTypeAncestors(index, typeName, maxHops = 6) {
|
|
|
10187
11312
|
* never confirmed by bare-name resolution
|
|
10188
11313
|
* null — receiver type unknown; existing heuristics decide
|
|
10189
11314
|
*/
|
|
10190
|
-
function _calleeReceiverTypeRoute(index, call, localTypes, language) {
|
|
11315
|
+
function _calleeReceiverTypeRoute(index, call, localTypes, language, scopeRef = null) {
|
|
10191
11316
|
const raw = call.receiverType || localTypes?.get(call.receiver);
|
|
10192
11317
|
if (!raw || typeof raw !== 'string') return null;
|
|
10193
11318
|
const head = _structuralTypeHead(raw, { index, language }) || raw;
|
|
@@ -10197,7 +11322,7 @@ function _calleeReceiverTypeRoute(index, call, localTypes, language) {
|
|
|
10197
11322
|
_calleeLanguageCompatible(index, d, language));
|
|
10198
11323
|
let matches = defs.filter(d => d.className === head || d.className === norm);
|
|
10199
11324
|
if (matches.length === 0 && defs.length > 0) {
|
|
10200
|
-
const ancestors = _receiverTypeAncestors(index, head);
|
|
11325
|
+
const ancestors = _receiverTypeAncestors(index, head, 6, scopeRef);
|
|
10201
11326
|
matches = defs.filter(d => ancestors.has(d.className));
|
|
10202
11327
|
}
|
|
10203
11328
|
if (matches.length === 1) return { resolve: matches[0] };
|
|
@@ -10273,7 +11398,12 @@ function _calleeSingleOwnerMatch(index, def, fileEntry, call, name, language, fl
|
|
|
10273
11398
|
const head = _structuralTypeHead(call.receiverType, { index, language }) || call.receiverType;
|
|
10274
11399
|
const norm = head;
|
|
10275
11400
|
if (head !== owner && norm !== owner &&
|
|
10276
|
-
!_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;
|
|
10277
11407
|
}
|
|
10278
11408
|
if (traits?.typeSystem === 'nominal' && call.argCount != null &&
|
|
10279
11409
|
!_callArityCompatible(call, ownerDefs, language)) return null;
|
|
@@ -10488,6 +11618,28 @@ const JAVA_FINAL_REFERENCE_TYPES = new Set([
|
|
|
10488
11618
|
'Integer', 'Long', 'Float', 'Double', 'Void',
|
|
10489
11619
|
]);
|
|
10490
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
|
+
|
|
10491
11643
|
// Which parameter types a call-site literal kind can bind (Java overload
|
|
10492
11644
|
// resolution: identity, widening, boxing — plus the boxed types' interfaces).
|
|
10493
11645
|
// Anything not provably incompatible MATCHES: only certainty excludes.
|
|
@@ -10499,8 +11651,37 @@ const JAVA_KIND_TYPES = {
|
|
|
10499
11651
|
float: ['float', 'double', 'Float', 'Number', 'Comparable', 'Serializable'],
|
|
10500
11652
|
double: ['double', 'Double', 'Number', 'Comparable', 'Serializable'],
|
|
10501
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'],
|
|
10502
11658
|
};
|
|
10503
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
|
+
|
|
10504
11685
|
// Exact subset of C# implicit conversions for compiler-owned closed types.
|
|
10505
11686
|
// Returns null when user-defined conversions or external ancestry could
|
|
10506
11687
|
// matter. A false verdict is therefore exclusion-grade evidence.
|
|
@@ -10558,6 +11739,19 @@ function _csharpKnownTypeAssignable(actualRaw, expectedRaw) {
|
|
|
10558
11739
|
// name-based guesses. They close the common portable-AST gap where overload
|
|
10559
11740
|
// resolution depends on the return type of a library call.
|
|
10560
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'],
|
|
10561
11755
|
['java.lang.Object#getClass', 'java.lang.Class'],
|
|
10562
11756
|
['java.lang.Class#getEnclosingClass', 'java.lang.Class'],
|
|
10563
11757
|
['java.lang.Class#getDeclaringClass', 'java.lang.Class'],
|
|
@@ -10807,7 +12001,13 @@ function _javaOwnerFieldType(index, owner, fieldName) {
|
|
|
10807
12001
|
definition.className === ownerSimple &&
|
|
10808
12002
|
(definition.type === 'field' || definition.memberType === 'field') &&
|
|
10809
12003
|
definition.fieldType);
|
|
10810
|
-
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
|
+
}
|
|
10811
12011
|
|
|
10812
12012
|
const types = new Set();
|
|
10813
12013
|
for (const field of fields) {
|
|
@@ -10922,6 +12122,27 @@ function _javaArgKindMatches(index, kind, paramType, language) {
|
|
|
10922
12122
|
if (tSimple === bare) return true;
|
|
10923
12123
|
const platformAssignable = _javaPlatformAssignable(t, bare);
|
|
10924
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
|
+
}
|
|
10925
12146
|
// These java.lang types are final. A statically known different type
|
|
10926
12147
|
// can never bind their overload, even when the argument type's own
|
|
10927
12148
|
// ancestry ends in external Object and is therefore incomplete.
|
|
@@ -10944,6 +12165,14 @@ function _javaArgKindMatches(index, kind, paramType, language) {
|
|
|
10944
12165
|
d.modifiers?.includes('sealed'))) {
|
|
10945
12166
|
return false;
|
|
10946
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
|
+
}
|
|
10947
12176
|
if (tDefs.length === 0) return true; // external arg type — unknowable
|
|
10948
12177
|
const asTarget = [{ className: tSimple, file: tDefs[0].file }];
|
|
10949
12178
|
if (_isDispatchAncestor(index, bare, asTarget)) return true;
|
|
@@ -10963,6 +12192,7 @@ function _cppTypeCategory(type) {
|
|
|
10963
12192
|
const unqualified = original
|
|
10964
12193
|
.replace(/\b(const|volatile|constexpr|typename|struct|class)\b/g, ' ')
|
|
10965
12194
|
.replace(/&&|\.\.\.|[&*]/g, ' ')
|
|
12195
|
+
.replace(/\[[^\]]*\]/g, ' ')
|
|
10966
12196
|
.replace(/\s+/g, ' ')
|
|
10967
12197
|
.trim();
|
|
10968
12198
|
const headText = unqualified.split('<')[0].trim();
|
|
@@ -11082,7 +12312,8 @@ function _cppArgKindMatches(kind, paramType) {
|
|
|
11082
12312
|
if (kind === 'null') {
|
|
11083
12313
|
return !['number', 'bool', 'character'].includes(expected.kind);
|
|
11084
12314
|
}
|
|
11085
|
-
if (kind.startsWith('type:') || kind.startsWith('call:')
|
|
12315
|
+
if (kind.startsWith('type:') || kind.startsWith('call:') ||
|
|
12316
|
+
kind.startsWith('bcall:')) {
|
|
11086
12317
|
const actualType = kind.slice(kind.indexOf(':') + 1);
|
|
11087
12318
|
const actual = _cppTypeCategory(actualType);
|
|
11088
12319
|
if (actual.head && expected.head && actual.head === expected.head) return true;
|
|
@@ -11197,7 +12428,7 @@ function _javaTypeAtLeastAsSpecific(index, subType, superType, subDef) {
|
|
|
11197
12428
|
// when every argument position is at least as specific as every competing
|
|
11198
12429
|
// candidate and at least one position is strictly more specific. Unknown
|
|
11199
12430
|
// relationships stay ambiguous.
|
|
11200
|
-
function _javaMostSpecificOverload(index, applicable, call) {
|
|
12431
|
+
function _javaMostSpecificOverload(index, applicable, call, language) {
|
|
11201
12432
|
const argCount = call.argCount;
|
|
11202
12433
|
if (!Number.isInteger(argCount) || applicable.length < 2) return null;
|
|
11203
12434
|
// Compiler-visible exact static types outrank candidates whose external
|
|
@@ -11233,6 +12464,64 @@ function _javaMostSpecificOverload(index, applicable, call) {
|
|
|
11233
12464
|
if (winners.length === 1) return winners[0].definition;
|
|
11234
12465
|
}
|
|
11235
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
|
+
}
|
|
11236
12525
|
const dominates = (a, b) => {
|
|
11237
12526
|
let strict = false;
|
|
11238
12527
|
for (let i = 0; i < argCount; i++) {
|
|
@@ -11394,16 +12683,142 @@ function _cppQualifiedPathOwnsTarget(index, callerFile, call, targetDefs) {
|
|
|
11394
12683
|
return targetDefs.some(definition => {
|
|
11395
12684
|
if (!definition.file) return false;
|
|
11396
12685
|
const namespace = String(definition.namespace || '');
|
|
11397
|
-
if (namespace
|
|
11398
|
-
namespace
|
|
11399
|
-
|
|
11400
|
-
|
|
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}`);
|
|
11401
12694
|
}
|
|
11402
12695
|
const relative = path.relative(index.root, definition.file);
|
|
11403
12696
|
return relative.split(path.sep).includes(namespaceRoot);
|
|
11404
12697
|
});
|
|
11405
12698
|
}
|
|
11406
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
|
+
|
|
11407
12822
|
function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
11408
12823
|
const targetOwners = new Set(targetDefs.map(d => d.className).filter(Boolean));
|
|
11409
12824
|
const targetLanguage = targetDefs[0]?.file && index.files.get(targetDefs[0].file)?.language;
|
|
@@ -11430,12 +12845,22 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11430
12845
|
targetDefs.every(d => !d.className && !d.receiver) &&
|
|
11431
12846
|
_cppTargetVisibleFrom(index, callerFile, targetDefs)) {
|
|
11432
12847
|
const visible = _cppVisibleFiles(index, callerFile);
|
|
11433
|
-
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
|
+
};
|
|
11434
12860
|
family = definitions.filter(d =>
|
|
11435
12861
|
!NON_CALLABLE_TYPES.has(d.type) &&
|
|
11436
12862
|
!d.className && !d.receiver &&
|
|
11437
|
-
(
|
|
11438
|
-
pinnedKeys.has(`${d.file}:${d.startLine}`)) &&
|
|
12863
|
+
pathCanName(d) &&
|
|
11439
12864
|
(visible.has(d.file) ||
|
|
11440
12865
|
pinnedKeys.has(`${d.file}:${d.startLine}`)));
|
|
11441
12866
|
} else {
|
|
@@ -11471,6 +12896,17 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11471
12896
|
// constrained templates can share an identical function signature while
|
|
11472
12897
|
// remaining distinct compiler overloads.
|
|
11473
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
|
+
}
|
|
11474
12910
|
const pinFile = targetDefs.find(d => d.file)?.file;
|
|
11475
12911
|
const seenCls = new Set(targetOwners);
|
|
11476
12912
|
const queue = [...targetOwners];
|
|
@@ -11487,13 +12923,42 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11487
12923
|
for (const d of definitions) {
|
|
11488
12924
|
if (NON_CALLABLE_TYPES.has(d.type) || d.className !== pName) continue;
|
|
11489
12925
|
const sig = typeSig(d);
|
|
11490
|
-
if (sig !== null &&
|
|
12926
|
+
if (sig !== null && familySigs.has(sig)) continue; // occupied slot
|
|
12927
|
+
if (sig !== null) familySigs.add(sig);
|
|
11491
12928
|
family.push(d);
|
|
11492
12929
|
}
|
|
11493
12930
|
}
|
|
11494
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
|
+
}
|
|
11495
12941
|
if (family.length <= 1) return null;
|
|
11496
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
|
+
}
|
|
11497
12962
|
let applicable = family.filter(d => _overloadApplicable(index, call, d));
|
|
11498
12963
|
if (applicable.length === 0) return null; // shape fits nothing we model — no claim
|
|
11499
12964
|
if (targetLanguage === 'java' || targetLanguage === 'csharp') {
|
|
@@ -11502,7 +12967,8 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11502
12967
|
}
|
|
11503
12968
|
const mostSpecific = (targetLanguage === 'java' ||
|
|
11504
12969
|
targetLanguage === 'csharp')
|
|
11505
|
-
? _javaMostSpecificOverload(index, applicable, call)
|
|
12970
|
+
? _javaMostSpecificOverload(index, applicable, call, targetLanguage)
|
|
12971
|
+
: null;
|
|
11506
12972
|
if (mostSpecific) {
|
|
11507
12973
|
return pinnedKeys.has(`${mostSpecific.file}:${mostSpecific.startLine}`)
|
|
11508
12974
|
? null : 'other-overload';
|
|
@@ -11514,6 +12980,13 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11514
12980
|
return null;
|
|
11515
12981
|
}
|
|
11516
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
|
+
}
|
|
11517
12990
|
const compileTimeDispatch = targetLanguage === 'cpp';
|
|
11518
12991
|
const templateOnly = compileTimeDispatch &&
|
|
11519
12992
|
applicable.every(definition => definition.templateDependent);
|
|
@@ -11529,6 +13002,121 @@ function _overloadDiscipline(index, call, targetDefs, definitions, callerFile) {
|
|
|
11529
13002
|
};
|
|
11530
13003
|
}
|
|
11531
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
|
+
|
|
11532
13120
|
/**
|
|
11533
13121
|
* Build the target type set for receiver-class disambiguation: target
|
|
11534
13122
|
* classes/receivers + their non-overriding subtypes (transitively). A Child
|
|
@@ -12174,6 +13762,31 @@ function _builtinMethodReturnType(language, receiverType, methodName) {
|
|
|
12174
13762
|
* the PRODUCER's scope (_resolveFlowTypeOrigin). External producer packages
|
|
12175
13763
|
* and reject-set returns stay untyped — no evidence either way.
|
|
12176
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
|
+
|
|
12177
13790
|
function _nominalChainedReceiverType(index, call, fileEntry, filePath) {
|
|
12178
13791
|
const language = fileEntry.language;
|
|
12179
13792
|
const defs = (index.symbols.get(call.receiverCall) || [])
|
|
@@ -12859,18 +14472,26 @@ function _rustClosureReceiverType(index, fileEntry, filePath, call, ctx) {
|
|
|
12859
14472
|
function _typeOfCallResultFold(index, fileEntry, filePath, record, ctx, consumerAwaited) {
|
|
12860
14473
|
if (ctx.memo.has(record)) return ctx.memo.get(record);
|
|
12861
14474
|
// Real builder APIs routinely exceed 64 hops (clap's benchmark command
|
|
12862
|
-
// has ~160). Keep a generous hard safety bound
|
|
12863
|
-
//
|
|
12864
|
-
//
|
|
12865
|
-
|
|
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
|
+
}
|
|
12866
14486
|
ctx.visiting.add(record);
|
|
14487
|
+
const tripsBefore = ctx.foldTrips || 0;
|
|
12867
14488
|
let out;
|
|
12868
14489
|
try {
|
|
12869
14490
|
out = _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, consumerAwaited);
|
|
12870
14491
|
} finally {
|
|
12871
14492
|
ctx.visiting.delete(record);
|
|
12872
14493
|
}
|
|
12873
|
-
if (out) ctx.memo.set(record, out);
|
|
14494
|
+
if (out || (ctx.foldTrips || 0) === tripsBefore) ctx.memo.set(record, out ?? null);
|
|
12874
14495
|
return out;
|
|
12875
14496
|
}
|
|
12876
14497
|
|
|
@@ -12967,6 +14588,16 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
12967
14588
|
const cands = (index.symbols.get(name) || [])
|
|
12968
14589
|
.filter(d => !NON_CALLABLE_TYPES.has(d.type) && d.returnType && !d.className);
|
|
12969
14590
|
let matches = cands.filter(d => d.file === modFile);
|
|
14591
|
+
// A named/default import may itself be an exported namespace object
|
|
14592
|
+
// (`import { z } from 'zod/v3'; z.string()`). Resolve the producer
|
|
14593
|
+
// name through that exact namespace identity before the older
|
|
14594
|
+
// one-hop barrel fallback. Exported callable-alias symbols then carry
|
|
14595
|
+
// the captured class member's declared return type into the chain.
|
|
14596
|
+
if (matches.length === 0) {
|
|
14597
|
+
matches = cands.filter(definition =>
|
|
14598
|
+
_importedNamespaceMemberOwnership(
|
|
14599
|
+
index, fileEntry, record, new Set([definition.file]))?.verdict === 'yes');
|
|
14600
|
+
}
|
|
12970
14601
|
if (matches.length === 0) {
|
|
12971
14602
|
const hop = index.importGraph.get(modFile);
|
|
12972
14603
|
if (hop) matches = cands.filter(d => hop.has(d.file));
|
|
@@ -13022,7 +14653,8 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
|
|
|
13022
14653
|
// to the one-hop project-wide agreement rule when the receiver stays
|
|
13023
14654
|
// untyped.
|
|
13024
14655
|
if (record.isMethod) {
|
|
13025
|
-
let rt =
|
|
14656
|
+
let rt = _goBuiltinChainedReceiverType(
|
|
14657
|
+
index, fileEntry, filePath, record);
|
|
13026
14658
|
if (record.receiverType && !record.receiverIsChainRoot) {
|
|
13027
14659
|
const origin = nominal
|
|
13028
14660
|
? _resolveFlowTypeOrigin(index, filePath, record.receiverType,
|
|
@@ -13423,4 +15055,4 @@ function findCallbackUsages(index, name) {
|
|
|
13423
15055
|
return usages;
|
|
13424
15056
|
}
|
|
13425
15057
|
|
|
13426
|
-
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 };
|