feat(joinir): Phase 188 StringAppend support in Pattern2/4

- Extended Pattern2/4 whitelist to accept StringLiteral updates
- CarrierUpdateEmitter now emits JoinIR for string append
- Selective Fail-Fast: accept safe patterns, reject complex

Changes:
- pattern2_with_break.rs: StringLiteral whitelist
- pattern4_with_continue.rs: StringLiteral whitelist
- carrier_update_emitter.rs: StringLiteral JoinIR emission

Tests:
- phase188_string_append_char.hako
- phase188_string_append_literal.hako
- 10/10 carrier_update_emitter tests PASS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-09 01:09:54 +09:00
parent d4231f5d3a
commit a2933880ae
8 changed files with 160 additions and 32 deletions

View File

@ -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 生成対応

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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`

View File

@ -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:

View File

@ -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<CarrierVar> = 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;
}
_ => {}
}
}
}

View File

@ -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<CarrierVar> = 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;
}
_ => {}
}
}
}

View File

@ -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
));
}
};