Skip to content

fix(resolver): qualified callerName mismatch in class-scoped typeMap lookup#1403

Merged
carlos-alm merged 6 commits into
mainfrom
fix/csharp-static-receiver-1385
Jun 9, 2026
Merged

fix(resolver): qualified callerName mismatch in class-scoped typeMap lookup#1403
carlos-alm merged 6 commits into
mainfrom
fix/csharp-static-receiver-1385

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • resolveByMethodOrGlobal (WASM) and resolve_call_targets (native) now resolve no-receiver calls inside class methods by trying the class-qualified name — e.g. IsValidEmail() inside Validators.ValidateUserValidators.IsValidEmail
  • Fixes the post-CHA role re-classification in the native orchestrator: switched from incremental (stale median) to full, ensuring correct roles across all files after CHA expands edges

Root cause

The same-class method lookup was gated on call.receiver && callerName (WASM) / call.receiver.is_some() (native). Static sibling calls in C#/Java have no receiver, so the guard prevented the qualified-name fallback from firing.

Additionally, the Rust orchestrator classifies roles before the CHA post-pass. After CHA adds interface-dispatch edges, the global fan-out median shifts, but the subsequent incremental re-classification used a stale cached median and excluded files not directly connected to CHA targets (e.g. Validators.cs). Switching to a full re-classification ensures correct roles.

Test plan

The test was using auto engine (native-preferred), causing it to pick the
published npm native binary which predates the prototype-method fixes.
WASM correctly extracts Dog.prototype.bark and resolves all call edges.

Fixes #1381
…lookup

When a method is called without a receiver inside a class-qualified method
(e.g. `IsValidEmail()` inside `Validators.ValidateUser`), both the WASM and
native engines now try the class-qualified name as a fallback.

Root cause: the same-class method lookup in `resolveByMethodOrGlobal` was
gated on `call.receiver && callerName`, which excluded no-receiver calls.
Static sibling calls in C#/Java (e.g. `IsValidEmail()` inside a static class)
have no receiver — the guard prevented the `Validators.IsValidEmail` lookup.

Fixes:
- WASM (call-resolver.ts): `if (call.receiver && callerName)` → `if (callerName)`
- Native (edge_builder.rs): moves class-scoped exact lookup outside the
  `call.receiver.is_some()` guard; suffix scan remains gated on receiver-present
  to avoid false positives on global function calls inside class methods.

Also fixes a latent CHA re-classification bug exposed by this change: the Rust
orchestrator classifies roles before the CHA post-pass, so the global fan-out
median was computed from pre-CHA edges. After CHA added edges, the median
shifted but Validators.cs (not directly connected to CHA-affected files) was
excluded from the incremental re-classification, leaving stale roles. Fixed by
switching the post-CHA re-classification from incremental to full.

C# same-file recall: 0/2 → 2/2 (100%).
Overall C# recall: 73.9% → 82.6% (19/23 expected edges).
Remaining gap: receiver-typed (0/4) tracked in #1402.
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two distinct bugs in the call resolution pipeline. The same-class sibling lookup in both the WASM (call-resolver.ts) and Rust (edge_builder.rs) resolvers was gated on the presence of a receiver, so bare static method calls in C#/Java (e.g., IsValidEmail() inside Validators.ValidateUser) never hit the ClassName.method qualified-name path. Removing the receiver guard fixes this, bringing C# recall from 73.9% to 82.6%. The post-CHA role re-classification in the native orchestrator is also corrected: the previous incremental approach used a stale global fan-out median, producing wrong roles for files not directly touched by CHA; switching to a full classifyNodeRoles(db) call ensures the recomputed median is applied everywhere.

  • edge_builder.rs / call-resolver.ts: Receiver guard removed from class-scoped lookup; the exact unqualified lookup still runs first so no-receiver calls to global functions (confidence ≥ 0.5) are unaffected, and the broader suffix-scan fallback remains receiver-gated to prevent false positives.
  • native-orchestrator.ts: runPostNativeCha return type simplified from Set<number> to number (count); caller switches from incremental file-scoped re-classification to a full graph re-classification, trading some incremental-build cost for correctness.
  • Benchmark: C# thresholds raised to precision: 1.0 / recall: 0.8, locking in the observed gains and matching the pattern of other mature-fixture entries.

