ucn 5.1.1 → 5.2.1

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