Fix reactive conditional rendering with proper branch swapping

Implements efficient reactive conditionals that pre-render branches once
and swap DOM nodes without recreation, enabling fast switching with
thousands of elements.

Key Fixes:
- Runtime now calls reevaluate_conditional_*() on signal changes
- Match/case swap logic uses phi nodes to load correct old case
- Previously hardcoded case_0 as old value, causing accumulation
- Branch render functions now wrap all content in container div

Implementation:
- Pre-render both if/else branches at init
- Store roots in globals (@cond_N_then_root, @cond_N_else_root)
- On signal change, swap DOM nodes via js_replace_at_marker()
- Match statements use switch/phi pattern to select old case

Tests:
- Added test_reactive_conditionals.py (5 new tests)
- Updated test_llvm_codegen_conditional.py for reactive infrastructure
- All 35 tests passing

Performance: No re-rendering on switches, perfect for dynamic content.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Aaron SinclairandClaude committed 2025-11-15 10:32:40 -08:00
1 parent ac7726df46
commit e686903170
4 files changed
+272 -41

No files matched your search

+11 -8
View File
@@ -6,7 +6,7 @@ from ritz.llvm_codegen_signals import generate_llvm_ir
def test_if_statement_codegen():
"""Test LLVM IR generation for if statement."""
"""Test LLVM IR generation for if statement with reactive rendering."""
source = """
from ritz.syntax import *
@@ -27,13 +27,15 @@ def Toggle():
# Should have conditional branch
assert "br i1" in llvm_ir
# Should have basic blocks for then/else/merge
assert "if.then." in llvm_ir
assert "if.end." in llvm_ir
# Should have reactive conditional infrastructure
assert "@cond_0_marker" in llvm_ir
assert "_init_conditional_0" in llvm_ir
assert "_render_cond_0_then" in llvm_ir
assert "_render_cond_0_else" in llvm_ir
def test_if_else_codegen():
"""Test LLVM IR generation for if/else."""
"""Test LLVM IR generation for if/else with reactive rendering."""
source = """
from ritz.syntax import *
@@ -54,9 +56,10 @@ def Toggle():
# Should have conditional branch
assert "br i1" in llvm_ir
# Should have both then and else blocks
assert "if.then." in llvm_ir
assert "if.else." in llvm_ir
# Should have reactive infrastructure with both branches
assert "_render_cond_0_then" in llvm_ir
assert "_render_cond_0_else" in llvm_ir
assert "reevaluate_conditional_0" in llvm_ir
def test_if_comparison_codegen():
+169
View File
@@ -0,0 +1,169 @@
"""Tests for reactive conditional rendering with signal updates."""
import pytest
from ritz.parser import parse_component
from ritz.llvm_codegen_signals import generate_llvm_ir
def test_reactive_conditional_infrastructure():
"""Test that reactive conditional infrastructure is generated."""
source = """
from ritz.syntax import *
@component
def Toggle():
visible = signal(1)
if visible:
with P():
yield "Visible"
else:
with P():
yield "Hidden"
"""
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should have marker global
assert "@cond_0_marker = global i32 0" in llvm_ir
# Should have current state global
assert "@cond_0_current = global i32 0" in llvm_ir
# Should have root globals for both branches
assert "@cond_0_then_root = global i32 0" in llvm_ir
assert "@cond_0_else_root = global i32 0" in llvm_ir
# Should have init function
assert "define void @_init_conditional_0()" in llvm_ir
# Should have reevaluate function (for signal updates)
assert "define void @reevaluate_conditional_0()" in llvm_ir
# Should have branch render functions
assert "define i32 @_render_cond_0_then()" in llvm_ir
assert "define i32 @_render_cond_0_else()" in llvm_ir
def test_reactive_conditional_init_logic():
"""Test that init function pre-renders both branches."""
source = """
from ritz.syntax import *
@component
def Toggle():
visible = signal(1)
if visible:
with P():
yield "Shown"
else:
with P():
yield "Hidden"
"""
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Init should call both render functions
assert "call i32 @_render_cond_0_then()" in llvm_ir
assert "call i32 @_render_cond_0_else()" in llvm_ir
# Init should store both roots
assert "store i32" in llvm_ir and "@cond_0_then_root" in llvm_ir
assert "store i32" in llvm_ir and "@cond_0_else_root" in llvm_ir
# Init should insert active branch at marker
assert "call void @js_replace_at_marker" in llvm_ir
def test_reactive_conditional_swap_logic():
"""Test that reevaluate function swaps branches when condition changes."""
source = """
from ritz.syntax import *
@component
def Toggle():
visible = signal(0)
if visible:
with P():
yield "On"
else:
with P():
yield "Off"
"""
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Reevaluate should re-evaluate the condition
assert "reevaluate_conditional_0" in llvm_ir
# Should check if condition changed
assert "icmp ne" in llvm_ir
# Should have swap logic
assert "swap:" in llvm_ir or "swap_to_then:" in llvm_ir
assert "swap_to_then:" in llvm_ir
assert "swap_to_else:" in llvm_ir
def test_multiple_reactive_conditionals():
"""Test multiple if statements in same component get unique IDs."""
source = """
from ritz.syntax import *
@component
def MultiToggle():
flag1 = signal(1)
flag2 = signal(0)
if flag1:
with P():
yield "First on"
if flag2:
with P():
yield "Second on"
"""
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should have two separate conditionals
assert "@cond_0_marker" in llvm_ir
assert "@cond_1_marker" in llvm_ir
# Should have init functions for both
assert "define void @_init_conditional_0()" in llvm_ir
assert "define void @_init_conditional_1()" in llvm_ir
# Should have reevaluate functions for both
assert "define void @reevaluate_conditional_0()" in llvm_ir
assert "define void @reevaluate_conditional_1()" in llvm_ir
def test_reactive_conditional_with_comparison():
"""Test reactive conditional with comparison operator."""
source = """
from ritz.syntax import *
@component
def Counter():
count = signal(0)
if count > 5:
with P():
yield "High"
else:
with P():
yield "Low"
"""
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should generate icmp for comparison
assert "icmp" in llvm_ir
assert "sgt" in llvm_ir # signed greater than
# Should still have reactive infrastructure
assert "@_init_conditional_0()" in llvm_ir
assert "@reevaluate_conditional_0()" in llvm_ir