diff --git a/src/mir/control_tree/normalized_shadow/builder.rs b/src/mir/control_tree/normalized_shadow/builder.rs index 8ecfb181..9797aa13 100644 --- a/src/mir/control_tree/normalized_shadow/builder.rs +++ b/src/mir/control_tree/normalized_shadow/builder.rs @@ -19,6 +19,58 @@ use crate::mir::join_ir::JoinModule; 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 { + use std::collections::BTreeSet; + + // 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; @@ -60,25 +112,26 @@ impl StepTreeNormalizedShadowLowererBox { } } - /// Lower if-only StepTree to Normalized JoinModule (Phase 122-123) + /// Lower if-only StepTree to Normalized JoinModule (Phase 122-125) /// /// ## Design /// - /// - env レイアウト: `writes` に含まれる変数だけ(決定的順序) + /// - env レイアウト: writes + inputs(決定的順序) /// - merge 形式: `join_k(env)` への tail-call(PHI 禁止) /// - 対応ノード: If/Return/Assign(最小セット) /// - /// ## Phase 123 Node Support + /// ## Phase 123-125 Node Support /// /// - Return(Integer literal): `Const + Ret(Some(vid))` - /// - Return(Variable): Out of scope (Phase 124) + /// - Return(Variable from writes): Phase 124 + /// - Return(Variable from inputs): Phase 125 (reads-only) /// - Return(void): `Ret(None)` /// - If(minimal compare): Compare with Integer literal only /// /// ## Returns /// /// - `Ok(Some((module, meta)))`: Normalized JoinModule生成成功 - /// - `Ok(None)`: Out of scope for Phase 123 (unsupported patterns) + /// - `Ok(None)`: Out of scope for Phase 123-125 (unsupported patterns) /// - `Err(msg)`: 生成できるはずなのに失敗(内部エラー) fn lower_if_only_to_normalized( step_tree: &StepTree, @@ -87,13 +140,18 @@ impl StepTreeNormalizedShadowLowererBox { use crate::mir::ValueId; use std::collections::BTreeMap; - // Phase 122: env レイアウト - let env_fields: Vec = step_tree.contract.writes.iter().cloned().collect(); + // Phase 125 P2: available_inputs (P3 で配線、今は空) + let available_inputs: BTreeMap = BTreeMap::new(); + + // Phase 125 P2: EnvLayout 生成 + let env_layout = EnvLayout::from_contract(&step_tree.contract, &available_inputs); + let main_func_id = JoinFuncId::new(0); - // env フィールドに対応する引数ValueIdを生成 + // Phase 125 P2: writes 用の ValueId 生成 let mut next_value_id = 1; - let env_params: Vec = env_fields + let writes_params: Vec = env_layout + .writes .iter() .map(|_| { let vid = ValueId(next_value_id); @@ -102,13 +160,36 @@ impl StepTreeNormalizedShadowLowererBox { }) .collect(); - // Phase 124: env マッピング(変数名 → ValueId) - let env: BTreeMap = env_fields + // Phase 125 P2: inputs 用の ValueId 生成(今は空だが、P3 で available_inputs から参照) + let inputs_params: Vec = env_layout + .inputs .iter() - .cloned() - .zip(env_params.iter().cloned()) + .map(|name| { + // Phase 125 P3: available_inputs から取得 + // 今は空なので到達しないはずだが、念のため placeholder + available_inputs + .get(name) + .copied() + .unwrap_or_else(|| { + // Should not reach here (inputs は available_inputs にある前提) + ValueId(0) // placeholder + }) + }) .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 125 P2: 関数パラメータは writes のみ(inputs は外側から来る前提) + // TODO P3: inputs も params に含める必要があるか検討 + let env_params = writes_params; + // main 関数生成 let mut main_func = JoinFunction::new( main_func_id, @@ -116,8 +197,8 @@ impl StepTreeNormalizedShadowLowererBox { env_params.clone(), ); - // Phase 123-124: Return node lowering - // If Phase 123-124 patterns are not supported, return Ok(None) + // Phase 123-125: Return node lowering + // If Phase 123-125 patterns are not supported, return Ok(None) match Self::lower_return_from_tree( &step_tree.root, &mut main_func.body, @@ -128,8 +209,12 @@ impl StepTreeNormalizedShadowLowererBox { Ok(()) => { // Success - continue } - Err(msg) if msg.starts_with("[phase123/") || msg.starts_with("[phase124/") => { - // Phase 123-124 limitation - out of scope + Err(msg) + if msg.starts_with("[phase123/") + || msg.starts_with("[phase124/") + || msg.starts_with("[phase125/") => + { + // Phase 123-125 limitation - out of scope return Ok(None); } Err(msg) => { @@ -404,13 +489,16 @@ impl StepTreeNormalizedShadowLowererBox { } } - /// Phase 123-124 P1-P2-P3: Lower return value + /// Phase 123-125 P1-P2-P3-P4: Lower return value /// /// ## Support /// /// - Integer literal: Generate Const + Ret(Some(vid)) /// - None: Ret(None) - /// - Variable: Phase 124 - lookup from env (dev-only, Fail-Fast if not in env) + /// - 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, @@ -458,20 +546,41 @@ impl StepTreeNormalizedShadowLowererBox { } }, ASTNode::Variable { name, .. } => { - // Phase 124 P3: Variable return support (dev-only) - // Check if variable is in env (writes-derived) + // 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) { - // Variable found in env - return it + // Phase 125 P4: Variable found in env (writes or inputs) - return it body.push(JoinInst::Ret { value: Some(vid) }); Ok(()) } else { - // Variable not in env - out of scope for Phase 124 - // Phase 124 only supports Return(Variable) when variable is in env (writes) - // Variables in reads-only or undefined are not supported - Err(format!( - "[phase124/return/var_not_in_env] Variable '{}' not in env (writes). Phase 124 only supports return of written variables. Hint: Assign variable before return", - name - )) + // 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)) } } _ => { @@ -684,16 +793,22 @@ mod tests { #[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) + // 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)), @@ -701,10 +816,12 @@ mod tests { span: Span::unknown(), }; - // Lower to JoinModule - should return Ok(None) (out of scope for Phase 123) + // Phase 125 P4: Lower to JoinModule - should return Ok(None) + // because x is in reads but not in available_inputs (not in env) let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree); assert!(result.is_ok()); - assert!(result.unwrap().is_none(), "Should return None for Phase 123 unsupported patterns"); + let option = result.unwrap(); + assert!(option.is_none(), "Should return None when variable is in reads but not available as input"); } #[test] @@ -834,4 +951,41 @@ mod tests { 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 result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree); + 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)"); + } }