ucn 4.2.1 → 4.2.3

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/core/callers.js CHANGED
@@ -133,6 +133,55 @@ function getCachedCalls(index, filePath, options = {}) {
133
133
  }
134
134
  }
135
135
 
136
+ function _javaPackageKey(relativePath) {
137
+ const normalized = String(relativePath || '').replace(/\\/g, '/');
138
+ const marker = '/java/';
139
+ const at = normalized.lastIndexOf(marker);
140
+ const packagePath = at >= 0 ? normalized.slice(at + marker.length) : normalized;
141
+ return path.posix.dirname(packagePath);
142
+ }
143
+
144
+ function _javaConstructorDisposition(index, filePath, fileEntry, call, targetDefs) {
145
+ const typeKinds = new Set(['class', 'record', 'enum']);
146
+ const targets = (targetDefs || []).filter(d => typeKinds.has(d.type));
147
+ if (targets.length === 0) return 'target';
148
+ const all = (index.symbols.get(call.name) || []).filter(d => typeKinds.has(d.type));
149
+ const targetKeys = new Set(targets.map(d => `${d.file}:${d.startLine}`));
150
+ const isTarget = d => targetKeys.has(`${d.file}:${d.startLine}`);
151
+
152
+ if (call.receiver) {
153
+ const owned = all.filter(d => d.enclosingType === call.receiver);
154
+ if (owned.some(isTarget)) return 'target';
155
+ if (owned.length > 0) return 'other';
156
+ // Lowercase qualifiers are package fragments; the Java parser retains
157
+ // only the terminal qualifier, so identity is not provable here.
158
+ return 'unknown';
159
+ }
160
+
161
+ const bindings = (fileEntry.importBindings || []).filter(b =>
162
+ b.name === call.name || b.alias === call.name);
163
+ if (bindings.length > 0) {
164
+ const resolvedFiles = bindings.map(b => fileEntry.moduleResolved?.[b.module])
165
+ .filter(Boolean).map(rel => path.join(index.root, rel));
166
+ if (resolvedFiles.some(fp => targets.some(d => d.file === fp))) return 'target';
167
+ if (resolvedFiles.length > 0) return 'other';
168
+ return 'unknown';
169
+ }
170
+
171
+ const caller = index.findEnclosingFunction(filePath, call.line, true);
172
+ if (targets.some(d => d.file === filePath &&
173
+ (!d.enclosingType || d.enclosingType === caller?.className))) return 'target';
174
+
175
+ const callerPackage = _javaPackageKey(fileEntry.relativePath);
176
+ const samePackage = all.filter(d => !d.enclosingType &&
177
+ _javaPackageKey(d.relativePath) === callerPackage);
178
+ if (samePackage.some(isTarget)) return 'target';
179
+ if (samePackage.length > 0) return 'other';
180
+
181
+ if (all.length > 0 && all.every(isTarget)) return 'target';
182
+ return 'unknown';
183
+ }
184
+
136
185
  /**
137
186
  * Find all call sites that invoke the named symbol.
138
187
  *
@@ -313,6 +362,24 @@ function findCallers(index, name, options = {}) {
313
362
  exportAliasRenamers.get(e.alias).add(fp);
314
363
  }
315
364
  }
365
+ // CommonJS callable default export: `const express = require('./lib')`
366
+ // followed by `express()` denotes the local function assigned to
367
+ // `module.exports`, even though the call-site spelling differs. This is
368
+ // exact module ownership: require bindings preserve their default-like
369
+ // shape and the resolved module records the assigned local definition.
370
+ for (const [fp, fe] of index.files) {
371
+ for (const b of (fe.importBindings || [])) {
372
+ if (!b.defaultLike) continue;
373
+ const rel = fe.moduleResolved && fe.moduleResolved[b.module];
374
+ if (!rel) continue;
375
+ const moduleFile = path.join(index.root, rel);
376
+ if (!aliasTargetFiles.has(moduleFile)) continue;
377
+ const exported = index.files.get(moduleFile)?.exportDetails || [];
378
+ if (!exported.some(e => e.type === 'module.exports' && e.name === name)) continue;
379
+ if (!importAliasLocals.has(fp)) importAliasLocals.set(fp, new Set());
380
+ importAliasLocals.get(fp).add(b.alias || b.name);
381
+ }
382
+ }
316
383
  // Files that can reach each renaming module through imports — re-export
317
384
  // chains run deep (test → mini/index → external → schemas), so a fixed
318
385
  // hop count misses real surfaces. Bounded reverse-import BFS; matching
@@ -342,6 +409,14 @@ function findCallers(index, name, options = {}) {
342
409
  for (const local of locals) aliasNames.add(local);
343
410
  }
344
411
  const hasAliasSurfaces = aliasNames.size > 0;
412
+ const shadowCanReachPinnedTarget = (filePath, call) => {
413
+ if (call.resolvedName === name || call.resolvedNames?.includes(name)) return true;
414
+ const scope = call.enclosingFunction;
415
+ return !!scope && (options.targetDefinitions || definitions).some(d =>
416
+ d.file === filePath && d.name === call.name &&
417
+ d.startLine >= scope.startLine && d.endLine <= scope.endLine &&
418
+ d.startLine <= call.line);
419
+ };
345
420
 
346
421
  // Phase 1: Find matching calls without reading file content.
347
422
  // Collect pending callers keyed by file — content is read only in Phase 2.
@@ -415,6 +490,64 @@ function findCallers(index, name, options = {}) {
415
490
  }
416
491
  if (!calledAs) continue;
417
492
  }
493
+ if (!calledAs && call.name !== name &&
494
+ (call.resolvedName === name ||
495
+ (call.resolvedNames && call.resolvedNames.includes(name)))) {
496
+ calledAs = call.name;
497
+ }
498
+
499
+ // Java constructor identity is package/import/nesting scoped.
500
+ // A bare `new Tag()` imported from parser.Tag cannot construct
501
+ // either Evaluator.Tag or Token.Tag merely because all three
502
+ // share the terminal name. Keep unresolved ownership visible;
503
+ // only compiler-shaped scope evidence enters the confirmed tier.
504
+ if (call.isConstructor && fileEntry.language === 'java') {
505
+ const constructorDisposition = _javaConstructorDisposition(
506
+ index, filePath, fileEntry, call,
507
+ options.targetDefinitions || definitions);
508
+ if (constructorDisposition === 'other') {
509
+ recordExcluded(filePath, call.line, 'other-definition-import');
510
+ continue;
511
+ }
512
+ if (constructorDisposition === 'unknown') {
513
+ if (collectAccount) {
514
+ routeUnverified(filePath, fileEntry, call,
515
+ 'ambiguous-binding', calledAs);
516
+ }
517
+ continue;
518
+ }
519
+ }
520
+
521
+ // A named function expression's name is visible only inside
522
+ // its own body (ECMA-262). A pinned NFE target can be called
523
+ // only by code within its range (recursion); any other site
524
+ // provably binds a different definition of the name.
525
+ const pinnedForScope = options.targetDefinitions || definitions;
526
+ if (!call.isMethod && options.targetDefinitions &&
527
+ pinnedForScope.length > 0 &&
528
+ pinnedForScope.every(d => d.bodyScopedName) &&
529
+ !pinnedForScope.some(d => d.file === filePath &&
530
+ call.line >= d.startLine && call.line <= d.endLine)) {
531
+ recordExcluded(filePath, call.line, 'other-definition');
532
+ continue;
533
+ }
534
+
535
+ // A cast receiver type is a compile-true STATIC type, but a
536
+ // cast naming a type with NO project definition places the
537
+ // value in code UCN cannot see (java.lang.Integer, external
538
+ // interfaces). Name-knowledge is never routing evidence
539
+ // (#222(4)): the invisible type may extend the target's
540
+ // class, so an unresolved cast neither confirms nor excludes
541
+ // — drop to the receiver-evidence-free physics (#210
542
+ // external-contract / #204 owner rules decide, both modes),
543
+ // exactly the pre-cast-capture record shape. Resolved casts
544
+ // keep their honest typed routing.
545
+ if (call.receiverTypeCast && call.receiverType &&
546
+ !(index.symbols.get(call.receiverType) || [])
547
+ .some(d => NON_CALLABLE_TYPES.has(d.type) && d.type !== 'field')) {
548
+ call = { ...call, receiver: undefined, receiverType: undefined,
549
+ receiverTypeCast: undefined };
550
+ }
418
551
 
419
552
  // Return-type flow: an untyped method receiver may be a
420
553
  // variable assigned from a call with a known return annotation
@@ -486,6 +619,55 @@ function findCallers(index, name, options = {}) {
486
619
  }
487
620
  }
488
621
 
622
+ // An unqualified constructor name can still have exact import
623
+ // provenance: `from io import StringIO; out = StringIO()`.
624
+ // A resolved project import pins the type to that file; a
625
+ // clearly external import blocks project-local single-owner
626
+ // confirmation. Annotation-only receivers do not take this
627
+ // path because they may hold a project subtype.
628
+ if (call.isMethod && call.receiverConstructed && call.receiverType &&
629
+ !call.receiverTypeQualifier &&
630
+ langTraits(fileEntry.language)?.typeSystem === 'structural') {
631
+ const bindings = (fileEntry.importBindings || []).filter(b =>
632
+ b.name === call.receiverType || b.alias === call.receiverType);
633
+ if (bindings.length > 0) {
634
+ const project = bindings.map(b => ({
635
+ binding: b,
636
+ rel: fileEntry.moduleResolved && fileEntry.moduleResolved[b.module],
637
+ })).find(x => x.rel);
638
+ if (project) {
639
+ call = { ...call,
640
+ receiverType: project.binding.name,
641
+ receiverTypeFlowFile: path.join(index.root, project.rel),
642
+ };
643
+ } else {
644
+ const projectish = bindings.some(b => {
645
+ const mod = String(b.module || '');
646
+ const first = mod.split(/[./]/).filter(Boolean)[0];
647
+ return mod.startsWith('.') ||
648
+ (first && _projectTopLevelNames(index).has(first));
649
+ });
650
+ const via = `${bindings[0].module}.${bindings[0].name}`;
651
+ call = { ...call, receiverType: undefined,
652
+ ...(projectish
653
+ ? { receiverQualifiedFlow: `${via} — unresolved module` }
654
+ : { receiverExternalFlow: via }) };
655
+ }
656
+ }
657
+ }
658
+
659
+ // An untyped value produced by a context manager (`with ...
660
+ // as out`) has no receiver identity. A unique project method
661
+ // spelling is not enough to confirm it; keep the edge visible
662
+ // until an annotation/constructor/return flow proves the type.
663
+ if (collectAccount && call.isMethod && call.receiverWithBinding &&
664
+ !call.receiverType && !call.receiverExternalFlow && !call.receiverQualifiedFlow) {
665
+ routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
666
+ dispatchVia: 'context-manager result',
667
+ });
668
+ continue;
669
+ }
670
+
489
671
  // Chained-receiver typing (fix #219): the receiver IS a call —
490
672
  // `me._def.args.parseAsync(args, params).catch(...)` — so the
491
673
  // producer's DECLARED return annotation types it (Promise →
@@ -750,6 +932,24 @@ function findCallers(index, name, options = {}) {
750
932
  // same-name symbol shadows it invisibly.
751
933
  const cbTargetFiles = new Set(cbTargetDefs.map(d => d.file).filter(Boolean));
752
934
  const cbSameFile = cbTargetFiles.has(filePath);
935
+ // A module-local definition owns a bare callback name
936
+ // before any file-level import graph evidence. Importing
937
+ // the target's package elsewhere in the file cannot make
938
+ // `register(group)` refer past a local `def group()`.
939
+ // Ownership requires a def a BARE name can actually bind
940
+ // (#218a/#222(3)/#269(2)): class members, property-
941
+ // assigned functions, and body-scoped function-expression
942
+ // names never own module scope — a same-file `class X {
943
+ // validate() {} }` must not exclude the imported
944
+ // validate passed as a callback.
945
+ if (!cbSameFile && (index.symbols.get(call.name) || []).some(d =>
946
+ d.file === filePath && !cbTargetFiles.has(d.file) &&
947
+ d.startLine <= call.line &&
948
+ !d.className && !d.memberAssigned && !d.bodyScopedName &&
949
+ !NON_CALLABLE_TYPES.has(d.type))) {
950
+ recordExcluded(filePath, call.line, 'other-definition-import');
951
+ continue;
952
+ }
753
953
  const cbSamePackage = !cbSameFile &&
754
954
  langTraits(fileEntry.language)?.typeSystem === 'nominal' &&
755
955
  cbTargetDefs.some(d => d.file &&
@@ -772,7 +972,12 @@ function findCallers(index, name, options = {}) {
772
972
  // same-name definition in this file (or one this file
773
973
  // imports) that is NOT the target — same disposition
774
974
  // as the import-graph disambiguation for plain calls.
775
- const cbOtherDefFiles = new Set(definitions
975
+ // Bare-name bindable defs only (#222(3)): a foreign
976
+ // class's same-name METHOD is unreachable by a bare
977
+ // reference and proves no mis-link.
978
+ const cbOtherDefFiles = new Set((index.symbols.get(call.name) || [])
979
+ .filter(d => !d.className && !d.memberAssigned &&
980
+ !d.bodyScopedName && !NON_CALLABLE_TYPES.has(d.type))
776
981
  .map(d => d.file).filter(f => f && !cbTargetFiles.has(f)));
777
982
  if (cbOtherDefFiles.has(filePath) ||
778
983
  (cbImports && setSome(cbImports, imp => cbOtherDefFiles.has(imp)))) {
@@ -791,7 +996,7 @@ function findCallers(index, name, options = {}) {
791
996
  // locals and inner-arrow params (fix #203 — parser-side
792
997
  // lexical scope walk sets call.localShadow; JS block-accurate,
793
998
  // Python function-wide assignment semantics).
794
- if (call.localShadow ||
999
+ if ((call.localShadow && !shadowCanReachPinnedTarget(filePath, call)) ||
795
1000
  (callerSymbol && Array.isArray(callerSymbol.paramsStructured) &&
796
1001
  callerSymbol.paramsStructured.some(p => p && p.name === call.name))) {
797
1002
  recordExcluded(filePath, call.line, 'local-shadow');
@@ -822,7 +1027,8 @@ function findCallers(index, name, options = {}) {
822
1027
  // an isFunctionReference-only argument ref (`router.use(path,
823
1028
  // f)` inside `use(fn)`'s forEach closure) used to slip past to
824
1029
  // binding resolution and exact-confirm on the shadowed name.
825
- if (call.localShadow && !call.isPotentialCallback) {
1030
+ if (call.localShadow && !call.isPotentialCallback &&
1031
+ !shadowCanReachPinnedTarget(filePath, call)) {
826
1032
  recordExcluded(filePath, call.line, 'local-shadow');
827
1033
  continue;
828
1034
  }
@@ -838,7 +1044,7 @@ function findCallers(index, name, options = {}) {
838
1044
  // Super records resolve only through the parent-chain walk.
839
1045
  const selfReceivers = new Set(['self', 'cls', 'this']);
840
1046
  const indirectStructuralReceiver = call.isMethod && !call.receiver &&
841
- !!(call.receiverRoot || call.receiverField || call.receiverCall);
1047
+ !!(call.receiverRoot || call.receiverField || call.receiverCall || call.receiverType);
842
1048
  const skipLocalBinding = (call.receiver && !selfReceivers.has(call.receiver)) ||
843
1049
  indirectStructuralReceiver;
844
1050
  if (!bindingId && !skipLocalBinding) {
@@ -1389,7 +1595,7 @@ function findCallers(index, name, options = {}) {
1389
1595
  // modules must not exclude: a binding to an unresolved module
1390
1596
  // whose first segment matches a project directory routes
1391
1597
  // visible instead.
1392
- if (!bindingId && !call.isMethod && !calledAs &&
1598
+ if (!bindingId && !call.isMethod &&
1393
1599
  langTraits(fileEntry.language)?.typeSystem === 'structural' &&
1394
1600
  (fileEntry.importBindings || []).length > 0) {
1395
1601
  // Renamed destructures look up by the RESOLVED name (fix
@@ -1400,7 +1606,31 @@ function findCallers(index, name, options = {}) {
1400
1606
  // ownership chase and the call scope-confirmed against
1401
1607
  // hooks.js's unrelated validate.
1402
1608
  const lookupName = call.resolvedName || call.name;
1403
- let nameBindings = fileEntry.importBindings.filter(b => b.name === lookupName);
1609
+ // Alias-matched surfaces (calledAs import/export renames)
1610
+ // spell the call by the LOCAL alias while importBindings
1611
+ // store the ORIGINAL name: `import { _gt as gt }` binds
1612
+ // name '_gt' with alias 'gt'. Match the alias side too so
1613
+ // the #217 chase adjudicates the rename by NAME (borrowed
1614
+ // renames still exclude other-definition-import) instead
1615
+ // of the empty lookup excluding every legitimate renamed
1616
+ // call as name-not-in-scope.
1617
+ let nameBindings = fileEntry.importBindings.filter(b =>
1618
+ b.name === lookupName ||
1619
+ (calledAs && b.alias === lookupName));
1620
+ // Python bindings carry no alias field — the pairing
1621
+ // lives in fileEntry.importAliases ({original, local});
1622
+ // map the alias back to its original's binding so the
1623
+ // chase adjudicates `from lib import transform as t`.
1624
+ if (calledAs && nameBindings.length === 0 &&
1625
+ (fileEntry.importAliases || []).length > 0) {
1626
+ const originals = new Set(fileEntry.importAliases
1627
+ .filter(a => a && a.local === lookupName && a.original)
1628
+ .map(a => a.original));
1629
+ if (originals.size > 0) {
1630
+ nameBindings = fileEntry.importBindings.filter(b =>
1631
+ originals.has(b.name));
1632
+ }
1633
+ }
1404
1634
  // Exact alias pairing (fix #269): two renames of one
1405
1635
  // source name from DIFFERENT modules — the record's local
1406
1636
  // alias (call.name) picks its own binding; source-name
@@ -1871,7 +2101,8 @@ function findCallers(index, name, options = {}) {
1871
2101
  // from the producing annotation's scope — the name
1872
2102
  // was written THERE, not in the consuming file.
1873
2103
  const identity = _resolveReceiverTypeIdentity(
1874
- index, call.receiverTypeFlowFile || filePath, knownType, targetDefs);
2104
+ index, call.receiverTypeFlowFile || filePath, knownType, targetDefs,
2105
+ call.receiverTypeFlowFile ? undefined : call.line);
1875
2106
  if (identity === 'other') {
1876
2107
  receiverTypeValidated = false;
1877
2108
  } else if (identity === 'unknown') {
@@ -1899,6 +2130,19 @@ function findCallers(index, name, options = {}) {
1899
2130
  }
1900
2131
  }
1901
2132
  }
2133
+ } else if (structural) {
2134
+ // Name-only subtype sets cannot represent an
2135
+ // imported parent that is exposed under a
2136
+ // different type name. Resolve the receiver's
2137
+ // exact declaration and walk its file-scoped
2138
+ // ancestry before treating it as unrelated.
2139
+ const identity = _resolveStructuralFlowTypeIdentity(
2140
+ index, call.receiverTypeFlowFile || filePath, knownType, targetDefs);
2141
+ if (identity === 'target') {
2142
+ receiverTypeValidated = true;
2143
+ } else if (identity === 'unknown') {
2144
+ receiverTypeUnresolved = true;
2145
+ }
1902
2146
  }
1903
2147
  const matchesTarget = receiverTypeValidated ||
1904
2148
  ((structural || viaFieldHop) && _isAncestorOfTargetClass(index, knownType, targetDefs));
@@ -2052,7 +2296,7 @@ function findCallers(index, name, options = {}) {
2052
2296
  // target's package, not a same-named foreign type.
2053
2297
  let identity = 'target';
2054
2298
  if (targetDefs.some(d => (d.className || (d.receiver || '').replace(/^\*/, '')) === inferredType)) {
2055
- identity = _resolveReceiverTypeIdentity(index, filePath, inferredType, targetDefs);
2299
+ identity = _resolveReceiverTypeIdentity(index, filePath, inferredType, targetDefs, call.line);
2056
2300
  }
