fix(semantic/cfg): connect while branches from condition exit (#23790)

Correct the CFG generated for `while` statements whose test expression creates multiple basic blocks, such as logical or conditional expressions.

Previously, the loop body and exit edges originated from the test expression's entry block. They now originate from its exit block after the complete expression has been evaluated.

## Before

The body and exit edges could bypass the test expression, leaving its exit block disconnected.

```mermaid
flowchart LR
    Entry["Test entry"] --> Test["Test expression"]
    Test --> Exit["Test exit"]

    Entry -->|true| Body["Loop body"]
    Entry -->|false| After["After while"]
    Body -->|backedge| Entry
```
## After

The complete test expression is evaluated before control branches to the loop body or exit.

```mermaid
flowchart LR
    Entry["Test entry"] --> Test["Test expression"]
    Test --> Exit["Test exit"]

    Exit -->|true| Body["Loop body"]
    Exit -->|false| After["After while"]
    Body -->|backedge| Entry
```

The implementation records cfg.current_node_ix after visiting the test expression as after_test_graph_ix, then uses that block for the body and exit edges. The loop backedge and continue target remain connected to the test entry so each iteration evaluates the expression again.
This commit is contained in:
camc314 committed 2026-06-25 17:19:17 +00:00
1 parent 222e9f6b5f
commit d3e92d5258
2 files changed
+7 -6

No files matched your search

+5 -4
View File
@@ -1911,12 +1911,13 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
/* cfg - body basic block */
#[cfg(feature = "cfg")]
let body_graph_ix = control_flow!(self, |cfg| {
let (after_test_graph_ix, body_graph_ix) = control_flow!(self, |cfg| {
cfg.append_condition_to(condition_graph_ix, test_node_id);
let after_test_graph_ix = cfg.current_node_ix;
let body_graph_ix = cfg.new_basic_block_normal();
cfg.ctx(None).default().allow_break().allow_continue();
body_graph_ix
(after_test_graph_ix, body_graph_ix)
});
/* cfg */
@@ -1928,9 +1929,9 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let after_while_graph_ix = cfg.new_basic_block_normal();
cfg.add_edge(before_while_stmt_graph_ix, condition_graph_ix, EdgeType::Normal);
cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Jump);
cfg.add_edge(after_test_graph_ix, body_graph_ix, EdgeType::Jump);
cfg.add_edge(after_body_graph_ix, condition_graph_ix, EdgeType::Backedge);
cfg.add_edge(condition_graph_ix, after_while_graph_ix, EdgeType::Normal);
cfg.add_edge(after_test_graph_ix, after_while_graph_ix, EdgeType::Normal);
cfg.ctx(None)
.mark_break(after_while_graph_ix)
@@ -94,9 +94,9 @@ unreachable" shape = box]
8 -> 9 [ label="Unreachable", style="dotted"]
10 -> 2 [ label="Error(Implicit)", color=red, style=dashed]
3 -> 4 [ label="Normal"]
4 -> 8 [ label="Jump", color=green]
7 -> 8 [ label="Jump", color=green]
9 -> 4 [ label="Backedge", style="dotted", color=grey]
4 -> 10 [ label="Normal"]
7 -> 10 [ label="Normal"]
11 -> 2 [ label="Error(Implicit)", style=dashed, color=red]
10 -> 11 [ label="Unreachable", style="dotted"]
12 -> 0 [ label="Error(Implicit)", color=red, style=dashed]