Skip to content

fix(native): add dispatch-table PTS resolution in JS extractor#1690

Merged
carlos-alm merged 1 commit into
mainfrom
fix/parity-1684-dispatch-table
Jun 21, 2026
Merged

fix(native): add dispatch-table PTS resolution in JS extractor#1690
carlos-alm merged 1 commit into
mainfrom
fix/parity-1684-dispatch-table

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • Native JS extractor was emitting a file-sink edge for ({a:fnA,b:fnB})[key]() dispatch-table calls instead of resolving to dtFn1/dtFn2
  • Add extract_dispatch_table_call in crates/codegraph-core/src/extractors/javascript.rs — mirrors WASM extractSubscriptCallInfo RES-2 branch (src/extractors/javascript.ts:3196–3233)
  • When the subscript object is an object literal and the index is an identifier, seed arrayElemBindings under a synthetic <dt_line_col> name and emit <dt_line_col>[*] so the PTS solver resolves to each target
  • Two new Rust unit tests lock in the behaviour

Verification

node scripts/parity-compare.mjs | grep pts-javascript
# === pts-javascript: wasm vs native OK (27 nodes, 41 edges)

All 8 build-parity tests pass, all 5 pts-javascript resolution benchmark tests pass.

Closes #1684

Test plan

  • cargo test -p codegraph-core dispatch_table — 2 new tests pass
  • node scripts/parity-compare.mjs — pts-javascript PARITY OK
  • npx vitest run tests/benchmarks/resolution/resolution-benchmark.test.ts -t "pts-javascript" — 5/5 pass
  • npx vitest run tests/integration/build-parity.test.ts — 8/8 pass

…ch WASM

WASM resolves `({a:fnA,b:fnB})[key]()` by seeding arrayElemBindings under a
synthetic `<dt_line_col>` name and emitting a `<dt_line_col>[*]` call, allowing
the PTS solver to expand the wildcard to each concrete target.

Native was falling through to the generic `<dynamic:computed-key>` path and
emitting a file-sink edge instead.  Add `extract_dispatch_table_call` in the
native JS extractor (mirroring WASM `extractSubscriptCallInfo` RES-2 branch)
and call it from `handle_call_expr` before the `extract_call_info` fallback.

Also adds two unit tests that lock in the behaviour.

Closes #1684
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds extract_dispatch_table_call to the native Rust JS extractor, intercepting inline object-literal dispatch table calls like ({a:fnA,b:fnB})[key]() before they fall through to the generic <dynamic:computed-key> handler. The function seeds ArrayElemBinding entries under a synthetic <dt_line_col> name and emits a <dt_line_col>[*] call so the PTS solver resolves the wildcard to each concrete target function.

  • New function extract_dispatch_table_call handles both parenthesized (({a:fn})[k]()) and bare ({a:fn}[k]()) object-literal subscripts, walking the object's children for shorthand_property_identifier and pair nodes.
  • Two unit tests are added to verify the <dt_line_col>[*] call name, the dispatch-table dynamic kind, and the array_elem_bindings entries for both target functions.

Confidence Score: 4/5

Safe to merge; the dispatch-table path is well-contained, falls back correctly for non-matching patterns, and is verified by the parity and benchmark test suites.

The new function correctly intercepts inline object-literal subscript calls, builds array-elem bindings, and emits the expected wildcard call. The core resolution path is unaffected for all other call patterns. Two minor issues exist: idx is not incremented when a pair value is a non-identifier (inconsistent with the array-element binding convention elsewhere), and both new tests exercise only parenthesized object literals, leaving the bare-object branch untested.

crates/codegraph-core/src/extractors/javascript.rs — specifically the idx increment logic in extract_dispatch_table_call and the test coverage gap for non-parenthesized object literals.

Important Files Changed

Filename Overview
crates/codegraph-core/src/extractors/javascript.rs Adds extract_dispatch_table_call to handle inline object-literal dispatch table patterns and intercepts them in handle_call_expr before falling through to the generic subscript handler; two unit tests are added, but both test only parenthesized object literals and idx is not incremented for non-identifier pair values.

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(native): add dispatch-table PTS reso..." | Re-trigger Greptile

Comment on lines +1511 to +1525
"pair" => {
if let Some(val) = child.child_by_field_name("value") {
if val.kind() == "identifier" {
let text = node_text(&val, source);
if !JS_BUILTIN_GLOBALS.contains(&text) {
array_elem_bindings.push(ArrayElemBinding {
array_name: table_name.clone(),
index: idx,
elem_name: text.to_string(),
});
idx += 1;
}
}
}
}

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 idx not incremented for non-identifier pair values

When a pair's value is a non-identifier expression (e.g., an arrow function or a literal like {a: () => {}, b: fnB}), idx is not incremented, so fnB gets index 0 instead of 1. The existing collect_array_elem_bindings (line ~3272) always increments idx for every non-punctuation element regardless of whether it contributes a binding, preserving positional accuracy. Here indices are consecutive only over identifier-valued entries.

This doesn't affect [*]-wildcard resolution (the PTS solver generates a constraint for every entry regardless of its index), but it does affect the spread_arg_bindings codepath in build_edges.rs (lines 251-278): that path iterates 0..=array_max_index to map positional spread parameters, and array_max_index is derived from the highest index stored in array_elem_bindings. If any future caller seeds a spread-arg binding against a dispatch-table name, the index gap would silently misalign parameter slots.

Fix in Claude Code

Comment on lines +5030 to 5037
"function fnA() {}\n\
function fnB() {}\n\
function run(k) { ({a: fnA, b: fnB})[k](); }",
);
let dt_call = s.calls.iter().find(|c| c.name.starts_with("<dt_") && c.name.ends_with(">[*]"));
assert!(dt_call.is_some(), "dispatch-table call missing for parenthesized object; got: {:?}", s.calls);
}
}

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 Second test covers the same pattern as the first

Both dispatch_table_emits_dt_call_and_array_elem_bindings and dispatch_table_parenthesized_object_also_works use a parenthesized object literal ({...})[k](). The second test's name implies it specifically verifies the parenthesized path, but so does the first — there is no test for the unparenthesized form obj_literal[k]() where the subscript object is a bare object node (which can appear in non-statement expression contexts, e.g., as an argument). The non-parenthesized branch in extract_dispatch_table_call goes through else { obj } and is never exercised by the current test suite.

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

@carlos-alm carlos-alm merged commit fc06b26 into main Jun 21, 2026
37 checks passed
@carlos-alm carlos-alm deleted the fix/parity-1684-dispatch-table branch June 21, 2026 23:28
@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: pts-javascript dispatch-table edges — native emits file-sink instead of resolved dtFn1/dtFn2 targets

1 participant