2057
2301
  if (identity === 'target') {
2058
2302
  inferredMatch = true;
@@ -2110,7 +2354,7 @@ function findCallers(index, name, options = {}) {
2110
2354
  // its type is unknown, identity proves nothing.
2111
2355
  if (matchesTarget && call.isPathCall &&
2112
2356
  langTraits(fileEntry.language)?.typeQualifiedCallStyle === 'path') {
2113
- const identity = _resolveReceiverTypeIdentity(index, filePath, call.receiver, targetDefs);
2357
+ const identity = _resolveReceiverTypeIdentity(index, filePath, call.receiver, targetDefs, call.line);
2114
2358
  if (identity === 'other') {
2115
2359
  isUncertain = true;
2116
2360
  typeMismatch = true;
@@ -2383,7 +2627,7 @@ function findCallers(index, name, options = {}) {
2383
2627
  // name fallback above handles multi-definition names; this
2384
2628
  // covers single-definition targets that skip it.
2385
2629
  if (typeQualifiedReceiver) {
2386
- const identity = _resolveReceiverTypeIdentity(index, filePath, receiverName, targetDefs2);
2630
+ const identity = _resolveReceiverTypeIdentity(index, filePath, receiverName, targetDefs2, call.line);
2387
2631
  if (identity === 'other') {
2388
2632
  recordExcluded(filePath, call.line, 'path-type-mismatch');
2389
2633
  continue;
@@ -2723,6 +2967,30 @@ function findCallers(index, name, options = {}) {
2723
2967
  if (call.isMethod && !call.receiverIsModule && !recvSubmoduleRel) {
2724
2968
  const tTypes = dispatchTargetTypes(targetDefs2);
2725
2969
  const typeQualifiedReceiver = !!(call.receiver && tTypes.has(call.receiver));
2970
+ // A local receiver whose nearest assignment the flow
2971
+ // machinery examined and could NOT type holds a value
2972
+ // of unknown provenance (`local = factory()`) — the
2973
+ // #222(4) evidence class with a local producer, so
2974
+ // single-owner spelling must not confirm it. A bare
2975
+ // never-assigned parameter has no such evidence and
2976
+ // keeps the measured #204/#209 single-owner physics
2977
+ // (`run(codec) { codec.decodeFrames(...) }` stays
2978
+ // confirmed when Codec is the one project owner).
2979
+ if (!typeQualifiedReceiver && call.receiverLocalBinding &&
2980
+ !call.receiverType && !fieldHopType && !fieldDispatchType &&
2981
+ !call.receiverExternalFlow && !call.receiverQualifiedFlow) {
2982
+ let demoteFlowMap = returnFlowCache.get(filePath);
2983
+ if (demoteFlowMap === undefined) {
2984
+ demoteFlowMap = _buildReturnTypeFlowMap(index, filePath, calls);
2985
+ returnFlowCache.set(filePath, demoteFlowMap);
2986
+ }
2987
+ if (_receiverAssignedUntyped(demoteFlowMap, call)) {
2988
+ routeUnverified(filePath, fileEntry, call, 'possible-dispatch', calledAs, {
2989
+ dispatchVia: call.receiverRootType || 'local receiver',
2990
+ });
2991
+ continue;
2992
+ }
2993
+ }
2726
2994
  // External-producer receiver (fix #222, httpx-measured
2727
2995
  // — the #220 Go rule for structural languages): the
2728
2996
  // variable was assigned from a call into an external
@@ -2898,9 +3166,13 @@ function findCallers(index, name, options = {}) {
2898
3166
  // Method calls where binding resolution was skipped (non-self receiver)
2899
3167
  // and the receiver has no binding evidence → uncertain (JS/TS/Python only)
2900
3168
  isUncertain: !!isUncertain || uncertainMethodReceiver,
2901
- hasReceiverType: langTraits(fileEntry.language)?.typeSystem === 'structural'
2902
- ? (receiverTypeValidated || resolvedByTypedAttribute)
2903
- : !!call.receiverType,
3169
+ // Only a receiver type that was validated against the
3170
+ // pinned target earns receiver-hint confidence. Raw
3171
+ // nominal annotations may name an interface, external
3172
+ // type, or unresolved same-name owner; declared-field
3173
+ // hops and local inference participate once validated.
3174
+ hasReceiverType: !!(receiverTypeValidated ||
3175
+ resolvedByTypedAttribute || nominalInferredMatch),
2904
3176
  hasReceiverEvidence: !!(call.receiver &&
2905
3177
  (fileEntry.bindings || []).some(b => b.name === call.receiver)),
2906
3178
  hasImportEvidence: !!bindingId || hasImportLink || !!call.moduleOwnedPath,
@@ -3312,6 +3584,21 @@ function findCallees(index, definition, options = {}) {
3312
3584
  if (!isDirectMatch && !isNestedCallback) continue;
3313
3585
  if (calleeAccount) calleeAccount.totalSites++;
3314
3586
 
3587
+ // Parser-proven lexical shadow: a parameter/local named `option`
3588
+ // cannot call an imported/project `option`. A nested local
3589
+ // function with that name is the one safe exception and remains
3590
+ // eligible for exact same-file resolution below.
3591
+ if (call.localShadow && !call.resolvedName && !call.resolvedNames) {
3592
+ const localTarget = (index.symbols.get(call.name) || []).some(s =>
3593
+ s.file === def.file && !NON_CALLABLE_TYPES.has(s.type) &&
3594
+ s.startLine >= def.startLine && s.endLine <= def.endLine &&
3595
+ s.startLine <= call.line);
3596
+ if (!localTarget) {
3597
+ noteSite(siteId, 'excluded', 'local-shadow', call);
3598
+ continue;
3599
+ }
3600
+ }
3601
+
3315
3602
  // Query-time return flow for an ordinary receiver assignment:
3316
3603
  // `v := New(); v.ReadConfig()`. The compiler-declared return type
3317
3604
  // is stronger than constructor-name guesses and must participate
@@ -3511,7 +3798,8 @@ function findCallees(index, definition, options = {}) {
3511
3798
  externalContract: true,
3512
3799
  });
3513
3800
  continue;
3514
- } else if (localTypes && localTypes.has(call.receiver) && !directReceiverFlow?.type) {
3801
+ } else if (!call.receiverType && localTypes &&
3802
+ localTypes.has(call.receiver) && !directReceiverFlow?.type) {
3515
3803
  // Resolve method calls on locally-constructed objects:
3516
3804
  // bt = Backtester(...); bt.run_backtest() → Backtester.run_backtest
3517
3805
  // Go: f.Run() where f is *Framework → Framework.Run (receiver match)
@@ -3728,7 +4016,21 @@ function findCallees(index, definition, options = {}) {
3728
4016
  });
3729
4017
  continue;
3730
4018
  }
3731
- // No match found with inferred type fall through to include as unresolved
4019
+ // A compiler-typed nominal receiver that does not resolve
4020
+ // to a method on that type must never fall through to the
4021
+ // generic same-name ranking. The receiver may name an
4022
+ // external interface or an unindexed ancestor, so keep
4023
+ // the dispatch visible without inventing one concrete
4024
+ // project callee.
4025
+ if (collectAccount && langTraits(language)?.typeSystem === 'nominal') {
4026
+ noteUnverified(siteId, call, 'possible-dispatch', {
4027
+ dispatchVia: typeName,
4028
+ dispatchCandidates: _countDispatchCandidates(
4029
+ index, typeName, symbols || []),
4030
+ });
4031
+ continue;
4032
+ }
4033
+ // Structural legacy mode retains its historical fallback.
3732
4034
  } else if (call.receiverCall && (!call.receiver || call.receiverIsChainRoot) && !call.isConstructor &&
3733
4035
  langTraits(language)?.typeSystem === 'nominal') {
3734
4036
  // Chained receiver, nominal callee direction (fix #268,
@@ -3851,7 +4153,7 @@ function findCallees(index, definition, options = {}) {
3851
4153
  });
3852
4154
  continue;
3853
4155
  }
3854
- if (collectAccount && call.receiver && !call.receiverType &&
4156
+ if (collectAccount && call.receiver && !call.receiverIsModule && !call.receiverType &&
3855
4157
  !call.receiverCall && !call.isPotentialCallback &&
3856
4158
  !['self', 'cls', 'this', 'super', 'Self'].includes(call.receiver)) {
3857
4159
  const owners = new Set((index.symbols.get(call.name) || [])
@@ -3882,11 +4184,21 @@ function findCallees(index, definition, options = {}) {
3882
4184
  const shadowsBuiltin = !call.receiver && (
3883
4185
  fileEntry?.importBindings?.some(b => b.name === call.name) ||
3884
4186
  fileEntry?.bindings?.some(b => b.name === call.name));
3885
- if (!selfShaped && !shadowsBuiltin && index.isKeyword(call.name, language)) {
4187
+ if (!selfShaped && !call.receiverIsModule && !shadowsBuiltin &&
4188
+ index.isKeyword(call.name, language)) {
3886
4189
  noteSite(siteId, 'external', null, call);
3887
4190
  continue;
3888
4191
  }
3889
4192
 
4193
+ // A local alias such as `get_style = console.get_style` preserves
4194
+ // the callable relationship, but the surface spelling no longer
4195
+ // lets a definition oracle verify the target directly. Keep it
4196
+ // visible without presenting it as an exact callee.
4197
+ if (call.aliasCall) {
4198
+ noteUnverified(siteId, call, 'alias-call');
4199
+ continue;
4200
+ }
4201
+
3890
4202
  // Use resolved name (from alias tracking) if available
3891
4203
  // For multi-target aliases (ternary), pick the first that exists in symbol table
3892
4204
  let effectiveName = call.resolvedName || call.name;
@@ -3959,7 +4271,7 @@ function findCallees(index, definition, options = {}) {
3959
4271
  existing.count += 1;
3960
4272
  if (collectAccount) { existing.sites.push(call.line); existing.siteIds.push(siteId); }
3961
4273
  } else {
3962
- callees.set(key, { name: effectiveName, bindingId: match.bindingId, count: 1,
4274
+ callees.set(key, { name: match.name, bindingId: match.bindingId, count: 1,
3963
4275
  ...(collectAccount && { sites: [call.line], siteIds: [siteId] }) });
3964
4276
  }
3965
4277
  }
@@ -3991,7 +4303,7 @@ function findCallees(index, definition, options = {}) {
3991
4303
  existing.count += 1;
3992
4304
  if (collectAccount) { existing.sites.push(call.line); existing.siteIds.push(siteId); }
3993
4305
  } else {
3994
- callees.set(key, { name: effectiveName, bindingId: match.bindingId, count: 1,
4306
+ callees.set(key, { name: match.name, bindingId: match.bindingId, count: 1,
3995
4307
  ...(collectAccount && { sites: [call.line], siteIds: [siteId] }) });
3996
4308
  }
3997
4309
  }
@@ -5363,6 +5675,28 @@ function _lookupReturnTypeFlow(map, call) {
5363
5675
  return undefined;
5364
5676
  }
5365
5677
 
5678
+ // True when the receiver's NEAREST preceding assignment is a flow tombstone —
5679
+ // the variable provably holds a value the type machinery examined and could
5680
+ // not type (`local = factory()`). This is the #222(4) unknown-provenance
5681
+ // evidence class with a local producer: it demotes single-owner confirmation.
5682
+ // A never-assigned parameter has no entries and keeps the documented
5683
+ // #204/#209 single-owner physics — parameter-ness alone is not provenance
5684
+ // evidence against the one project owner.
5685
+ function _receiverAssignedUntyped(map, call) {
5686
+ if (!map || !call.receiver) return false;
5687
+ const fnScope = call.enclosingFunction ? `${call.enclosingFunction.startLine}` : '';
5688
+ for (const scope of fnScope === '' ? [''] : [fnScope, '']) {
5689
+ const entries = map.get(`${scope}:${call.receiver}`);
5690
+ if (!entries) continue;
5691
+ let best = null;
5692
+ for (const e of entries) {
5693
+ if (e.line < call.line && (!best || e.line > best.line)) best = e;
5694
+ }
5695
+ if (best) return !!best.invalidated;
5696
+ }
5697
+ return false;
5698
+ }
5699
+
5366
5700
  // Return-annotation names that must never type a receiver in nominal flow:
5367
5701
  // builtin interfaces/primitives whose project implementors UCN cannot see —
5368
5702
  // a receiver typed `error` CAN dispatch into a project type's Error() method,
@@ -5522,6 +5856,48 @@ function _resolveFlowTypeOrigin(index, producerFile, typeName, qualifier) {
5522
5856
  }
5523
5857
  const sameFile = typeDefs.find(d => d.file === producerFile);
5524
5858
  if (sameFile) return finish({ fromFile: sameFile.file });
5859
+ // An explicit imported name outranks directory proximity. Structural
5860
+ // modules commonly contain several same-named classes in sibling files;
5861
+ // `import { Hono } from './hono'` still pins one declaration exactly.
5862
+ const producerEntry = index.files.get(producerFile);
5863
+ const namedBindings = (producerEntry?.importBindings || []).filter(b =>
5864
+ b.name === typeName || b.alias === typeName);
5865
+ if (namedBindings.length > 0) {
5866
+ const resolvedFiles = new Set();
5867
+ for (const b of namedBindings) {
5868
+ const rel = producerEntry.moduleResolved?.[b.module];
5869
+ if (rel) resolvedFiles.add(path.join(index.root, rel));
5870
+ }
5871
+ const bound = typeDefs.filter(d => resolvedFiles.has(d.file));
5872
+ if (new Set(bound.map(d => d.file)).size === 1) {
5873
+ return finish({ fromFile: bound[0].file });
5874
+ }
5875
+ // The binding may land on a barrel. Chase the imported NAME through
5876
+ // modeled re-exports, and pin only one reachable declaration.
5877
+ const reachable = typeDefs.filter(d => [...resolvedFiles].some(start =>
5878
+ _nameBindingReaches(index, start, typeName, new Set([d.file])) === 'yes'));
5879
+ if (new Set(reachable.map(d => d.file)).size === 1) {
5880
+ return finish({ fromFile: reachable[0].file });
5881
+ }
5882
+ // An UN-modelable export surface is 'unknown', never negative
5883
+ // evidence (#217) — Rust `pub use` chains defeat the name-level
5884
+ // chase, and returning null here unpinned every `use clap::Command`
5885
+ // fold root (clap confirmed placement 1432 → 10). Fall back to the
5886
+ // measured #258 FILE-level unique-reach rail, anchored at the
5887
+ // binding's own resolved module files so same-directory declarations
5888
+ // stay shadowed (the structural parallel-API protection) and only
5889
+ // the declared module's closure can pin.
5890
+ if (resolvedFiles.size > 0) {
5891
+ const fileReachable = typeDefs.filter(d => [...resolvedFiles].some(start =>
5892
+ start === d.file || _importReaches(index, start, new Set([d.file]))));
5893
+ if (new Set(fileReachable.map(d => d.file)).size === 1) {
5894
+ return finish({ fromFile: fileReachable[0].file });
5895
+ }
5896
+ }
5897
+ // An explicit binding shadows same-directory declarations even when
5898
+ // its external or dynamic export surface cannot be resolved.
5899
+ return finish(null);
5900
+ }
5525
5901
  const dir = path.dirname(producerFile);
5526
5902
  const sameDir = typeDefs.filter(d => path.dirname(d.file) === dir);
5527
5903
  if (sameDir.length === 1) return finish({ fromFile: sameDir[0].file });
@@ -6060,19 +6436,46 @@ function _sameNominalPackageDir(dirA, dirB, language) {
6060
6436
  return a === norm(dirB);
6061
6437
  }
6062
6438
 
6063
- function _resolveReceiverTypeIdentity(index, filePath, knownType, targetDefs) {
6439
+ function _resolveReceiverTypeIdentity(index, filePath, knownType, targetDefs, line) {
6064
6440
  const typeDefs = (index.symbols.get(knownType) || []).filter(d => IDENTITY_TYPE_KINDS.has(d.type));
6065
6441
  if (typeDefs.length <= 1) return 'target';
6066
- const language = index.files.get(filePath)?.language;
6442
+ const fileEntry = index.files.get(filePath);
6443
+ const language = fileEntry?.language;
6067
6444
  const targetDirs = new Set(targetDefs.map(d => d.file && path.dirname(d.file)).filter(Boolean));
6068
6445
  const inTargetPkg = (d) => d.file && [...targetDirs].some(dir =>
6069
6446
  _sameNominalPackageDir(path.dirname(d.file), dir, language));
6070
- const sameFile = typeDefs.filter(d => d.file === filePath);
6447
+ // Explicit imports outrank package proximity. This matters in Java files
6448
+ // that sit beside `Token.Comment` but import `org.jsoup.nodes.Comment`.
6449
+ // A nested type is not a package member and cannot win a bare type lookup.
6450
+ const namedBindings = (fileEntry?.importBindings || []).filter(b =>
6451
+ b.name === knownType || b.alias === knownType);
6452
+ if (namedBindings.length > 0) {
6453
+ const resolved = new Set(namedBindings.map(b => fileEntry.moduleResolved?.[b.module])
6454
+ .filter(Boolean).map(rel => path.join(index.root, rel)));
6455
+ const imported = typeDefs.filter(d => resolved.has(d.file));
6456
+ if (imported.length > 0) return imported.some(inTargetPkg) ? 'target' : 'other';
6457
+ return 'unknown';
6458
+ }
6459
+ const callerClass = line != null
6460
+ ? index.findEnclosingFunction(filePath, line, true)?.className : null;
6461
+ const sameFile = typeDefs.filter(d => d.file === filePath &&
6462
+ (!d.enclosingType || d.enclosingType === callerClass));
6071
6463
  if (sameFile.length > 0) return sameFile.some(inTargetPkg) ? 'target' : 'other';
6072
6464
  const callerDir = path.dirname(filePath);
6073
6465
  const sameDir = typeDefs.filter(d => d.file &&
6466
+ !d.enclosingType &&
6074
6467
  _sameNominalPackageDir(path.dirname(d.file), callerDir, language));
6075
6468
  if (sameDir.length > 0) return sameDir.some(inTargetPkg) ? 'target' : 'other';
6469
+ if (language === 'java') {
6470
+ const wildcardPackages = (fileEntry?.imports || [])
6471
+ .filter(m => String(m).endsWith('.*'))
6472
+ .map(m => String(m).slice(0, -2));
6473
+ if (wildcardPackages.length > 0) {
6474
+ const imported = typeDefs.filter(d => !d.enclosingType && d.relativePath &&
6475
+ wildcardPackages.includes(_javaPackageKey(d.relativePath).replace(/\//g, '.')));
6476
+ if (imported.length > 0) return imported.some(inTargetPkg) ? 'target' : 'other';
6477
+ }
6478
+ }
6076
6479
  const imports = index.importGraph.get(filePath);
6077
6480
  if (imports) {
6078
6481
  const imported = typeDefs.filter(d => d.file && imports.has(d.file));
@@ -6107,7 +6510,7 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
6107
6510
  const files = new Set();
6108
6511
  for (const mf of methodFiles) {
6109
6512
  for (const td of typeDefs) {
6110
- if (td.file === mf || path.dirname(td.file) === path.dirname(mf)) files.add(td.file);
6513
+ if (td.file === mf) files.add(td.file);
6111
6514
  }
6112
6515
  if (files.size === 0) files.add(mf);
6113
6516
  }
@@ -6118,28 +6521,70 @@ function _resolveStructuralFlowTypeIdentity(index, originFile, knownType, target
6118
6521
  if (!file) return 'unknown';
6119
6522
  const files = targetFiles.get(name) || new Set();
6120
6523
  if (files.has(file)) return 'target';
6121
- const sameDir = [...files].some(f => path.dirname(f) === path.dirname(file));
6122
- return sameDir ? 'target' : 'other';
6524
+ return 'other';
6123
6525
  };
6124
6526
  const direct = ownerIdentity(knownType, origin.fromFile);
6125
- if (direct) return direct;
6527
+ if (direct === 'target') return direct;
6528
+
6529
+ const targetKeys = new Set(targetDefs
6530
+ .filter(d => d.file && d.startLine != null)
6531
+ .map(d => `${d.file}:${d.startLine}`));
6532
+ const overridesPinnedSlot = (name, file) => {
6533
+ if (!file) return false;
6534
+ const methodName = targetDefs[0]?.name;
6535
+ if (!methodName) return false;
6536
+ return (index.symbols.get(methodName) || []).some(d =>
6537
+ d.file === file && d.className === name &&
6538
+ !targetKeys.has(`${d.file}:${d.startLine}`));
6539
+ };
6540
+
6541
+ // Resolve a parent as written in the child file. Import bindings know the
6542
+ // module file, while export details recover the source-side type name for
6543
+ // `export { Internal as PublicBase }`. Keeping both pieces prevents
6544
+ // same-named classes in other files from being conflated.
6545
+ const parentOrigin = (parent, childFile) => {
6546
+ const fe = index.files.get(childFile);
6547
+ if (fe) {
6548
+ const alias = (fe.importAliases || []).find(a => a.local === parent);
6549
+ const importedName = alias?.original || parent;
6550
+ const binding = (fe.importBindings || []).find(b =>
6551
+ b.name === importedName || b.alias === parent);
6552
+ const rel = binding && fe.moduleResolved?.[binding.module];
6553
+ if (rel) {
6554
+ const parentFile = path.join(index.root, rel);
6555
+ const parentEntry = index.files.get(parentFile);
6556
+ const exported = (parentEntry?.exportDetails || []).find(e =>
6557
+ (e.alias || e.name) === importedName);
6558
+ const canonical = exported?.name || importedName;
6559
+ const ownsCanonical = (index.symbols.get(canonical) || []).some(d =>
6560
+ IDENTITY_TYPE_KINDS.has(d.type) && d.file === parentFile);
6561
+ return { name: ownsCanonical ? canonical : importedName, file: parentFile };
6562
+ }
6563
+ }
6564
+ const parentFile = index._resolveClassFile
6565
+ ? index._resolveClassFile(parent, childFile) : undefined;
6566
+ return { name: parent, file: parentFile };
6567
+ };
6126
6568
 
6127
6569
  const queue = [{ name: knownType, file: origin.fromFile }];
6128
6570
  const visited = new Set();
6129
- let sawUnresolved = false;
6571
+ let sawUnresolved = direct === 'unknown';
6130
6572
  while (queue.length > 0 && visited.size < 128) {
6131
6573
  const cur = queue.shift();
6132
6574
  const key = `${cur.file || ''}\0${cur.name}`;
6133
6575
  if (visited.has(key)) continue;
6134
6576
  visited.add(key);
6577
+ // An override on the concrete receiver intercepts dispatch before the
6578
+ // ancestor slot. In that case ancestry is evidence for the other
6579
+ // definition, not the pinned one.
6580
+ if (overridesPinnedSlot(cur.name, cur.file)) continue;
6135
6581
  const parents = index._getInheritanceParents(cur.name, cur.file) || [];
6136
6582
  for (const parent of parents) {
6137
- const pFile = index._resolveClassFile
6138
- ? index._resolveClassFile(parent, cur.file) : undefined;
6139
- const verdict = ownerIdentity(parent, pFile);
6583
+ const edge = parentOrigin(parent, cur.file);
6584
+ const verdict = ownerIdentity(edge.name, edge.file);
6140
6585
  if (verdict === 'target') return 'target';
6141
6586
  if (verdict === 'unknown') sawUnresolved = true;
6142
- if (pFile) queue.push({ name: parent, file: pFile });
6587
+ if (edge.file) queue.push(edge);
6143
6588
  else sawUnresolved = true;
6144
6589
  }
6145
6590
  }
@@ -6355,7 +6800,7 @@ function _goQualifierNamesImport(index, fieldFile, qualifier) {
6355
6800
  function _calleeStructuralModuleRoute(index, fileEntry, call, language) {
6356
6801
  const bindings = (fileEntry?.importBindings || []).filter(b => b.name === call.receiver);
6357
6802
  if (bindings.length === 0) return { unknown: true };
6358
- return _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, call.name);
6803
+ return _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, call.name, false);
6359
6804
  }
6360
6805
 
6361
6806
  function _calleeStructuralImportedNameRoute(index, fileEntry, call, language) {
@@ -6366,15 +6811,10 @@ function _calleeStructuralImportedNameRoute(index, fileEntry, call, language) {
6366
6811
  if (paired.length > 0) bindings = paired;
6367
6812
  }
6368
6813
  if (bindings.length === 0) return null;
6369
- return _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, lookupName);
6814
+ return _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, lookupName, true);
6370
6815
  }
6371
6816
 
6372
- function _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, exportName) {
6373
- const candidates = (index.symbols.get(exportName) || []).filter(d =>
6374
- (!NON_CALLABLE_TYPES.has(d.type) ||
6375
- (call.isConstructor && d.type === 'class') ||
6376
- (langTraits(language)?.classesCallableWithoutNew && d.type === 'class')) &&
6377
- _calleeLanguageCompatible(index, d, language));
6817
+ function _calleeStructuralBindingRoute(index, fileEntry, call, language, bindings, exportName, allowDefaultExport) {
6378
6818
  const matches = new Map();
6379
6819
  let sawProjectish = false;
6380
6820
  let sawUnknown = false;
@@ -6392,17 +6832,105 @@ function _calleeStructuralBindingRoute(index, fileEntry, call, language, binding
6392
6832
  }
6393
6833
  sawProjectish = true;
6394
6834
  const moduleFile = path.join(index.root, rel);
6395
- for (const d of candidates) {
6396
- const verdict = _nameBindingReaches(index, moduleFile, exportName, new Set([d.file]));
6397
- if (verdict === 'yes') matches.set(`${d.file}:${d.startLine}`, d);
6398
- else if (verdict === 'unknown') sawUnknown = true;
6835
+ const routed = _calleeExportDefinitions(index, moduleFile, exportName, language, call, {
6836
+ allowDefaultExport: allowDefaultExport && !!binding.defaultLike,
6837
+ });
6838
+ for (const d of routed.matches) {
6839
+ matches.set(`${d.file}:${d.startLine}`, d);
6399
6840
  }
6841
+ if (routed.unknown) sawUnknown = true;
6400
6842
  }
6401
6843
  if (matches.size > 0) return { matches: [...matches.values()] };
6402
6844
  if (!sawProjectish && !sawUnknown) return { external: true };
6403
6845
  return { unknown: true };
6404
6846
  }
6405
6847
 
6848
+ // Resolve a module attribute to its implementation definition while
6849
+ // preserving identity across ESM/TS barrel renames and Python re-export
6850
+ // imports. Searching only symbols named with the public spelling loses
6851
+ // `export { _gt as gt }`, because its implementation is named `_gt`.
6852
+ function _calleeExportDefinitions(index, startAbs, exposedName, language, call, options = {}) {
6853
+ const matches = new Map();
6854
+ const visited = new Set();
6855
+ let unknown = false;
6856
+ let frontier = [[startAbs, exposedName]];
6857
+ const shapeMatches = d =>
6858
+ (!NON_CALLABLE_TYPES.has(d.type) ||
6859
+ (call.isConstructor && d.type === 'class') ||
6860
+ (langTraits(language)?.classesCallableWithoutNew && d.type === 'class')) &&
6861
+ _calleeLanguageCompatible(index, d, language);
6862
+
6863
+ for (let depth = 0; depth <= 4 && frontier.length > 0; depth++) {
6864
+ const next = [];
6865
+ for (const [abs, attr] of frontier) {
6866
+ const stateKey = `${abs}\x00${attr}`;
6867
+ if (visited.has(stateKey)) continue;
6868
+ visited.add(stateKey);
6869
+ const fe = index.files.get(abs);
6870
+ if (!fe) { unknown = true; continue; }
6871
+
6872
+ const enqueue = (module, nextAttr) => {
6873
+ const rel = fe.moduleResolved && fe.moduleResolved[module];
6874
+ if (!rel) {
6875
+ const mod = String(module || '');
6876
+ const firstSeg = mod.split(/[./]/).filter(Boolean)[0];
6877
+ if (mod.startsWith('.') ||
6878
+ (firstSeg && _projectTopLevelNames(index).has(firstSeg))) unknown = true;
6879
+ return;
6880
+ }
6881
+ next.push([path.join(index.root, rel), nextAttr]);
6882
+ };
6883
+
6884
+ const details = fe.exportDetails || [];
6885
+ const localExposed = fe.language === 'python' ||
6886
+ (fe.exports || []).includes(attr) ||
6887
+ details.some(e => !e.source && (e.alias || e.name) === attr);
6888
+ if (localExposed) {
6889
+ for (const d of (index.symbols.get(attr) || [])) {
6890
+ if (d.file === abs && shapeMatches(d)) {
6891
+ matches.set(`${d.file}:${d.startLine}`, d);
6892
+ }
6893
+ }
6894
+ }
6895
+
6896
+ // A simple CommonJS require binds the value assigned directly to
6897
+ // module.exports. This applies to a bare imported-name call only,
6898
+ // never to namespace.member() routing.
6899
+ if (options.allowDefaultExport && depth === 0) {
6900
+ for (const e of details) {
6901
+ if (e.type !== 'module.exports' || !e.name) continue;
6902
+ for (const d of (index.symbols.get(e.name) || [])) {
6903
+ if (d.file === abs && shapeMatches(d)) {
6904
+ matches.set(`${d.file}:${d.startLine}`, d);
6905
+ }
6906
+ }
6907
+ }
6908
+ }
6909
+
6910
+ for (const e of details) {
6911
+ if (!e.source) continue;
6912
+ if (e.type === 're-export' && (e.alias || e.name) === attr) enqueue(e.source, e.name);
6913
+ else if (e.type === 're-export-all') {
6914
+ if (e.alias) { if (e.alias === attr) unknown = true; }
6915
+ else enqueue(e.source, attr);
6916
+ }
6917
+ }
6918
+ const aliases = fe.importAliases || [];
6919
+ for (const b of (fe.importBindings || [])) {
6920
+ const exposed = [b.alias || b.name,
6921
+ ...aliases.filter(a => a.original === b.name).map(a => a.local)];
6922
+ if (exposed.includes(attr)) enqueue(b.module, b.name);
6923
+ }
6924
+ if ((fe.importNames || []).includes('*')) unknown = true;
6925
+ if ((fe.moduleAssignedNames || []).includes(attr)) unknown = true;
6926
+ if ((index.symbols.get('__getattr__') || []).some(s => s.file === abs && !s.className)) unknown = true;
6927
+ }
6928
+ frontier = next;
6929
+ }
6930
+ if (frontier.length > 0) unknown = true;
6931
+ return { matches: [...matches.values()], unknown };
6932
+ }
6933
+
6406
6934
  function _calleeGoPackageMatch(index, call, importModule) {
6407
6935
  const allSymbols = index.symbols.get(call.name);
6408
6936
  if (!allSymbols) return null;
@@ -7260,6 +7788,28 @@ function _buildTargetTypeSet(index, targetDefs, definitions) {
7260
7788
  }
7261
7789
  }
7262
7790
  }
7791
+ // Rust Deref changes method lookup identity: methods on Target are
7792
+ // callable through the wrapper value. Close only over wrappers whose
7793
+ // indexed type definitions all agree on one Deref target.
7794
+ if (targetTypes.size > 0) {
7795
+ const derefPairs = [];
7796
+ for (const [wrapper, defs] of index.symbols) {
7797
+ const typeDefs = defs.filter(d => IDENTITY_TYPE_KINDS.has(d.type));
7798
+ if (typeDefs.length === 0 || !typeDefs.every(d => d.derefTarget)) continue;
7799
+ const targets = new Set(typeDefs.map(d => d.derefTarget));
7800
+ if (targets.size === 1) derefPairs.push([wrapper, [...targets][0]]);
7801
+ }
7802
+ let changed = derefPairs.length > 0;
7803
+ while (changed) {
7804
+ changed = false;
7805
+ for (const [wrapper, target] of derefPairs) {
7806
+ if (targetTypes.has(target) && !targetTypes.has(wrapper)) {
7807
+ targetTypes.add(wrapper);
7808
+ changed = true;
7809
+ }
7810
+ }
7811
+ }
7812
+ }
7263
7813
  // Type aliases are the SAME type (Rust `pub type StyledString =
7264
7814
  // SpannedString<Style>`, Go `type A = B`) — compiler-checked identity,
7265
7815
  // not a subtype edge. Close over them in BOTH directions: a method on
@@ -7720,6 +8270,21 @@ const _STRUCTURAL_FLOW_REJECT = new Set([
7720
8270
  'this', 'Self', 'Object', 'None',
7721
8271
  ]);
7722
8272
 
8273
+ // Small, stable stdlib contracts used only to type the value of a chained
8274
+ // receiver. These are runtime identity facts, not guesses about user code.
8275
+ function _builtinMethodReturnType(language, receiverType, methodName) {
8276
+ if (language !== 'python') return null;
8277
+ const owner = String(receiverType || '').split('.').pop();
8278
+ if (methodName === 'getvalue') {
8279
+ if (owner === 'BytesIO') return 'bytes';
8280
+ if (owner === 'StringIO') return 'str';
8281
+ }
8282
+ if (owner === 'bytes' && methodName === 'decode') return 'str';
8283
+ if (owner === 'str' && methodName === 'encode') return 'bytes';
8284
+ if (['list', 'dict', 'set'].includes(owner) && methodName === 'copy') return owner;
8285
+ return null;
8286
+ }
8287
+
7723
8288
  /**
7724
8289
  * Type a chained receiver from its producer's declared return annotation
7725
8290
  * (fix #219): `parseAsync(args).catch(...)` — the receiver of .catch IS the
@@ -8125,6 +8690,16 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
8125
8690
  }
8126
8691
  if (rt && rt.externalVia) return rt;
8127
8692
  if (rt && rt.type) {
8693
+ // Stdlib return contracts apply only when no project type shadows
8694
+ // the name (#232 shadowing rule): a project class named StringIO
8695
+ // keeps its own annotations authoritative.
8696
+ const projectShadow = (index.symbols.get(
8697
+ String(rt.type).split('.').pop()) || [])
8698
+ .some(d => NON_CALLABLE_TYPES.has(d.type) && d.type !== 'field');
8699
+ if (!projectShadow) {
8700
+ const builtinReturn = _builtinMethodReturnType(language, rt.type, name);
8701
+ if (builtinReturn) return { type: builtinReturn };
8702
+ }
8128
8703
  return _methodReturnOnType(index, rt.type, rt.fromFile, name, language,
8129
8704
  { filePath, consumerAwaited });
8130
8705
  }