diff --git a/src/mir/builder/control_flow/plan/lowerer.rs b/src/mir/builder/control_flow/plan/lowerer.rs index 4743aeaf..0124e4fa 100644 --- a/src/mir/builder/control_flow/plan/lowerer.rs +++ b/src/mir/builder/control_flow/plan/lowerer.rs @@ -199,14 +199,7 @@ impl PlanLowerer { // Special handling for body block: emit body CorePlan instead of effects if *block_id == loop_plan.body_bb { for plan in &loop_plan.body { - match plan { - CorePlan::Effect(effect) => { - Self::emit_effect(builder, effect)?; - } - _ => { - return Err("[lowerer] Non-Effect plan in Loop body not yet supported".to_string()); - } - } + Self::emit_body_plan(builder, plan)?; } } else { // Normal block: emit effects @@ -326,12 +319,26 @@ impl PlanLowerer { } Ok(()) } + + fn emit_body_plan(builder: &mut MirBuilder, plan: &CorePlan) -> Result<(), String> { + match plan { + CorePlan::Effect(effect) => Self::emit_effect(builder, effect), + CorePlan::Seq(plans) => { + for nested in plans { + Self::emit_body_plan(builder, nested)?; + } + Ok(()) + } + _ => Err("[lowerer] Non-Effect plan in Loop body not yet supported".to_string()), + } + } } #[cfg(test)] mod tests { use super::*; use crate::ast::{ASTNode, LiteralValue, Span}; + use crate::mir::builder::control_flow::edgecfg::api::Frag; use crate::mir::{ConstValue, MirInstruction}; fn make_ctx<'a>(condition: &'a ASTNode, body: &'a [ASTNode]) -> LoopPatternContext<'a> { @@ -412,4 +419,74 @@ mod tests { "expected Branch terminator" ); } + + #[test] + fn test_lower_loop_body_seq_flattens() { + let mut builder = MirBuilder::new(); + builder.enter_function_for_test("test_loop_body_seq".to_string()); + + let preheader_bb = builder.next_block_id(); + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + let found_bb = builder.next_block_id(); + + let eff1 = CoreEffectPlan::Const { + dst: builder.alloc_value_for_test(), + value: ConstValue::Integer(1), + }; + let eff2 = CoreEffectPlan::Const { + dst: builder.alloc_value_for_test(), + value: ConstValue::Integer(2), + }; + let eff3 = CoreEffectPlan::Const { + dst: builder.alloc_value_for_test(), + value: ConstValue::Integer(3), + }; + + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb, + body: vec![CorePlan::Seq(vec![ + CorePlan::Effect(eff1), + CorePlan::Seq(vec![CorePlan::Effect(eff2)]), + CorePlan::Effect(eff3), + ])], + cond_loop: builder.alloc_value_for_test(), + cond_match: builder.alloc_value_for_test(), + block_effects: vec![ + (preheader_bb, vec![]), + (header_bb, vec![]), + (body_bb, vec![]), + (step_bb, vec![]), + ], + phis: vec![], + frag: Frag::new(header_bb), + final_values: vec![], + }; + + let cond = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let body: Vec = vec![]; + let ctx = make_ctx(&cond, &body); + + let result = PlanLowerer::lower(&mut builder, CorePlan::Loop(loop_plan), &ctx); + assert!(result.is_ok()); + + let func = builder.scope_ctx.current_function.as_ref().expect("function"); + let block = func.get_block(body_bb).expect("body block"); + let const_count = block + .instructions + .iter() + .filter(|inst| matches!(inst, MirInstruction::Const { .. })) + .count(); + assert_eq!(const_count, 3, "expected 3 Const effects in body"); + } }