Skip to content

fix(native): add RES-3 reflection resolution to Rust resolver for Groovy/Java/Scala#1685

Merged
carlos-alm merged 1 commit into
mainfrom
fix/parity-1681-reflection-groovy-java-scala
Jun 21, 2026
Merged

fix(native): add RES-3 reflection resolution to Rust resolver for Groovy/Java/Scala#1685
carlos-alm merged 1 commit into
mainfrom
fix/parity-1681-reflection-groovy-java-scala

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • Native engine was missing resolved-reflection call edges for invokeMethod("lit", args) (Groovy) and .getMethod("lit") (Java/Scala), causing wasm=1 native=0 divergence in dynamic-groovy, dynamic-java, and dynamic-scala parity fixtures
  • Root cause: the Rust extractors (already merged via earlier PRs) correctly emit calls with dynamicKind='reflection' and keyExpr=<literal>, but resolve_call_targets in build_edges.rs had no code path to resolve them
  • Fix: add a RES-3 block to resolve_call_targets that mirrors the TypeScript resolveFallbackTargets RES-3 logic — when dynamicKind='reflection' and keyExpr is set, try two qualified lookups: (1) typeMap[receiver] → resolvedType.keyExpr, and (2) callerName class prefix → CallerClass.keyExpr for same-class sibling methods (e.g. DynamicDispatch.greet)
  • Scoped to non-JS/TS files via is_module_scoped_language to avoid interfering with the existing JS reflection path

Test plan

  • node scripts/parity-compare.mjs --langs dynamic-groovy,dynamic-java,dynamic-scala → all 3 fixtures now report PARITY OK
  • Full node scripts/parity-compare.mjs → 39/42 fixtures pass (dynamic-kotlin, dynamic-typescript, pts-javascript were pre-existing divergences unrelated to this fix — no regressions)
  • npx napi build --platform --release compiles cleanly (1 pre-existing unused-variable warning in javascript.rs, unrelated to this change)

Closes #1681

…r Groovy/Java/Scala

JVM getMethod/invokeMethod calls with literal method names were emitted
by the Rust extractors with dynamicKind='reflection' and keyExpr set,
but resolve_call_targets in build_edges.rs had no path to resolve them.

Methods in Groovy/Java/Scala are stored as class-qualified names
(e.g. DynamicDispatch.greet), so the plain name lookup of 'greet'
finds nothing. The TypeScript resolver has a RES-3 fallback in
resolveFallbackTargets that handles this via two qualified lookups:
  1. typeMap[receiver] -> resolvedType.keyExpr (type-annotated locals)
  2. callerName class prefix -> CallerClass.keyExpr (same-class siblings)

Add the equivalent block to resolve_call_targets in Rust, scoped to
non-JS/TS files to avoid interfering with the existing JS reflection
path. Fixes the wasm=1 native=0 divergence for:
  dynamic-groovy: DynamicDispatch.runInvokeMethod -> DynamicDispatch.greet
  dynamic-java:   Reflection.runGetMethod -> Reflection.greet
  dynamic-scala:  Reflection.runGetMethod -> Reflection.greet

