Repository navigation
fix(parser): omit calls inside Rust if false - #1132
1 commit merged into
Conversation
code-review-graph reviewOverall risk: 0.55 (MEDIUM) — 35 changed function(s)/class(es), 47 affected flow(s), 1 test gap(s) Risk-scored changes
Affected execution flows
Test gaps
Powered by code-review-graph — local-first analysis; no code leaves the CI runner. |
773d483 to
21c92f7
Compare
|
Hi @tirth8205, bump on this one. The gap: Rust's The fix is a Rust-only helper, Tests: a fixture test on the parse path, plus direct unit tests for the helper in CI is green on this head and it merges cleanly against staging. Happy to adjust anything. |
|
Filled in the description with what this does to real code: eight Rust codebases parsed on both branches, every CALLS edge diffed. 32 edges go, none are added, and each removed edge sits in an
|
There was a problem hiding this comment.
🟡 Changes recommended
The parser incorrectly applies guards around an impl declaration to its method bodies, dropping reachable calls and potentially reporting live targets as dead code.
1 open issue
What changed in this PR
This PR adds Rust-specific dead-guard detection so calls inside literal if false branches are omitted from the code-review graph.
Changes:
- Recognizes Rust’s
if_expressionandboolean_literalnodes without changing Kotlin or Scala behavior. - Preserves calls in else branches,
if true, and compound conditions. - Adds a Rust fixture and helper/integration tests for dead and live branches.
| File | Description |
|---|---|
tests/test_parser.py |
Adds Rust dead-guard helper and parsing tests. |
tests/fixtures/sample_dead_guard.rs |
Provides dead and live branch examples. |
code_review_graph/parser.py |
Adds the Rust-specific ancestor check and integrates it into call extraction. |
Review effort: Balanced
| """ | ||
| cursor = node.parent | ||
| while cursor is not None: | ||
| if cursor.type == "if_expression": |
Rust's if is an `if_expression` and its false is a `boolean_literal`, but the non-Python dead-guard walk looks for `if_statement` and a bare `false` node. Neither matches, so a call under `if false` in Rust reached the graph as a CALLS edge. `_is_in_rust_dead_guard` walks the ancestors for that pair and drops the call when it sits in the consequence. Else branches and `if true` stay live, and so do `if !false`, `if false && false`, `while false` and `cfg!(any())`. A parenthesised literal, `if (false)`, counts the same as the bare one. The walk ends at an `impl_item`. Rust impl blocks are non-local: one written inside a branch still applies to the type, so its methods and associated consts are reached from outside that branch and their callees are live. Inheriting the guard from the declaration reported those targets as dead code, which is worse than keeping a call that never runs. Everything else a dead branch encloses stays dead, because a local `fn`, `mod` or `trait` is scoped to the block. Only Rust takes this walk. Kotlin and Scala build the same two node types, and their `if (false)` calls keep the edges they have today. The helper has direct unit tests in `TestDeadGuardHelpers` alongside the existing ones, plus fixture tests for the parse path. Both directions are pinned: an impl const keeps its edge, a block-scoped fn/mod/trait loses it, and the impl fixture asserts the edge that makes the method live. Signed-off-by: Minxi Hou <houminxi@gmail.com>
21c92f7 to
bcaacc8
Compare
|
Confirmed, and thanks — the guard was inheriting across the declaration boundary. Reproduced with the shape from the review: struct S;
fn live_helper() {}
fn main() {
if false {
impl S { fn run() { live_helper(); } }
}
S::run();
}
The walk now ends at an
Guards inside a body still apply, because the walk reaches the Tests: Both directions are load-bearing: switching the boundary back to |
0160eb0

Linked issue
No issue. The gap turned up while porting a local branch onto
staging: thewalk covers
if_statementfor Go and TS/JS, and C/C++ preprocessor blocks,but nothing matched Rust's shape.
What & why
A call under
if falsein Rust is kept as a CALLS edge.Rust's if is an
if_expression, and itsfalseis aboolean_literal. Thetree-sitter walk in
_is_in_static_dead_guardlooks forif_statementwith abare
falsenode, so neither half matches._is_in_rust_dead_guardwalks theancestors for that pair and drops the call when it sits in the consequence.
What stays live: else branches,
if true,if !false,if false && false,while false, andcfg!(any()). A compound condition is left alone onpurpose:
false || trueis also statically true, and a wrong call there woulddelete a live edge.
Kotlin and Scala build the same
if_expressionwith aboolean_literal:if (false) { dead() }dead,live,otherif (false) dead()dead,live,otherif false { dead() }dead,live,otherlive,otherPutting the new node types in the shared walk instead deleted the Kotlin and
Scala edges. That is why the walk is Rust-only.
How it was tested
The new test fails if either half of the lookup is removed: dropping the
if_expressionbranch or dropping theboolean_literalcheck each turnstest_rust_dead_guard_if_false_omits_dead_edgesred.tests/fixtures/sample_dead_guard.rscovers an unguarded call,if false,the else branch, an
else if, andif true.TestDeadGuardHelpersuses the same shape as the existing helper tests: thefixture test covers the parse path, and small direct tests pin the helper's
branches so a wrong node type or a dropped range check fails on its own.
Against real code
I parsed eight Rust codebases with both staging and this branch and diffed
every CALLS edge. The right column is what this branch removes.
No codebase gains an edge, and every removed edge sits inside an
if falsebody. Two of them in context:
jstation-controlleris the other common shape: a layout-debug switch inif falsewith the real branch inelse. Only theexplainedge goes.while falsekeeps its edges, same as today.One consequence: a function whose only call sites sit under
if falsenow hasno caller, so dead-code reporting will list it. That is the point of the
change, but it is a visible diff for anyone carrying dormant code that way.
Checklist
uv run pytest tests/ --tb=short -quv run ruff check code_review_graph/uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optionalSigned-off-by: Minxi Hou houminxi@gmail.com