Confidence Score: 5/5

Safe to merge — both resolver changes are well-scoped, the exact-lookup-first ordering prevents regressions in non-C# languages, and the full re-classification is a deliberate correctness trade-off backed by benchmark evidence.

The logic changes are narrow and internally consistent across the WASM and native paths. The only finding is a stale JSDoc on runPostNativeCha that describes the old Set return; it has no runtime impact.

No files require special attention. The JSDoc in native-orchestrator.ts is the only item worth tidying before merge.

Important Files Changed

Filename Overview
crates/codegraph-core/src/edge_builder.rs Removes the call.receiver.is_some() guard on the class-scoped lookup so that bare method calls inside class methods (static C#/Java siblings) also benefit from the ClassName.method qualified-name search; confidence filtering added in prior commit; suffix-scan fallback correctly remains receiver-gated.
src/domain/graph/builder/call-resolver.ts Mirrors the Rust change: drops call.receiver from the same-class lookup guard (if (call.receiver && callerName)if (callerName)), enabling qualified-name fallback for no-receiver sibling calls; ordering (exact lookup first) prevents false positives when a global with confidence ≥ 0.5 already matches.
src/domain/graph/builder/stages/native-orchestrator.ts Switches post-CHA role re-classification from incremental (stale median) to full classifyNodeRoles(db) call, fixing incorrect roles in files not directly connected to CHA targets; JSDoc for runPostNativeCha is now stale (still describes the old Set<number> return).
tests/benchmarks/resolution/resolution-benchmark.test.ts Raises C# benchmark thresholds from precision 0.5/recall 0.2 to precision 1.0/recall 0.8, reflecting the observed 82.6% recall and 100% precision on the current fixture; consistent with the pts-javascript fixture which also uses precision: 1.0.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["resolve_call_targets / resolveByMethodOrGlobal
    call.name = 'IsValidEmail', callerName = 'Validators.ValidateUser'"] --> B{typeMap lookup
    for typed receiver?}
    B -->|hit| C[Return typeMap-typed targets]
    B -->|miss| D["Exact unqualified lookup
    nodes_by_name['IsValidEmail']
    confidence >= 0.5"]
    D -->|non-empty| E[Return exact matches]
    D -->|empty| F{callerName has dot?
    e.g. 'Validators.ValidateUser'}
    F -->|no| G[Return empty]
    F -->|yes| H["Class-scoped qualified lookup
    'Validators.IsValidEmail'
    kind == 'method', confidence >= 0.5
    (NEW: fires even with no receiver)"]
    H -->|non-empty| I[Return class-scoped matches]
    H -->|empty| J{call.receiver present?
    e.g. this/self/super}
    J -->|no| K[Return empty]
    J -->|yes| L["Broader same-file suffix scan
    nodes ending in '.IsValidEmail'
    restricted to caller class prefix"]
    L --> M[Return matches or empty]

    style H fill:#d4edda,stroke:#28a745
    style F fill:#d4edda,stroke:#28a745
Loading

Fix All in Claude Code

Reviews (8): Last reviewed commit: "Merge branch 'main' into fix/csharp-stat..." | Re-trigger Greptile

Comment on lines +515 to +519
let class_scoped: Vec<&NodeInfo> = ctx.nodes_by_name
.get(qualified.as_str())
.map(|v| v.iter().filter(|n| n.kind == "method").copied().collect())
.unwrap_or_default();
if !class_scoped.is_empty() { return class_scoped; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Class-scoped Rust lookup skips confidence filtering

The WASM counterpart in call-resolver.ts (line 192) applies computeConfidence >= 0.5 when building sameClass, but the Rust path here only filters by n.kind == "method". If two separate packages in the same graph both define a ClassName.MethodName method (e.g. same-named utility classes in different sub-projects), the Rust resolver will return all matches with equal weight, while WASM would keep only the ones that pass the confidence threshold. This pre-existed for receiver-based calls but now also fires for no-receiver static siblings, slightly widening the exposure.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added import_resolution::compute_confidence(rel_path, &n.file, None) >= 0.5 to the class-scoped Rust lookup, aligning it with the WASM counterpart in call-resolver.ts. Committed in d7cc1cb.

Comment on lines 1558 to 1559
const chaTargetIds = runPostNativeCha(ctx.db as unknown as BetterSqlite3Database);
if (chaTargetIds.size > 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 runPostNativeCha returns a full Set<number> but after this refactor only .size > 0 is ever read — the individual IDs are no longer needed. Building the Set is a minor allocation that now serves no purpose. Checking a returned count or boolean would make the intent clearer and avoid the unused allocation.

Suggested change
const chaTargetIds = runPostNativeCha(ctx.db as unknown as BetterSqlite3Database);
if (chaTargetIds.size > 0) {
const chaEdgeCount = runPostNativeCha(ctx.db as unknown as BetterSqlite3Database).size;
if (chaEdgeCount > 0) {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed runPostNativeCha return type from Set<number> to number, replacing newTargetIds set with a newEdgeCount counter. The call site now reads the count directly as chaEdgeCount. Committed in d7cc1cb.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

4 functions changed24 callers affected across 11 files

  • resolve_call_targets in crates/codegraph-core/src/edge_builder.rs:369 (6 transitive callers)
  • resolveByMethodOrGlobal in src/domain/graph/builder/call-resolver.ts:62 (14 transitive callers)
  • runPostNativeCha in src/domain/graph/builder/stages/native-orchestrator.ts:409 (4 transitive callers)
  • tryNativeOrchestrator in src/domain/graph/builder/stages/native-orchestrator.ts:1436 (5 transitive callers)

carlos-alm added a commit that referenced this pull request Jun 8, 2026
…; return count from runPostNativeCha

- edge_builder.rs: align class-scoped lookup with WASM call-resolver.ts
  by adding compute_confidence >= 0.5 filter, matching all other lookup
  paths in the same block (#1403)
- native-orchestrator.ts: change runPostNativeCha return type from
  Set<number> to number — caller only reads .size, individual IDs are
  never used; clearer intent and avoids the unused Set allocation (#1403)
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

carlos-alm added a commit that referenced this pull request Jun 8, 2026
…nPostNativeCha returns count

- edge_builder.rs: align class-scoped lookup with WASM call-resolver.ts
  by adding compute_confidence >= 0.5 filter, matching all other lookup
  paths in the same block (#1403)
- native-orchestrator.ts: change runPostNativeCha return type from
  Set<number> to number — caller only reads .size, individual IDs are
  never used; clearer intent and avoids the unused Set allocation (#1403)
@carlos-alm carlos-alm force-pushed the fix/csharp-static-receiver-1385 branch from d7cc1cb to 93ec343 Compare June 8, 2026 17:30
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed stale JSDoc on runPostNativeCha — updated the description from 'Returns the set of target node IDs' / 'An empty set means no edges' to 'Returns the count of newly inserted CHA edges' / 'Returns 0 when no edges were added', matching the actual number return type. Included in the merge commit.

…nPostNativeCha returns count

- edge_builder.rs: align class-scoped lookup with WASM call-resolver.ts
  by adding compute_confidence >= 0.5 filter, matching all other lookup
  paths in the same block (#1403)
- native-orchestrator.ts: change runPostNativeCha return type from
  Set<number> to number — caller only reads .size, individual IDs are
  never used; clearer intent and avoids the unused Set allocation (#1403)
@carlos-alm carlos-alm force-pushed the fix/csharp-static-receiver-1385 branch from 4857606 to c1a32c5 Compare June 9, 2026 03:16
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed commitlint failure — commit d7cc1cb had a 105-character header exceeding the 100-char limit. Rewrote branch history: reset to 0831e36, merged origin/main (resolving the prototype-method-resolution conflict by taking the origin/main version that removes the TODO comment), then cherry-picked the Greptile fixes (93ec343) with a 97-char header. Force-pushed with new CI run in progress.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 2695c60 into main Jun 9, 2026
28 checks passed
@carlos-alm carlos-alm deleted the fix/csharp-static-receiver-1385 branch June 9, 2026 05:25
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant