ucn 5.2.1 → 5.3.0

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