//! Phase 121: StepTree → JoinModule shadow lowering (if-only) //! //! ## Responsibility //! //! - Convert StepTree to JoinModule (Normalized dialect) //! - Only for if-only patterns (no loops) //! - Returns None for out-of-scope patterns //! - Returns Err for patterns that should be supported but conversion failed //! //! ## Design //! //! - Input: `&StepTree` with pre-computed contract //! - No AST re-analysis (contract-only decisions) //! - Single responsibility: structure → JoinIR conversion use crate::mir::control_tree::step_tree::StepTree; use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta; use crate::mir::join_ir::JoinModule; use crate::mir::ValueId; // Phase 126 use std::collections::BTreeMap; // Phase 126 use super::contracts::{check_if_only, CapabilityCheckResult}; /// Phase 125: Normalized env layout (writes + inputs) /// /// ## Design /// /// - writes: Variables written in the function (generate ValueId) /// - inputs: Variables read from outer scope (reference ValueId, don't generate) /// /// ## SSOT /// /// - writes: From StepTreeContract.writes /// - inputs: From (StepTreeContract.reads ∩ available_inputs) #[derive(Debug, Clone)] pub struct EnvLayout { /// Variables written (generate ValueId for these) pub writes: Vec, /// Variables read from outer scope (reference ValueId from available_inputs) pub inputs: Vec, } impl EnvLayout { /// Create env layout from contract and available_inputs (Phase 125) /// /// ## Contract /// /// - writes: All variables from contract.writes (deterministic order) /// - inputs: Variables in contract.reads that are available from outer scope /// /// ## SSOT /// /// - available_inputs source: function params + CapturedEnv (pinned/captured) /// - No AST inference (don't capture from AST) pub fn from_contract( contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract, available_inputs: &std::collections::BTreeMap, ) -> Self { // Phase 125 P2: writes from contract let writes: Vec = contract.writes.iter().cloned().collect(); // Phase 125 P2: inputs = reads ∩ available_inputs (deterministic order) let inputs: Vec = contract .reads .iter() .filter(|name| available_inputs.contains_key(*name)) .cloned() .collect(); EnvLayout { writes, inputs } } } /// Box-First: StepTree → Normalized shadow lowering pub struct StepTreeNormalizedShadowLowererBox; impl StepTreeNormalizedShadowLowererBox { /// Phase 129-B: Expected env field count (writes + inputs) pub fn expected_env_field_count( step_tree: &StepTree, available_inputs: &BTreeMap, ) -> usize { let env_layout = EnvLayout::from_contract(&step_tree.contract, available_inputs); env_layout.writes.len() + env_layout.inputs.len() } /// Phase 129-B: If-as-last shape detection (no post-if) pub fn expects_join_k_as_last(step_tree: &StepTree) -> bool { use crate::mir::control_tree::step_tree::StepNode; match &step_tree.root { StepNode::If { .. } => true, StepNode::Block(nodes) => matches!(nodes.last(), Some(StepNode::If { .. })), _ => false, } } /// Try to lower an if-only StepTree to normalized form /// /// ## Returns /// /// - `Ok(None)`: Out of scope (e.g., contains loops) /// - `Ok(Some(...))`: Shadow generation succeeded /// - `Err(...)`: Should be supported but conversion failed (internal error) /// /// ## Contract /// /// - Only processes if-only patterns (no loops/breaks/continues) /// - Uses contract information only (no AST re-analysis) /// - Dev-only: caller must check `joinir_dev_enabled()` before calling /// /// ## Phase 122-126 Implementation /// /// - Generates Normalized JoinIR (env + continuation) /// - env layout: writes + inputs (Phase 125-126) /// - merge = join_k(env) tail-call (no PHI) /// - Minimal node support: If/Return/Assign(Const/Variable/BinOp(Add)) /// /// ## Phase 126: available_inputs /// /// - available_inputs: function params + CapturedEnv (SSOT) /// - inputs = reads ∩ available_inputs (deterministic order) pub fn try_lower_if_only( step_tree: &StepTree, available_inputs: &BTreeMap, ) -> Result, String> { // Phase 121 P1: Capability check (if-only scope) let capability = check_if_only(step_tree); match capability { CapabilityCheckResult::Supported => { // Phase 122-126: Generate Normalized JoinModule Self::lower_if_only_to_normalized(step_tree, available_inputs) } CapabilityCheckResult::Unsupported(_reason) => { // Out of scope for Phase 121/122 Ok(None) } } } /// Lower if-only StepTree to Normalized JoinModule (Phase 122-126) /// /// ## Design /// /// - env レイアウト: writes + inputs(決定的順序) /// - merge 形式: `join_k(env)` への tail-call(PHI 禁止) /// - 対応ノード: If/Return/Assign(最小セット) /// /// ## Phase 123-126 Node Support /// /// - Return(Integer literal): `Const + Ret(Some(vid))` /// - Return(Variable from writes): Phase 124 /// - Return(Variable from inputs): Phase 125-126 (reads-only) /// - Return(void): `Ret(None)` /// - If(minimal compare): Compare with Integer literal only /// /// ## Phase 126: available_inputs /// /// - available_inputs: function params + CapturedEnv (SSOT) /// - inputs = reads ∩ available_inputs (deterministic order) /// /// ## Returns /// /// - `Ok(Some((module, meta)))`: Normalized JoinModule生成成功 /// - `Ok(None)`: Out of scope for Phase 123-126 (unsupported patterns) /// - `Err(msg)`: 生成できるはずなのに失敗(内部エラー) fn lower_if_only_to_normalized( step_tree: &StepTree, available_inputs: &BTreeMap, ) -> Result, String> { use crate::mir::join_ir::{JoinFunction, JoinFuncId}; use crate::mir::ValueId; // Phase 126: EnvLayout 生成(available_inputs を使用) let env_layout = EnvLayout::from_contract(&step_tree.contract, available_inputs); // Phase 129-B: If-as-last join_k lowering (dev-only) if let Some((module, meta)) = Self::lower_if_node_as_last_stmt(step_tree, &env_layout)? { return Ok(Some((module, meta))); } let main_func_id = JoinFuncId::new(0); // Phase 125 P2: writes 用の ValueId 生成 let mut next_value_id = 1; let writes_params: Vec = env_layout .writes .iter() .map(|_| { let vid = ValueId(next_value_id); next_value_id += 1; vid }) .collect(); // Phase 129-B: inputs 用の ValueId 生成(writes + inputs が env params のSSOT) let inputs_params: Vec = env_layout .inputs .iter() .map(|_| { let vid = ValueId(next_value_id); next_value_id += 1; vid }) .collect(); // Phase 125 P2: env マッピング(writes + inputs) let mut env: BTreeMap = BTreeMap::new(); for (name, vid) in env_layout.writes.iter().zip(writes_params.iter()) { env.insert(name.clone(), *vid); } for (name, vid) in env_layout.inputs.iter().zip(inputs_params.iter()) { env.insert(name.clone(), *vid); } // Phase 129-B: 関数パラメータは writes + inputs(env params SSOT) let mut env_params = writes_params; env_params.extend(inputs_params); // main 関数生成 let mut main_func = JoinFunction::new( main_func_id, "main".to_string(), env_params.clone(), ); // Phase 123-128: Return node lowering // If Phase 123-128 patterns are not supported, return Ok(None) match Self::lower_return_from_tree( &step_tree.root, &mut main_func.body, &mut next_value_id, &mut env, &step_tree.contract, ) { Ok(()) => { // Success - continue } Err(msg) if msg.starts_with("[phase123/") || msg.starts_with("[phase124/") || msg.starts_with("[phase125/") || msg.starts_with("[phase128/") => { // Phase 123-128 limitation - out of scope return Ok(None); } Err(msg) => { // Real error - propagate return Err(msg); } } // JoinModule 構築 let mut module = JoinModule::new(); module.add_function(main_func); module.entry = Some(main_func_id); module.mark_normalized(); // JoinFragmentMeta 生成(最小) let meta = JoinFragmentMeta::empty(); Ok(Some((module, meta))) } /// Phase 129-B: Lower if node as last statement using join_k (dev-only) /// /// Scope: "if-as-last" only (no post-if). If it doesn't match, return Ok(None). fn lower_if_node_as_last_stmt( step_tree: &StepTree, env_layout: &EnvLayout, ) -> Result, String> { use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind}; use crate::mir::join_ir::{ConstValue, JoinFunction, JoinFuncId, JoinInst, MirLikeInst}; use crate::mir::join_ir::lowering::error_tags; use crate::mir::ValueId; use std::collections::BTreeMap; let (prefix_nodes, if_node) = match &step_tree.root { StepNode::If { .. } => (&[][..], &step_tree.root), StepNode::Block(nodes) => { let last = nodes.last(); if matches!(last, Some(StepNode::If { .. })) { (&nodes[..nodes.len() - 1], nodes.last().unwrap()) } else { return Ok(None); } } _ => return Ok(None), }; let if_node = match if_node { StepNode::If { .. } => if_node, _ => return Ok(None), }; let env_fields: Vec = env_layout .writes .iter() .chain(env_layout.inputs.iter()) .cloned() .collect(); fn alloc_value_id(next_value_id: &mut u32) -> ValueId { let vid = ValueId(*next_value_id); *next_value_id += 1; vid } let mut next_value_id: u32 = 1; let alloc_env_params = |fields: &[String], next_value_id: &mut u32| -> Vec { fields .iter() .map(|_| alloc_value_id(next_value_id)) .collect() }; let build_env_map = |fields: &[String], params: &[ValueId]| -> BTreeMap { let mut env = BTreeMap::new(); for (name, vid) in fields.iter().zip(params.iter()) { env.insert(name.clone(), *vid); } env }; let collect_env_args = |fields: &[String], env: &BTreeMap| -> Result, String> { let mut args = Vec::with_capacity(fields.len()); for name in fields { let vid = env.get(name).copied().ok_or_else(|| { error_tags::freeze_with_hint( "phase129/join_k/env_missing", &format!("env missing required field '{name}'"), "ensure env layout and env map are built from the same SSOT field list", ) })?; args.push(vid); } Ok(args) }; // IDs (stable, dev-only) let main_id = JoinFuncId::new(0); let k_then_id = JoinFuncId::new(1); let k_else_id = JoinFuncId::new(2); let join_k_id = JoinFuncId::new(3); // main(env) let main_params = alloc_env_params(&env_fields, &mut next_value_id); let mut env_main = build_env_map(&env_fields, &main_params); let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params); // Lower prefix (pre-if) statements into main for n in prefix_nodes { match n { StepNode::Stmt { kind, .. } => match kind { StepStmtKind::Assign { target, value_ast } => { if Self::lower_assign_stmt( target, value_ast, &mut main_func.body, &mut next_value_id, &mut env_main, ) .is_err() { return Ok(None); } } StepStmtKind::LocalDecl { .. } => {} _ => { return Ok(None); } }, _ => { return Ok(None); } } } // Extract return variable and branch bodies. fn split_branch_for_as_last( branch: &StepNode, ) -> Result<(&[StepNode], &crate::mir::control_tree::step_tree::AstNodeHandle), String> { use crate::mir::control_tree::step_tree::StepNode; use crate::mir::control_tree::step_tree::StepStmtKind; use crate::mir::join_ir::lowering::error_tags; match branch { StepNode::Stmt { kind, .. } => match kind { StepStmtKind::Return { value_ast } => { let ast_handle = value_ast.as_ref().ok_or_else(|| { error_tags::freeze_with_hint( "phase129/join_k/branch_return_void", "branch return must return a variable (not void)", "use `return x` in both then/else branches", ) })?; Ok((&[][..], ast_handle)) } _ => Err(error_tags::freeze_with_hint( "phase129/join_k/branch_not_return", "branch must end with return", "use `return x` in both then/else branches", )), }, StepNode::Block(nodes) => { let last = nodes.last().ok_or_else(|| { error_tags::freeze_with_hint( "phase129/join_k/branch_empty", "branch is empty", "add `return x` as the last statement of the branch", ) })?; match last { StepNode::Stmt { kind, .. } => match kind { StepStmtKind::Return { value_ast } => { let ast_handle = value_ast.as_ref().ok_or_else(|| { error_tags::freeze_with_hint( "phase129/join_k/branch_return_void", "branch return must return a variable (not void)", "use `return x` in both then/else branches", ) })?; Ok((&nodes[..nodes.len() - 1], ast_handle)) } _ => Err(error_tags::freeze_with_hint( "phase129/join_k/branch_not_return", "branch must end with return", "add `return x` as the last statement of the branch", )), }, _ => Err(error_tags::freeze_with_hint( "phase129/join_k/branch_last_not_stmt", "branch last node must be a statement return", "ensure the branch ends with `return x`", )), } } _ => Err(error_tags::freeze_with_hint( "phase129/join_k/branch_node_unsupported", "unsupported branch node", "Phase 129-B supports only Block/Return branches", )), } } fn extract_return_var_name(ast_handle: &crate::mir::control_tree::step_tree::AstNodeHandle) -> Result { use crate::ast::ASTNode; use crate::mir::join_ir::lowering::error_tags; match ast_handle.0.as_ref() { ASTNode::Variable { name, .. } => Ok(name.clone()), _ => Err(error_tags::freeze_with_hint( "phase129/join_k/return_expr_unsupported", "branch return expression must be a variable", "use `return x` (variable) in both then/else branches", )), } } let (cond_ast, then_branch, else_branch) = match if_node { StepNode::If { cond_ast, then_branch, else_branch, .. } => (cond_ast, then_branch.as_ref(), else_branch.as_deref()), _ => unreachable!(), }; let else_branch = match else_branch { Some(b) => b, None => return Ok(None), }; let (then_prefix, then_ret_ast) = match split_branch_for_as_last(then_branch) { Ok(v) => v, Err(_msg) => return Ok(None), }; let (else_prefix, else_ret_ast) = match split_branch_for_as_last(else_branch) { Ok(v) => v, Err(_msg) => return Ok(None), }; let then_ret_var = match extract_return_var_name(then_ret_ast) { Ok(v) => v, Err(_msg) => return Ok(None), }; let else_ret_var = match extract_return_var_name(else_ret_ast) { Ok(v) => v, Err(_msg) => return Ok(None), }; if then_ret_var != else_ret_var { return Ok(None); } let ret_var = then_ret_var; if !env_layout.writes.iter().any(|w| w == &ret_var) { return Ok(None); } // join_k(env_phi): return env_phi[ret_var] let join_k_params = alloc_env_params(&env_fields, &mut next_value_id); let env_join_k = build_env_map(&env_fields, &join_k_params); let ret_vid = env_join_k.get(&ret_var).copied().ok_or_else(|| { error_tags::freeze_with_hint( "phase129/join_k/ret_vid_missing", "return variable not found in join_k env", "ensure env layout includes the return variable in writes", ) })?; let mut join_k_func = JoinFunction::new(join_k_id, "join_k".to_string(), join_k_params); join_k_func.body.push(JoinInst::Ret { value: Some(ret_vid) }); // k_then(env_in): ; tailcall join_k(env_out) let then_params = alloc_env_params(&env_fields, &mut next_value_id); let mut env_then = build_env_map(&env_fields, &then_params); let mut then_func = JoinFunction::new(k_then_id, "k_then".to_string(), then_params); for n in then_prefix { match n { StepNode::Stmt { kind, .. } => match kind { StepStmtKind::Assign { target, value_ast } => { if Self::lower_assign_stmt( target, value_ast, &mut then_func.body, &mut next_value_id, &mut env_then, ) .is_err() { return Ok(None); } } StepStmtKind::LocalDecl { .. } => {} _ => { return Ok(None); } }, _ => { return Ok(None); } } } let then_args = collect_env_args(&env_fields, &env_then)?; then_func.body.push(JoinInst::Call { func: join_k_id, args: then_args, k_next: None, dst: None, }); // k_else(env_in): ; tailcall join_k(env_out) let else_params = alloc_env_params(&env_fields, &mut next_value_id); let mut env_else = build_env_map(&env_fields, &else_params); let mut else_func = JoinFunction::new(k_else_id, "k_else".to_string(), else_params); for n in else_prefix { match n { StepNode::Stmt { kind, .. } => match kind { StepStmtKind::Assign { target, value_ast } => { if Self::lower_assign_stmt( target, value_ast, &mut else_func.body, &mut next_value_id, &mut env_else, ) .is_err() { return Ok(None); } } StepStmtKind::LocalDecl { .. } => {} _ => { return Ok(None); } }, _ => { return Ok(None); } } } let else_args = collect_env_args(&env_fields, &env_else)?; else_func.body.push(JoinInst::Call { func: join_k_id, args: else_args, k_next: None, dst: None, }); // main: cond compare + conditional jump to k_then, else to k_else let (lhs_var, op, rhs_literal) = Self::parse_minimal_compare(&cond_ast.0)?; let lhs_vid = env_main.get(&lhs_var).copied().ok_or_else(|| { error_tags::freeze_with_hint( "phase129/join_k/cond_lhs_missing", &format!("condition lhs var '{lhs_var}' not found in env"), "ensure the if condition uses a variable from writes or captured inputs", ) })?; let rhs_vid = alloc_value_id(&mut next_value_id); main_func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: rhs_vid, value: ConstValue::Integer(rhs_literal), })); let cond_vid = alloc_value_id(&mut next_value_id); main_func.body.push(JoinInst::Compute(MirLikeInst::Compare { dst: cond_vid, op, lhs: lhs_vid, rhs: rhs_vid, })); let main_args = collect_env_args(&env_fields, &env_main)?; main_func.body.push(JoinInst::Jump { cont: k_then_id.as_cont(), args: main_args.clone(), cond: Some(cond_vid), }); main_func.body.push(JoinInst::Jump { cont: k_else_id.as_cont(), args: main_args, cond: None, }); // Build module let mut module = JoinModule::new(); module.add_function(main_func); module.add_function(then_func); module.add_function(else_func); module.add_function(join_k_func); module.entry = Some(main_id); module.mark_normalized(); Ok(Some((module, JoinFragmentMeta::empty()))) } /// Phase 123-128 P1-P3: Lower node from StepTree /// /// ## Support (Phase 123-128) /// /// - Return(Integer literal): Generate Const + Ret(Some(vid)) /// - Return(void): Ret(None) /// - Return(Variable): Phase 124 - lookup from env (dev-only) /// - Return(other): Fail-Fast with structured error /// - If(minimal compare): Generate Compare + Branch + Ret (P3) /// - Assign(int literal): Phase 128 - update env with Const fn lower_return_from_tree( node: &crate::mir::control_tree::step_tree::StepNode, body: &mut Vec, next_value_id: &mut u32, env: &mut std::collections::BTreeMap, contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract, ) -> Result<(), String> { use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind}; use crate::mir::join_ir::JoinInst; match node { StepNode::Block(nodes) => { // Process nodes in order for n in nodes { match n { StepNode::Stmt { kind: StepStmtKind::Assign { target, value_ast }, .. } => { // Phase 128: Process assign statement Self::lower_assign_stmt(target, value_ast, body, next_value_id, env)?; // Continue to next node } StepNode::Stmt { kind: StepStmtKind::Return { value_ast }, .. } => { return Self::lower_return_value(value_ast, body, next_value_id, env, contract); } StepNode::If { .. } => { // Phase 123 P3: Lower If node return Self::lower_if_node(n, body, next_value_id, env, contract); } _ => { // Other nodes not yet supported } } } // No return found - default to void body.push(JoinInst::Ret { value: None }); Ok(()) } StepNode::Stmt { kind: StepStmtKind::Return { value_ast }, .. } => Self::lower_return_value(value_ast, body, next_value_id, env, contract), StepNode::If { .. } => { // Phase 123 P3: Lower If node Self::lower_if_node(node, body, next_value_id, env, contract) } _ => { // No return in tree - default to void body.push(JoinInst::Ret { value: None }); Ok(()) } } } /// Phase 128: Lower assign statement (int literal only) /// /// ## Support /// /// - target: Variable name /// - value: Integer literal only (Phase 128) /// /// ## Not Supported (Fail-Fast) /// /// - value: Any expression other than Integer literal /// /// ## Effect /// /// - Generates Const instruction /// - Updates env[target] to new ValueId fn lower_assign_stmt( target: &Option, value_ast: &Option, body: &mut Vec, next_value_id: &mut u32, env: &mut std::collections::BTreeMap, ) -> Result<(), String> { use crate::ast::{ASTNode, LiteralValue}; use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst}; use crate::mir::ValueId; // Check target let target_name = target .as_ref() .ok_or_else(|| "[phase128/assign/target] Assign target must be a variable".to_string())?; // Check value_ast let value_ast = value_ast.as_ref().ok_or_else(|| { "[phase128/assign/value] Assign value AST is missing".to_string() })?; // Parse value - Phase 128: int literal only match value_ast.0.as_ref() { ASTNode::Literal { value: LiteralValue::Integer(i), .. } => { // Generate Const instruction let dst_vid = ValueId(*next_value_id); *next_value_id += 1; body.push(JoinInst::Compute(MirLikeInst::Const { dst: dst_vid, value: ConstValue::Integer(*i), })); // Update env env.insert(target_name.clone(), dst_vid); Ok(()) } _ => { // Phase 128 limitation: only int literal supported Err(format!( "[phase128/assign/unsupported] Phase128 supports int literal only Hint: Assign RHS must be an integer literal (e.g., x = 42)" )) } } } /// Phase 123-128 P3: Lower If node with minimal compare /// /// ## Support /// /// - Minimal binary comparison: Variable vs Integer literal /// - then/else: Return(Integer literal) or Return(Variable) (Phase 124) /// - Merge: Phase 128 - env update + join continuation (else保持) /// /// ## Not Supported (Fail-Fast) /// /// - Compound expressions (&&, ||) /// - Method calls /// - Complex expressions /// - Non-return statements in branches (except Assign in Phase 128) fn lower_if_node( node: &crate::mir::control_tree::step_tree::StepNode, body: &mut Vec, next_value_id: &mut u32, env: &mut std::collections::BTreeMap, contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract, ) -> Result<(), String> { use crate::mir::control_tree::step_tree::StepNode; use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst}; use crate::mir::ValueId; if let StepNode::If { cond_ast, then_branch, else_branch, .. } = node { let ast = &cond_ast.0; // Phase 123 P3: Parse minimal binary comparison only let (_lhs_var, op, rhs_literal) = Self::parse_minimal_compare(ast)?; // Generate Compare instruction // 1. Load/create lhs variable (for now, assume it's a parameter) // For Phase 123 minimal: we'll just create a load instruction placeholder // This is a simplification - real implementation would need variable resolution let lhs_vid = ValueId(*next_value_id); *next_value_id += 1; // For now, emit a const for the variable (placeholder) // Real implementation in Phase 124 will use reads facts body.push(JoinInst::Compute(MirLikeInst::Const { dst: lhs_vid, value: ConstValue::Integer(0), // Placeholder })); // 2. Create constant for rhs literal let rhs_vid = ValueId(*next_value_id); *next_value_id += 1; body.push(JoinInst::Compute(MirLikeInst::Const { dst: rhs_vid, value: ConstValue::Integer(rhs_literal), })); // 3. Generate Compare instruction let cond_vid = ValueId(*next_value_id); *next_value_id += 1; body.push(JoinInst::Compute(MirLikeInst::Compare { dst: cond_vid, op, lhs: lhs_vid, rhs: rhs_vid, })); // Phase 123 P3: Verify then/else branches contain only Return(Integer literal) Self::verify_branch_is_return_literal(then_branch)?; if let Some(else_br) = else_branch { Self::verify_branch_is_return_literal(else_br)?; } // For Phase 123-124, we generate a simplified structure: // The actual branching logic will be added in future phases // For now, just emit the then branch return Self::lower_return_from_tree(then_branch, body, next_value_id, env, contract)?; Ok(()) } else { Err("[phase123/if/internal] Expected If node".to_string()) } } /// Parse minimal binary comparison: Variable op Integer /// /// Returns: (variable_name, compare_op, integer_value) fn parse_minimal_compare( ast: &crate::ast::ASTNode, ) -> Result<(String, crate::mir::join_ir::CompareOp, i64), String> { use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; use crate::mir::join_ir::CompareOp; match ast { ASTNode::BinaryOp { operator, left, right, .. } => { // Phase 123: Only support Variable on left, Integer literal on right let var_name = match &**left { ASTNode::Variable { name, .. } => name.clone(), _ => { return Err(format!( "[phase123/if/compare_lhs_unsupported] Phase 123 only supports Variable on left side of comparison. Hint: Use simple variable comparison or wait for Phase 124" )); } }; let int_value = match &**right { ASTNode::Literal { value: LiteralValue::Integer(i), .. } => *i, _ => { return Err(format!( "[phase123/if/compare_rhs_unsupported] Phase 123 only supports Integer literal on right side of comparison. Hint: Use integer literal or wait for Phase 124" )); } }; let compare_op = match operator { BinaryOperator::Equal => CompareOp::Eq, BinaryOperator::NotEqual => CompareOp::Ne, BinaryOperator::Less => CompareOp::Lt, BinaryOperator::LessEqual => CompareOp::Le, BinaryOperator::Greater => CompareOp::Gt, BinaryOperator::GreaterEqual => CompareOp::Ge, _ => { return Err(format!( "[phase123/if/compare_op_unsupported] Phase 123 only supports comparison operators (==, !=, <, <=, >, >=). Hint: Use comparison operator or wait for Phase 124" )); } }; Ok((var_name, compare_op, int_value)) } _ => Err(format!( "[phase123/if/cond_unsupported] Phase 123 only supports binary comparisons. Hint: Use simple comparison (var == literal) or wait for Phase 124" )), } } /// Verify branch contains only Return(Integer literal) fn verify_branch_is_return_literal( branch: &crate::mir::control_tree::step_tree::StepNode, ) -> Result<(), String> { use crate::ast::{ASTNode, LiteralValue}; use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind}; match branch { StepNode::Stmt { kind: StepStmtKind::Return { value_ast }, .. } => { if let Some(ast_handle) = value_ast { let ast = &ast_handle.0; if let ASTNode::Literal { value: LiteralValue::Integer(_), .. } = &**ast { Ok(()) } else { Err(format!( "[phase123/if/branch_return_not_int_literal] Phase 123 only supports Return(Integer literal) in then/else branches. Hint: Return integer literal only or wait for Phase 124" )) } } else { Err(format!( "[phase123/if/branch_return_void] Phase 123 requires Return(Integer literal) in branches, not void return. Hint: Return integer literal or wait for Phase 124" )) } } StepNode::Block(nodes) => { // Check first node only if nodes.is_empty() { return Err(format!( "[phase123/if/branch_empty] Phase 123 requires Return(Integer literal) in branches. Hint: Add return statement" )); } Self::verify_branch_is_return_literal(&nodes[0]) } _ => Err(format!( "[phase123/if/branch_not_return] Phase 123 only supports Return(Integer literal) in then/else branches. Hint: Use return statement with integer literal" )), } } /// Phase 123-125 P1-P2-P3-P4: Lower return value /// /// ## Support /// /// - Integer literal: Generate Const + Ret(Some(vid)) /// - None: Ret(None) /// - Variable (Phase 124-125): lookup from env (writes + inputs) /// - Phase 124: writes (written variables) /// - Phase 125: inputs (reads-only from outer scope) /// - Fail-Fast if not in env (structured error with hint) /// - Other: Fail-Fast (out of scope) fn lower_return_value( value_ast: &Option, body: &mut Vec, next_value_id: &mut u32, env: &std::collections::BTreeMap, contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract, ) -> Result<(), String> { use crate::ast::{ASTNode, LiteralValue}; use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst}; use crate::mir::ValueId; match value_ast { None => { body.push(JoinInst::Ret { value: None }); Ok(()) } Some(ast_handle) => { let ast = &ast_handle.0; match &**ast { ASTNode::Literal { value, .. } => match value { LiteralValue::Integer(i) => { // Phase 123 P1: Integer literal → Const + Ret(Some(vid)) let const_vid = ValueId(*next_value_id); *next_value_id += 1; // Generate Const instruction (wrapped in Compute) body.push(JoinInst::Compute(MirLikeInst::Const { dst: const_vid, value: ConstValue::Integer(*i), })); // Generate Ret instruction body.push(JoinInst::Ret { value: Some(const_vid), }); Ok(()) } _ => { // Phase 123: Other literals not supported Err(format!( "[phase123/return/literal_unsupported] Phase 123 only supports integer literals. Hint: Use integer literal or wait for Phase 124" )) } }, ASTNode::Variable { name, .. } => { // Phase 124-125 P3-P4: Variable return support (dev-only) // Check if variable is in env (writes + inputs) if let Some(&vid) = env.get(name) { // Phase 125 P4: Variable found in env (writes or inputs) - return it body.push(JoinInst::Ret { value: Some(vid) }); Ok(()) } else { // Phase 125 P4: Variable not in env - Fail-Fast with hint // Check if variable is in reads (potential input) let in_reads = contract.reads.contains(name); let in_writes = contract.writes.contains(name); let hint = if in_reads && !in_writes { // Variable is read but not available as input format!( "Variable '{}' is read but not available from outer scope. \ Hint: Pass as function parameter, add to pinned capture, or define before if", name ) } else if !in_reads && !in_writes { // Variable is neither read nor written (undefined) format!( "Variable '{}' is undefined. \ Hint: Define variable before return or check spelling", name ) } else { // In writes but not in env (internal error) format!( "Variable '{}' in writes but not in env (internal error)", name ) }; Err(format!("[phase125/return/var_not_in_env] {}", hint)) } } _ => { // Phase 123: Other expressions not supported Err(format!( "[phase123/return/expr_unsupported] Phase 123 only supports integer literals. Hint: Simplify to literal or wait for Phase 124" )) } } } } } /// Get shadow lowering status string for dev logging /// /// ## Contract /// /// - Returns 1-line summary: "shadow_lowered=true/false reason=..." /// - Does not perform actual lowering (use `try_lower_if_only` for that) pub fn get_status_string(step_tree: &StepTree) -> String { let capability = check_if_only(step_tree); match capability { CapabilityCheckResult::Supported => { format!( "shadow_lowered=true step_tree_sig={} exits={:?} writes={:?}", step_tree.signature_basis_string(), step_tree.contract.exits, step_tree.contract.writes ) } CapabilityCheckResult::Unsupported(reason) => { format!( "shadow_lowered=false reason=\"{}\" step_tree_sig={}", reason.reason(), step_tree.signature_basis_string() ) } } } } #[cfg(test)] mod tests { use super::*; use crate::mir::control_tree::step_tree::{ StepNode, StepStmtKind, StepTreeFeatures, StepTreeSignature, }; use crate::mir::control_tree::step_tree_contract_box::StepTreeContract; fn make_if_only_tree() -> StepTree { StepTree { root: StepNode::Block(vec![]), features: StepTreeFeatures { has_if: true, has_loop: false, has_break: false, has_continue: false, has_return: false, max_if_depth: 1, max_loop_depth: 0, }, contract: StepTreeContract { exits: Default::default(), writes: Default::default(), reads: Default::default(), // Phase 124 required_caps: Default::default(), cond_sig: Default::default(), }, signature: StepTreeSignature(0), } } fn make_loop_tree() -> StepTree { StepTree { root: StepNode::Block(vec![]), features: StepTreeFeatures { has_if: false, has_loop: true, has_break: false, has_continue: false, has_return: false, max_if_depth: 0, max_loop_depth: 1, }, contract: StepTreeContract { exits: Default::default(), writes: Default::default(), reads: Default::default(), // Phase 124 required_caps: Default::default(), cond_sig: Default::default(), }, signature: StepTreeSignature(0), } } #[test] fn test_if_only_supported() { let tree = make_if_only_tree(); let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); assert!(result.unwrap().is_some()); } #[test] fn test_loop_rejected() { let tree = make_loop_tree(); let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); assert!(result.unwrap().is_none()); } #[test] fn test_status_string_if_only() { let tree = make_if_only_tree(); let status = StepTreeNormalizedShadowLowererBox::get_status_string(&tree); assert!(status.contains("shadow_lowered=true")); assert!(status.contains("step_tree_sig=")); } #[test] fn test_status_string_loop() { let tree = make_loop_tree(); let status = StepTreeNormalizedShadowLowererBox::get_status_string(&tree); assert!(status.contains("shadow_lowered=false")); assert!(status.contains("reason=\"contains loop")); } #[test] fn test_return_integer_literal() { use crate::ast::{ASTNode, LiteralValue, Span}; use crate::mir::control_tree::step_tree::AstNodeHandle; // Create StepTree with "return 7" let return_ast = Box::new(ASTNode::Literal { value: LiteralValue::Integer(7), span: Span::unknown(), }); let mut tree = make_if_only_tree(); tree.root = StepNode::Stmt { kind: StepStmtKind::Return { value_ast: Some(AstNodeHandle(return_ast)), }, span: Span::unknown(), }; // Lower to JoinModule let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); let (module, _meta) = result.unwrap().expect("Should generate JoinModule"); // Verify Const + Ret instructions assert_eq!(module.functions.len(), 1); let func = &module.functions.values().next().unwrap(); assert_eq!(func.body.len(), 2, "Should have Const + Ret"); // Check Const instruction use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst}; use crate::mir::ValueId; if let JoinInst::Compute(MirLikeInst::Const { dst, value }) = &func.body[0] { assert_eq!(*dst, ValueId(1)); if let ConstValue::Integer(i) = value { assert_eq!(*i, 7); } else { panic!("Expected Integer const"); } } else { panic!("Expected Const instruction"); } // Check Ret instruction if let JoinInst::Ret { value } = &func.body[1] { assert_eq!(value, &Some(ValueId(1))); } else { panic!("Expected Ret instruction"); } } #[test] fn test_return_void() { use crate::ast::Span; // Create StepTree with "return" (no value) let mut tree = make_if_only_tree(); tree.root = StepNode::Stmt { kind: StepStmtKind::Return { value_ast: None }, span: Span::unknown(), }; // Lower to JoinModule let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); let (module, _meta) = result.unwrap().expect("Should generate JoinModule"); // Verify Ret(None) instruction assert_eq!(module.functions.len(), 1); let func = &module.functions.values().next().unwrap(); assert_eq!(func.body.len(), 1, "Should have only Ret"); // Check Ret instruction use crate::mir::join_ir::JoinInst; if let JoinInst::Ret { value } = &func.body[0] { assert_eq!(value, &None); } else { panic!("Expected Ret instruction"); } } #[test] fn test_return_variable_out_of_scope() { // Phase 125 P4: Test Return(Variable) when variable is not in env (neither writes nor inputs) // Expected: Ok(None) because variable is not available use crate::ast::{ASTNode, Span}; use crate::mir::control_tree::step_tree::AstNodeHandle; // Create StepTree with "return x" (variable not in env) let return_ast = Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown(), }); let mut tree = make_if_only_tree(); // Phase 125: Add x to reads (to simulate variable reference) tree.contract.reads.insert("x".to_string()); tree.root = StepNode::Stmt { kind: StepStmtKind::Return { value_ast: Some(AstNodeHandle(return_ast)), }, span: Span::unknown(), }; // Phase 125 P4: Lower to JoinModule - should return Ok(None) // because x is in reads but not in available_inputs (not in env) let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); let option = result.unwrap(); assert!(option.is_none(), "Should return None when variable is in reads but not available as input"); } #[test] fn test_if_minimal_compare() { use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::mir::control_tree::step_tree::AstNodeHandle; // Create condition: flag == 1 let cond_ast = Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(ASTNode::Variable { name: "flag".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown(), }), span: Span::unknown(), }); // Create then branch: return 2 let then_branch = Box::new(StepNode::Stmt { kind: StepStmtKind::Return { value_ast: Some(AstNodeHandle(Box::new(ASTNode::Literal { value: LiteralValue::Integer(2), span: Span::unknown(), }))), }, span: Span::unknown(), }); // Create else branch: return 3 let else_branch = Some(Box::new(StepNode::Stmt { kind: StepStmtKind::Return { value_ast: Some(AstNodeHandle(Box::new(ASTNode::Literal { value: LiteralValue::Integer(3), span: Span::unknown(), }))), }, span: Span::unknown(), })); // Create If node let mut tree = make_if_only_tree(); tree.root = StepNode::If { cond: crate::mir::control_tree::step_tree::AstSummary::Other("test"), cond_ast: AstNodeHandle(cond_ast), then_branch, else_branch, span: Span::unknown(), }; // Lower to JoinModule let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); let (module, _meta) = result.unwrap().expect("Should generate JoinModule"); // Verify structure assert_eq!(module.functions.len(), 1); let func = &module.functions.values().next().unwrap(); // Should have: Const (lhs placeholder), Const (rhs), Compare, Const (return), Ret assert!(func.body.len() >= 4, "Should have at least 4 instructions"); // Verify Compare instruction exists use crate::mir::join_ir::{JoinInst, MirLikeInst}; let has_compare = func.body.iter().any(|inst| { matches!( inst, JoinInst::Compute(MirLikeInst::Compare { .. }) ) }); assert!(has_compare, "Should have Compare instruction"); } #[test] fn test_return_variable_from_env() { // Phase 124 P3: Test Return(Variable) when variable is in env (writes) use crate::ast::{ASTNode, Span}; use crate::mir::control_tree::step_tree::AstNodeHandle; // Create StepTree with "local x; return x" let mut tree = make_if_only_tree(); // Add x to writes (simulating "local x" or assignment) tree.contract.writes.insert("x".to_string()); // Create return x node tree.root = StepNode::Stmt { kind: StepStmtKind::Return { value_ast: Some(AstNodeHandle(Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown(), }))), }, span: Span::unknown(), }; // Lower to JoinModule - should succeed let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); let option = result.unwrap(); assert!(option.is_some(), "Should generate JoinModule for return x (x in writes)"); let (module, _meta) = option.unwrap(); // Verify structure assert_eq!(module.functions.len(), 1); let func = &module.functions.values().next().unwrap(); // Should have exactly 1 parameter (x from writes/env) assert_eq!(func.params.len(), 1, "Should have 1 parameter (x)"); // Should have exactly 1 instruction: Ret(Some(x)) assert_eq!(func.body.len(), 1, "Should have 1 instruction (Ret)"); // Verify Ret instruction returns the parameter (ValueId(1)) use crate::mir::join_ir::JoinInst; use crate::mir::ValueId; if let JoinInst::Ret { value } = &func.body[0] { assert_eq!(value, &Some(ValueId(1)), "Should return ValueId(1) (parameter x)"); } else { panic!("Expected Ret instruction"); } } #[test] fn test_return_variable_from_inputs_stub() { // Phase 125 P2-P4: Test Return(Variable) when variable is in inputs (reads-only) // Note: P3 (available_inputs wiring) is not implemented yet, so this test // demonstrates the structure but won't actually provide inputs use crate::ast::{ASTNode, Span}; use crate::mir::control_tree::step_tree::AstNodeHandle; // Create StepTree with "return x" where x is read-only input let mut tree = make_if_only_tree(); // Phase 125 P2: Add x to reads (simulating outer scope variable read) tree.contract.reads.insert("x".to_string()); // Create return x node tree.root = StepNode::Stmt { kind: StepStmtKind::Return { value_ast: Some(AstNodeHandle(Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown(), }))), }, span: Span::unknown(), }; // Phase 125 P2-P4: Lower to JoinModule // Because available_inputs is empty (P3 not wired), x won't be in inputs // So this should return Ok(None) (out of scope) let available_inputs = BTreeMap::new(); // Phase 126: empty for unit tests let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs); assert!(result.is_ok()); let option = result.unwrap(); // Phase 125 P2: EnvLayout.inputs will be empty (no available_inputs) // So x is not in env, and we get Ok(None) assert!(option.is_none(), "Should return None when x is in reads but not in available_inputs (P3 not wired yet)"); } }