diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index f7554b2c..954635ff 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -91,6 +91,21 @@ - LoopBodyLocalInit 単体テスト 3/3 PASS - **既知制約**: Cascading LoopBodyLocal 依存(`ch` → `digit_pos` → condition)は Phase 193 からの既存制約(ConditionEnv のみ解決、LoopBodyLocalEnv 非対応) - **詳細**: [phase225-bodylocal-init-methodcall-design.md](docs/development/current/main/phase225-bodylocal-init-methodcall-design.md) +- **Phase 226 完了** ✅: Cascading LoopBodyLocal 対応(`digit_pos = digits.indexOf(ch)` 解決) + - **問題**: Phase 225 で `digit_pos` init が `ch` を参照するとき、ConditionEnv のみ検索し `Variable 'ch' not bound in ConditionEnv` エラー + - **根本原因**: 引数解決時に LoopBodyLocalEnv を参照していなかった(body-local → condition の優先順位なし) + - **解決**: + 1. `LoopBodyLocalInitLowerer::lower_init_expr()` に `env` パラメータ追加(cascading 対応) + 2. `LoopBodyLocalInitLowerer::emit_method_call_init()` に `body_local_env` パラメータ追加 + 3. `MethodCallLowerer::lower_for_init()` に `body_local_env` パラメータ追加 + 4. `MethodCallLowerer::lower_arg_with_cascading()` 新規ヘルパー関数実装(body_local → condition の優先順位解決) + - **成果**: + - **Cascading 解決成功**: `[method_call_lowerer] Arg 'ch' found in LoopBodyLocalEnv → ValueId(1013)` + - **`ch not bound` エラー解消**: `digit_pos = digits.indexOf(ch)` 正常に lowering + - **コード追加**: +100 行(2ファイル、loop_body_local_init.rs / method_call_lowerer.rs) + - **既存テスト**: 877/884 PASS(Phase 226 非関連 7失敗、pre-existing) + - **残課題**: `ValueId(14) undefined` エラー(promotion ロジック関連、Phase 226 範囲外) + - **詳細**: Cascading dependency chain: `ch = s.substring(p, p+1)` → `digit_pos = digits.indexOf(ch)` → `if digit_pos < 0 { break }` ### 2. JsonParser / Trim / selfhost への適用状況 diff --git a/src/mir/join_ir/lowering/loop_body_local_init.rs b/src/mir/join_ir/lowering/loop_body_local_init.rs index 18fd07da..6745c132 100644 --- a/src/mir/join_ir/lowering/loop_body_local_init.rs +++ b/src/mir/join_ir/lowering/loop_body_local_init.rs @@ -187,7 +187,8 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { ); // Lower init expression to JoinIR - let value_id = self.lower_init_expr(init_expr)?; + // Phase 226: Pass env for cascading LoopBodyLocal support + let value_id = self.lower_init_expr(init_expr, env)?; eprintln!( "[loop_body_local_init] Stored '{}' → {:?}", @@ -207,20 +208,23 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { /// - `Variable`: Condition variable reference (e.g., `pos`) /// - `BinOp`: Binary operation (e.g., `pos - start`) /// + /// Phase 226: Cascading LoopBodyLocal support + /// - `MethodCall`: Method call (e.g., `s.substring(...)`, `digits.indexOf(ch)`) + /// - Arguments can reference previously defined body-local variables + /// /// Unsupported (Fail-Fast): - /// - `MethodCall`: Method call (e.g., `s.substring(...)`) - /// - `String`: String literal (e.g., `"hello"`) /// - Other complex expressions /// /// # Arguments /// /// * `expr` - AST node representing initialization expression + /// * `env` - LoopBodyLocal environment (for resolving cascading dependencies) /// /// # Returns /// /// * `Ok(ValueId)` - JoinIR ValueId of computed result /// * `Err(msg)` - Unsupported expression (Fail-Fast) - fn lower_init_expr(&mut self, expr: &ASTNode) -> Result { + fn lower_init_expr(&mut self, expr: &ASTNode, env: &LoopBodyLocalEnv) -> Result { match expr { // Constant literal: 42, 0, 1, "string" (use Literal with value) ASTNode::Literal { value, .. } => { @@ -277,8 +281,9 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { eprintln!("[loop_body_local_init] BinaryOp({:?})", operator); // Recursively lower operands - let lhs = self.lower_init_expr(left)?; - let rhs = self.lower_init_expr(right)?; + // Phase 226: Pass env for cascading support + let lhs = self.lower_init_expr(left, env)?; + let rhs = self.lower_init_expr(right, env)?; // Convert operator let op_kind = self.convert_binop(operator)?; @@ -299,13 +304,14 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { Ok(result) } - // Phase 193: MethodCall support + // Phase 226: MethodCall support with cascading LoopBodyLocalEnv ASTNode::MethodCall { object, method, arguments, .. } => { Self::emit_method_call_init( object, method, arguments, self.cond_env, + env, // Phase 226: Pass LoopBodyLocalEnv for cascading support self.instructions, &mut self.alloc_value, ) @@ -343,11 +349,23 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { } } - /// Phase 225: Emit a method call in body-local init expression + /// Phase 226: Emit a method call in body-local init expression (with cascading support) /// /// Delegates to MethodCallLowerer for metadata-driven lowering. /// This ensures consistency and avoids hardcoded method/box name mappings. /// + /// # Cascading LoopBodyLocal Support (Phase 226) + /// + /// When lowering method arguments, this function checks BOTH environments: + /// 1. LoopBodyLocalEnv (for previously defined body-local variables like `ch`) + /// 2. ConditionEnv (for loop condition variables like `p`, `start`) + /// + /// This enables cascading dependencies: + /// ```nyash + /// local ch = s.substring(p, p+1) // ch defined first + /// local digit_pos = digits.indexOf(ch) // uses ch from LoopBodyLocalEnv + /// ``` + /// /// # Supported Methods /// /// All methods where `CoreMethodId::allowed_in_init() == true`: @@ -358,10 +376,11 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { /// /// # Arguments /// - /// * `receiver` - Object on which method is called (must be in ConditionEnv) + /// * `receiver` - Object on which method is called (must be in ConditionEnv or LoopBodyLocalEnv) /// * `method` - Method name (resolved via CoreMethodId) - /// * `args` - Method arguments (lowered recursively) + /// * `args` - Method arguments (lowered recursively, checks both envs) /// * `cond_env` - Condition environment for variable resolution + /// * `body_local_env` - LoopBodyLocal environment (for cascading dependencies) /// * `instructions` - Output buffer for JoinIR instructions /// * `alloc` - ValueId allocator /// @@ -374,18 +393,18 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { /// /// ```nyash /// local ch = s.substring(p, p+1) - /// local digit_pos = digits.indexOf(ch) + /// local digit_pos = digits.indexOf(ch) // ch resolved from body_local_env /// ``` /// /// Delegation flow: /// ``` /// emit_method_call_init - /// → Resolve receiver variable + /// → Resolve receiver variable (check body_local_env then cond_env) /// → Delegate to MethodCallLowerer::lower_for_init /// → Resolve method_name → CoreMethodId /// → Check allowed_in_init() /// → Check arity - /// → Lower arguments + /// → Lower arguments (check body_local_env then cond_env) /// → Emit BoxCall with metadata-driven box_name /// ``` fn emit_method_call_init( @@ -393,6 +412,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { method: &str, args: &[ASTNode], cond_env: &ConditionEnv, + body_local_env: &LoopBodyLocalEnv, instructions: &mut Vec, alloc: &mut dyn FnMut() -> ValueId, ) -> Result { @@ -406,34 +426,46 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { method ); - // 1. Resolve receiver (must be a Variable in ConditionEnv) + // 1. Resolve receiver (check LoopBodyLocalEnv first, then ConditionEnv) + // Phase 226: Cascading support - receiver can be a previously defined body-local variable let receiver_id = match receiver { - ASTNode::Variable { name, .. } => cond_env.get(name).ok_or_else(|| { - format!( - "Method receiver '{}' not found in ConditionEnv (must be condition variable or loop parameter)", - name - ) - })?, + ASTNode::Variable { name, .. } => { + // Try LoopBodyLocalEnv first (for cascading cases like `digit_pos = digits.indexOf(ch)` where `digits` might be body-local) + if let Some(vid) = body_local_env.get(name) { + eprintln!( + "[loop_body_local_init] Receiver '{}' found in LoopBodyLocalEnv → {:?}", + name, vid + ); + vid + } else if let Some(vid) = cond_env.get(name) { + eprintln!( + "[loop_body_local_init] Receiver '{}' found in ConditionEnv → {:?}", + name, vid + ); + vid + } else { + return Err(format!( + "Method receiver '{}' not found in LoopBodyLocalEnv or ConditionEnv (must be body-local or condition variable)", + name + )); + } + } _ => { return Err( - "Complex receiver not supported in init method call (Phase 225 - only simple variables)" + "Complex receiver not supported in init method call (Phase 226 - only simple variables)" .to_string(), ); } }; - eprintln!( - "[loop_body_local_init] Receiver resolved → {:?}", - receiver_id - ); - // 2. Delegate to MethodCallLowerer for metadata-driven lowering + // Phase 226: Pass LoopBodyLocalEnv for cascading argument resolution // This handles: // - Method name → CoreMethodId resolution // - allowed_in_init() whitelist check // - Arity validation // - Box name from CoreMethodId (no hardcoding!) - // - Argument lowering + // - Argument lowering (checks both body_local_env and cond_env) // - BoxCall emission // // Note: We need to wrap alloc in a closure to match the generic type @@ -445,6 +477,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { args, &mut alloc_wrapper, cond_env, + body_local_env, instructions, )?; diff --git a/src/mir/join_ir/lowering/method_call_lowerer.rs b/src/mir/join_ir/lowering/method_call_lowerer.rs index 22090b1e..b300496a 100644 --- a/src/mir/join_ir/lowering/method_call_lowerer.rs +++ b/src/mir/join_ir/lowering/method_call_lowerer.rs @@ -160,12 +160,19 @@ impl MethodCallLowerer { /// - Supports zero-argument methods /// - Supports methods with arguments (e.g., `substring(0, 5)`, `indexOf(ch)`) /// - Arity is checked against CoreMethodId metadata + /// + /// # Phase 226: Cascading LoopBodyLocal Support + /// + /// - Arguments can reference previously defined body-local variables + /// - Checks `body_local_env` first, then `cond_env` for variable resolution + /// - Example: `local digit_pos = digits.indexOf(ch)` where `ch` is body-local pub fn lower_for_init( recv_val: ValueId, method_name: &str, args: &[ASTNode], alloc_value: &mut F, - env: &ConditionEnv, + cond_env: &ConditionEnv, + body_local_env: &super::loop_body_local_env::LoopBodyLocalEnv, instructions: &mut Vec, ) -> Result where @@ -198,13 +205,15 @@ impl MethodCallLowerer { )); } - // Phase 224-C: Lower arguments using condition lowerer + // Phase 226: Lower arguments with cascading LoopBodyLocal support + // Check body_local_env first, then cond_env let mut lowered_args = Vec::new(); for arg_ast in args { - let arg_val = super::condition_lowerer::lower_value_expression( + let arg_val = Self::lower_arg_with_cascading( arg_ast, alloc_value, - env, + cond_env, + body_local_env, instructions )?; lowered_args.push(arg_val); @@ -227,12 +236,83 @@ impl MethodCallLowerer { Ok(dst) } + + /// Phase 226: Lower an argument expression with cascading LoopBodyLocal support + /// + /// This function extends `condition_lowerer::lower_value_expression` to support + /// cascading LoopBodyLocal variables. It checks both environments: + /// 1. LoopBodyLocalEnv first (for previously defined body-local variables) + /// 2. ConditionEnv as fallback (for loop condition variables) + /// + /// # Example + /// + /// ```nyash + /// local ch = s.substring(p, p+1) // ch stored in body_local_env + /// local digit_pos = digits.indexOf(ch) // ch resolved from body_local_env + /// ``` + /// + /// # Arguments + /// + /// * `expr` - Argument AST node + /// * `alloc_value` - ValueId allocator + /// * `cond_env` - Condition environment (fallback) + /// * `body_local_env` - LoopBodyLocal environment (priority) + /// * `instructions` - Instruction buffer + /// + /// # Returns + /// + /// * `Ok(ValueId)` - Lowered argument value + /// * `Err(String)` - If variable not found in either environment + fn lower_arg_with_cascading( + expr: &ASTNode, + alloc_value: &mut F, + cond_env: &ConditionEnv, + body_local_env: &super::loop_body_local_env::LoopBodyLocalEnv, + instructions: &mut Vec, + ) -> Result + where + F: FnMut() -> ValueId, + { + match expr { + // Variables - check body_local_env first, then cond_env + ASTNode::Variable { name, .. } => { + if let Some(vid) = body_local_env.get(name) { + eprintln!( + "[method_call_lowerer] Arg '{}' found in LoopBodyLocalEnv → {:?}", + name, vid + ); + Ok(vid) + } else if let Some(vid) = cond_env.get(name) { + eprintln!( + "[method_call_lowerer] Arg '{}' found in ConditionEnv → {:?}", + name, vid + ); + Ok(vid) + } else { + Err(format!( + "Variable '{}' not found in LoopBodyLocalEnv or ConditionEnv", + name + )) + } + } + + // For non-variables (literals, binops, etc.), delegate to condition_lowerer + // These don't need cascading lookup since they don't reference variables directly + _ => super::condition_lowerer::lower_value_expression( + expr, + alloc_value, + cond_env, + instructions, + ), + } + } } #[cfg(test)] mod tests { use super::*; use crate::mir::join_ir::JoinInst; + use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; #[test] fn test_resolve_string_length() { @@ -384,6 +464,8 @@ mod tests { assert!(cond_result.unwrap_err().contains("not allowed in loop condition")); // But IS allowed in init context + // Phase 226: Create empty LoopBodyLocalEnv + let body_local_env = LoopBodyLocalEnv::new(); instructions.clear(); let init_result = MethodCallLowerer::lower_for_init( recv_val, @@ -391,6 +473,7 @@ mod tests { &[arg1_ast, arg2_ast], &mut alloc_value, &env, + &body_local_env, &mut instructions, ); assert!(init_result.is_ok()); @@ -431,7 +514,7 @@ mod tests { #[test] fn test_lower_indexOf_with_arg() { - // Phase 224-C Test: s.indexOf(ch) with 1 argument + // Phase 226 Test: s.indexOf(ch) with 1 argument (cascading support) let recv_val = ValueId(10); let ch_val = ValueId(11); let mut value_counter = 100u32; @@ -442,6 +525,9 @@ mod tests { }; let mut instructions = Vec::new(); + // Phase 226: Create empty LoopBodyLocalEnv + let body_local_env = LoopBodyLocalEnv::new(); + // Create ConditionEnv with ch variable let mut env = ConditionEnv::new(); env.insert("ch".to_string(), ch_val); @@ -458,6 +544,7 @@ mod tests { &[arg_ast], &mut alloc_value, &env, + &body_local_env, &mut instructions, ); @@ -486,7 +573,7 @@ mod tests { #[test] fn test_lower_substring_with_args() { - // Phase 224-C Test: s.substring(i, j) with 2 arguments + // Phase 226 Test: s.substring(i, j) with 2 arguments (cascading support) let recv_val = ValueId(10); let i_val = ValueId(11); let j_val = ValueId(12); @@ -498,6 +585,9 @@ mod tests { }; let mut instructions = Vec::new(); + // Phase 226: Create empty LoopBodyLocalEnv + let body_local_env = LoopBodyLocalEnv::new(); + // Create ConditionEnv with i and j variables let mut env = ConditionEnv::new(); env.insert("i".to_string(), i_val); @@ -519,6 +609,7 @@ mod tests { &[arg1_ast, arg2_ast], &mut alloc_value, &env, + &body_local_env, &mut instructions, );