fix(resolver): qualified callerName mismatch in class-scoped typeMap lookup#1403
Conversation
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 SummaryThis PR fixes two distinct bugs in the call resolution pipeline. The same-class sibling lookup in both the WASM (
Confidence Score: 5/5Safe 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
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
Reviews (8): Last reviewed commit: "Merge branch 'main' into fix/csharp-stat..." | Re-trigger Greptile |
| 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; } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| const chaTargetIds = runPostNativeCha(ctx.db as unknown as BetterSqlite3Database); | ||
| if (chaTargetIds.size > 0) { |
There was a problem hiding this comment.
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.
| 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!
There was a problem hiding this comment.
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.
Codegraph Impact Analysis4 functions changed → 24 callers affected across 11 files
|
…; 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)
…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)
d7cc1cb to
93ec343
Compare
|
Fixed stale JSDoc on |
…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)
4857606 to
c1a32c5
Compare
|
Fixed commitlint failure — commit |
Summary
resolveByMethodOrGlobal(WASM) andresolve_call_targets(native) now resolve no-receiver calls inside class methods by trying the class-qualified name — e.g.IsValidEmail()insideValidators.ValidateUser→Validators.IsValidEmailRoot 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
npm testpasses (171 test files)vartype inference) tracked in feat(resolver): C# receiver-typed calls via var-declared instances (0/4 recall) #1402