Closes #1681
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a RES-3 reflection resolution block to the Rust resolve_call_targets function in build_edges.rs, closing the wasm=1 native=0 divergence for Groovy invokeMethod("lit", args) and Java/Scala .getMethod("lit") call sites. The block mirrors the existing TypeScript resolveFallbackTargets RES-3 logic and is guarded to non-JS/TS files.

  • RES-3.1: When dynamicKind='reflection' and keyExpr is set, resolves typeMap[receiver] → resolvedType.keyExpr for type-annotated locals.
  • RES-3.2: Falls back to CallerClass.keyExpr using the second-to-last dot segment of callerName, handling same-class sibling dispatch (e.g. DynamicDispatch.greet).
  • Both sub-steps use ctx.nodes_by_name with compute_confidence ≥ 0.5 — consistent with the established Rust resolver pattern (rather than the TypeScript's same-file-only byNameAndFile), which actually improves cross-file reflection resolution.

Confidence Score: 4/5

The change is narrowly scoped to a new resolution branch inside a single function; it only activates for reflection calls with a literal key expression in non-JS/TS files, and each sub-step exits early before touching the existing fallback chain.

The new RES-3 block faithfully reproduces the TypeScript original's logic. The caller_class extraction via double-rfind('.') correctly handles package-qualified names. The one meaningful divergence from the TypeScript — using nodes_by_name with a confidence threshold instead of the TS byNameAndFile (same-file only) lookup — is consistent with every other type-aware step in the Rust resolver and actually extends coverage to cross-file reflection targets.

No files require special attention; build_edges.rs is the only changed file and the new block is well-isolated.

Important Files Changed

Filename Overview
crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs Adds 46 lines for the RES-3 reflection resolution block; logic is correct and mirrors the TypeScript original, with the established Rust confidence-based lookup pattern in place of the TS file-scoped lookup.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant RC as resolve_call_targets
    participant TM as type_map
    participant NM as nodes_by_name
    participant CC as compute_confidence

    RC->>RC: "Guard: dynamicKind='reflection'<br/>keyExpr set, receiver set,<br/>!is_module_scoped_language"
    RC->>TM: get(receiver)
    alt RES-3.1: type-annotated local
        TM-->>RC: (resolved_type, _)
        RC->>NM: get(resolved_type.keyExpr)
        NM-->>RC: candidates
        RC->>CC: "compute_confidence(rel_path, n.file) >= 0.5"
        CC-->>RC: filtered nodes
        RC-->>RC: return typed (early exit if non-empty)
    else no typeMap entry
        TM-->>RC: None
    end
    RC->>RC: "RES-3.2: extract CallerClass from callerName<br/>(rfind last dot, then second-to-last dot)"
    RC->>NM: get(CallerClass.keyExpr)
    NM-->>RC: candidates
    RC->>CC: "compute_confidence(rel_path, n.file) >= 0.5"
    CC-->>RC: filtered nodes
    RC-->>RC: return class_scoped (early exit if non-empty)
    RC-->>RC: fall through to step 4 (scoped fallback)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant RC as resolve_call_targets
    participant TM as type_map
    participant NM as nodes_by_name
    participant CC as compute_confidence

    RC->>RC: "Guard: dynamicKind='reflection'<br/>keyExpr set, receiver set,<br/>!is_module_scoped_language"
    RC->>TM: get(receiver)
    alt RES-3.1: type-annotated local
        TM-->>RC: (resolved_type, _)
        RC->>NM: get(resolved_type.keyExpr)
        NM-->>RC: candidates
        RC->>CC: "compute_confidence(rel_path, n.file) >= 0.5"
        CC-->>RC: filtered nodes
        RC-->>RC: return typed (early exit if non-empty)
    else no typeMap entry
        TM-->>RC: None
    end
    RC->>RC: "RES-3.2: extract CallerClass from callerName<br/>(rfind last dot, then second-to-last dot)"
    RC->>NM: get(CallerClass.keyExpr)
    NM-->>RC: candidates
    RC->>CC: "compute_confidence(rel_path, n.file) >= 0.5"
    CC-->>RC: filtered nodes
    RC-->>RC: return class_scoped (early exit if non-empty)
    RC-->>RC: fall through to step 4 (scoped fallback)
Loading

Reviews (1): Last reviewed commit: "fix(native): add RES-3 reflection resolu..." | Re-trigger Greptile

@carlos-alm carlos-alm merged commit 8f41339 into main Jun 21, 2026
31 checks passed
@carlos-alm carlos-alm deleted the fix/parity-1681-reflection-groovy-java-scala branch June 21, 2026 22:48
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 21, 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.

Engine parity: native missing resolved-reflection edges for Groovy invokeMethod / Java+Scala getMethod literal patterns

1 participant