ucn 5.2.1 → 5.2.2

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