diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 256d7415..a6cb5df2 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -198,10 +198,15 @@ - Complex (method call / nested BinOp) は Fail-Fast 維持 - Phase 178 の Fail-Fast は完全保持 - Phase 188+ での実装方針を確立 - - [ ] Phase 188+: StringAppendChar/Literal 実装 - - Pattern2/4 lowerer の can_lower() whitelist 拡張 - - CarrierUpdateLowerer の string append 対応 - - _parse_number minimal 版の E2E テスト + - [x] **Phase 188: StringAppend 実装** ✅ (2025-12-09) + - Task 188-1: LoopUpdateAnalyzer 拡張(既存の UpdateRhs で対応済み) + - Task 188-2: Pattern2/4 can_lower ホワイトリスト更新(UpdateRhs::Other のみ拒否) + - Task 188-3: CarrierUpdateEmitter JoinIR emission 拡張(StringLiteral → Const emission) + - Task 188-4: E2E テスト(phase188_string_append_char.hako / phase188_string_append_literal.hako) + - Task 188-5: ドキュメント更新(phase187-string-update-design.md + joinir-architecture-overview.md) + - **成果**: Pattern2/4 が安全な string 更新パターンを受理。Complex パターンのみ Fail-Fast。 + - 許可: `s = s + ch`, `s = s + "literal"` + - 拒否: `s = s + s.substring(...)` (method call), `s = s + (a + b)` (nested BinOp) - [ ] Phase 185+: Body-local Pattern2/4 統合 + String ops 有効化 - Pattern2/4 lowerer に LoopBodyLocalEnv 統合 - body-local 変数(`local temp` in loop body)の完全 MIR 生成対応 diff --git a/apps/tests/phase188_string_append_char.hako b/apps/tests/phase188_string_append_char.hako new file mode 100644 index 00000000..aa8f28e1 --- /dev/null +++ b/apps/tests/phase188_string_append_char.hako @@ -0,0 +1,19 @@ +// Phase 188: String append with character variable +// Pattern: s = s + ch (StringAppendChar) +// Pattern 2: Loop with conditional break + +static box SAppendChar { + method main() { + local s = "" + local i = 0 + loop(i < 10) { + if i == 3 { + break + } + local ch = "x" + s = s + ch + i = i + 1 + } + print(s) // Expected: "xxx" + } +} diff --git a/apps/tests/phase188_string_append_literal.hako b/apps/tests/phase188_string_append_literal.hako new file mode 100644 index 00000000..079703f4 --- /dev/null +++ b/apps/tests/phase188_string_append_literal.hako @@ -0,0 +1,18 @@ +// Phase 188: String append with literal +// Pattern: s = s + "y" (StringAppendLiteral) +// Pattern 4: Loop with continue + +static box SAppendLit { + method main() { + local s = "" + local i = 0 + loop(i < 5) { + i = i + 1 + if i > 3 { + continue + } + s = s + "y" + } + print(s) // Expected: "yyy" + } +} diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 4bdf668d..ed214dfb 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -188,14 +188,18 @@ JoinIR ラインで守るべきルールを先に書いておくよ: ### 2.3 キャリア / Exit / Boundary ライン -- **CarrierInfo / LoopUpdateAnalyzer** +- **CarrierInfo / LoopUpdateAnalyzer / CarrierUpdateEmitter** - ファイル: - `src/mir/join_ir/lowering/carrier_info.rs` - `src/mir/join_ir/lowering/loop_update_analyzer.rs` + - `src/mir/join_ir/lowering/carrier_update_emitter.rs` - 責務: - ループで更新される変数(carrier)を検出し、UpdateExpr を保持。 - Pattern 4 では実際に更新されるキャリアだけを残す。 - - **Phase 187設計**: String 更新は UpdateKind ベースのホワイトリストで扱う方針(StringAppendChar/Literal は Phase 188+ で実装予定)。 + - **Phase 188 完了** ✅: String 更新(StringAppendChar/StringAppendLiteral)を UpdateRhs ベースのホワイトリストで受理し、JoinIR BinOp を emit。 + - 許可: `UpdateRhs::Const`, `UpdateRhs::Variable`, `UpdateRhs::StringLiteral` + - 拒否: `UpdateRhs::Other`(method call / nested BinOp 等の複雑パターンのみ) + - Pattern2/4 の can_lower() で選別、carrier_update_emitter.rs で JoinIR 生成 - **ExitMeta / JoinFragmentMeta** - ファイル: `carrier_info.rs` diff --git a/docs/development/current/main/phase187-string-update-design.md b/docs/development/current/main/phase187-string-update-design.md index 8ca1d2a8..ef47f22f 100644 --- a/docs/development/current/main/phase187-string-update-design.md +++ b/docs/development/current/main/phase187-string-update-design.md @@ -356,6 +356,48 @@ Add Phase 187 entry: --- +## Phase 188 Implementation Complete (2025-12-09) + +### Implementation Summary + +Phase 188 successfully implemented StringAppendChar and StringAppendLiteral support in JoinIR patterns. + +**Changes Made**: + +1. **Pattern2/4 `can_lower()` Whitelist** (Task 188-2) + - Updated `pattern2_with_break.rs` and `pattern4_with_continue.rs` + - Allow: `UpdateRhs::Const`, `UpdateRhs::Variable`, `UpdateRhs::StringLiteral` + - Reject: `UpdateRhs::Other` (complex updates only) + - Old behavior: Rejected all string updates + - New behavior: Accept safe string patterns, reject only complex ones + +2. **CarrierUpdateLowerer JoinIR Emission** (Task 188-3) + - Updated `carrier_update_emitter.rs` (both UpdateEnv and ConditionEnv versions) + - `UpdateRhs::StringLiteral(s)` → Emit `Const { value: ConstValue::String(s) }` + `BinOp` + - `UpdateRhs::Variable(name)` → Resolve variable, emit `BinOp` (handles both numeric and string) + - `UpdateRhs::Other` → Return error (should be caught by can_lower) + +3. **E2E Test Files** (Task 188-4) + - Created `apps/tests/phase188_string_append_char.hako` (Pattern 2 with break) + - Created `apps/tests/phase188_string_append_literal.hako` (Pattern 4 with continue) + - Both tests compile and run without errors + - JoinIR generation succeeds for both patterns + +**Verification**: +```bash +# Pattern 2: StringAppendChar +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase188_string_append_char.hako +# Output: Pattern 2 triggered, JoinIR generated successfully + +# Pattern 4: StringAppendLiteral +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase188_string_append_literal.hako +# Output: Pattern 4 triggered, JoinIR generated successfully +``` + +**Key Achievement**: Phase 178's Fail-Fast is now selective - only rejects truly complex updates (method calls, nested BinOp), while allowing safe string concatenation patterns. + +--- + ## 10. Conclusion Phase 187 establishes a clear design for string update handling in JoinIR: diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 406b3b7a..141dc525 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -57,7 +57,8 @@ pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) return false; } - // Phase 178: Check for string/complex carrier updates + // Phase 178/188: Check for complex carrier updates + // Phase 188: StringAppendChar/StringAppendLiteral are now allowed // Create dummy carriers from body assignment targets for analysis let dummy_carriers: Vec = ctx.body.iter().filter_map(|node| { match node { @@ -78,15 +79,27 @@ pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) let updates = LoopUpdateAnalyzer::analyze_carrier_updates(ctx.body, &dummy_carriers); - // Check if any update is string/complex + // Phase 188: Check if any update is complex (reject only UpdateRhs::Other) + // Allow: Const (numeric), Variable (numeric/string), StringLiteral + // Reject: Other (method calls, nested BinOp) for update in updates.values() { if let UpdateExpr::BinOp { rhs, .. } = update { match rhs { - UpdateRhs::StringLiteral(_) | UpdateRhs::Other => { - eprintln!("[pattern2/can_lower] Phase 178: String/complex update detected, rejecting Pattern 2 (unsupported)"); + UpdateRhs::Const(_) => { + // Numeric: i = i + 1 (allowed) + } + UpdateRhs::Variable(_) => { + // Phase 188: StringAppendChar: s = s + ch (allowed) + // Or numeric: sum = sum + i (allowed) + } + UpdateRhs::StringLiteral(_) => { + // Phase 188: StringAppendLiteral: s = s + "x" (allowed) + } + UpdateRhs::Other => { + // Phase 188: Complex update (method call, nested BinOp) - reject + eprintln!("[pattern2/can_lower] Phase 188: Complex update detected (UpdateRhs::Other), rejecting Pattern 2"); return false; } - _ => {} } } } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs index 4225e9e9..2aea893f 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs @@ -70,7 +70,8 @@ pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) return false; } - // Phase 178: Check for string/complex carrier updates + // Phase 178/188: Check for complex carrier updates + // Phase 188: StringAppendChar/StringAppendLiteral are now allowed // Create dummy carriers from body assignment targets for analysis let dummy_carriers: Vec = ctx.body.iter().filter_map(|node| { match node { @@ -91,15 +92,27 @@ pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) let updates = LoopUpdateAnalyzer::analyze_carrier_updates(ctx.body, &dummy_carriers); - // Check if any update is string/complex + // Phase 188: Check if any update is complex (reject only UpdateRhs::Other) + // Allow: Const (numeric), Variable (numeric/string), StringLiteral + // Reject: Other (method calls, nested BinOp) for update in updates.values() { if let UpdateExpr::BinOp { rhs, .. } = update { match rhs { - UpdateRhs::StringLiteral(_) | UpdateRhs::Other => { - eprintln!("[pattern4/can_lower] Phase 178: String/complex update detected, rejecting Pattern 4 (unsupported)"); + UpdateRhs::Const(_) => { + // Numeric: i = i + 1 (allowed) + } + UpdateRhs::Variable(_) => { + // Phase 188: StringAppendChar: s = s + ch (allowed) + // Or numeric: sum = sum + i (allowed) + } + UpdateRhs::StringLiteral(_) => { + // Phase 188: StringAppendLiteral: s = s + "x" (allowed) + } + UpdateRhs::Other => { + // Phase 188: Complex update (method call, nested BinOp) - reject + eprintln!("[pattern4/can_lower] Phase 188: Complex update detected (UpdateRhs::Other), rejecting Pattern 4"); return false; } - _ => {} } } } diff --git a/src/mir/join_ir/lowering/carrier_update_emitter.rs b/src/mir/join_ir/lowering/carrier_update_emitter.rs index 306c24f2..d7f5f47f 100644 --- a/src/mir/join_ir/lowering/carrier_update_emitter.rs +++ b/src/mir/join_ir/lowering/carrier_update_emitter.rs @@ -124,15 +124,22 @@ pub fn emit_carrier_update_with_env( ) })? } - // Phase 178: String updates detected but not lowered to JoinIR yet - // The Rust MIR path handles string concatenation - // For JoinIR: just pass through the carrier param (no JoinIR update) - UpdateRhs::StringLiteral(_) | UpdateRhs::Other => { - eprintln!( - "[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough", + // Phase 188: String updates now emit JoinIR BinOp + // StringAppendLiteral: s = s + "literal" + UpdateRhs::StringLiteral(s) => { + let const_id = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_id, + value: ConstValue::String(s.clone()), + })); + const_id + } + // Phase 178/188: Complex updates (method calls) still rejected + UpdateRhs::Other => { + return Err(format!( + "Carrier '{}' has complex update (UpdateRhs::Other) - should be rejected by can_lower()", carrier.name - ); - return Ok(carrier_param); // Pass-through: no JoinIR update + )); } }; @@ -259,15 +266,22 @@ pub fn emit_carrier_update( ) })? } - // Phase 178: String updates detected but not lowered to JoinIR yet - // The Rust MIR path handles string concatenation - // For JoinIR: just pass through the carrier param (no JoinIR update) - UpdateRhs::StringLiteral(_) | UpdateRhs::Other => { - eprintln!( - "[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough", + // Phase 188: String updates now emit JoinIR BinOp + // StringAppendLiteral: s = s + "literal" + UpdateRhs::StringLiteral(s) => { + let const_id = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_id, + value: ConstValue::String(s.clone()), + })); + const_id + } + // Phase 178/188: Complex updates (method calls) still rejected + UpdateRhs::Other => { + return Err(format!( + "Carrier '{}' has complex update (UpdateRhs::Other) - should be rejected by can_lower()", carrier.name - ); - return Ok(carrier_param); // Pass-through: no JoinIR update + )); } };