diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 37a6297e..f02c5eb8 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -69,6 +69,13 @@ - **Root Cause**: Break condition AST が元の変数名(digit_pos)を保持したまま lowerer に渡される - **Next Steps**: Option B(promoted variable tracking)で lowerer に昇格済み変数を通知する仕組みを追加(1-2h) - **詳細**: [PHASE_224_SUMMARY.md](docs/development/current/main/PHASE_224_SUMMARY.md) +- **Phase 224-D 完了** ✅: ConditionAlias 導入(昇格変数の条件参照解決) + - **ConditionAlias 型追加**: `CarrierInfo` に `condition_aliases: Vec` フィールド追加 + - **Promoter 側記録**: DigitPosPromoter / TrimPatternInfo が昇格時に alias を記録(`digit_pos` → `is_digit_pos`) + - **Pattern2 統合**: 昇格・merge 後に join_id 割り当て、ConditionEnv に alias を追加(`digit_pos` → ValueId(104)) + - **CarrierInfo 構造修正**: DigitPosPromoter が carriers list に追加する形に変更(loop_var_name 置換ではなく) + - **検証**: `phase2235_p2_digit_pos_min.hako` で alias 解決成功、エラーが次段階(substring init)に進展 + - **残課題**: substring method in body-local init(Phase 193 limitation) ### 2. JsonParser / Trim / selfhost への適用状況 diff --git a/docs/development/current/main/PHASE_224_SUMMARY.md b/docs/development/current/main/PHASE_224_SUMMARY.md index c75fb6d7..f527f377 100644 --- a/docs/development/current/main/PHASE_224_SUMMARY.md +++ b/docs/development/current/main/PHASE_224_SUMMARY.md @@ -1,7 +1,7 @@ # Phase 224: A-4 DigitPos Promoter - Implementation Summary **Date**: 2025-12-10 -**Status**: Core Implementation Complete, Integration Requires Additional Work +**Status**: Core Implementation Complete(Phase 224-D まで反映済み、init MethodCall は別 Phase) **Branch**: main **Commits**: TBD @@ -14,8 +14,9 @@ Phase 224 successfully implemented the **DigitPosPromoter** Box for A-4 pattern ✅ **Complete**: DigitPosPromoter implementation with full unit test coverage (6/6 tests passing) ✅ **Complete**: Integration into LoopBodyCondPromoter orchestrator ✅ **Complete**: Two-tier promotion strategy (A-3 Trim → A-4 DigitPos fallback) -✅ **Verified**: Promotion detection working correctly in Pattern2 pipeline -⚠️ **Partial**: Full E2E flow blocked by lowerer integration issue +✅ **Verified**: Promotion detection working correctly in Pattern2/4 pipeline +✅ **Complete** (Phase 224-D): ConditionEnv alias bridge(`digit_pos` → `is_digit_pos`)実装 +⚠️ **Partial**: Full E2E flowは body-local init の MethodCall 制約で一部ブロック中 --- @@ -61,7 +62,7 @@ Step 2: Try A-4 DigitPos promotion (DigitPosPromoter) Step 3: Fail-Fast with clear error message ``` -**Logs Verify Success**: +**Logs Verify Success(昇格フェーズ)**: ``` [cond_promoter] A-3 Trim promotion failed: No promotable Trim pattern detected [cond_promoter] Trying A-4 DigitPos promotion... @@ -74,10 +75,10 @@ Step 3: Fail-Fast with clear error message ## Current Limitation: Lowerer Integration Gap -### Problem Statement +### Problem Statement(当初) / Phase 224-D での一部解消 -**Symptom**: E2E test fails despite successful promotion -**Root Cause**: `lower_loop_with_break_minimal` performs independent LoopBodyLocal check +**Symptom(224 実装直後)**: E2E test fails despite successful promotion +**Root Cause**: `lower_loop_with_break_minimal` performs independent LoopBodyLocal check **Result**: Promoted variables are detected as "unsupported" by the lowerer ### Error Flow @@ -108,29 +109,32 @@ But for A-4 DigitPos (Phase 223.5), we: - Merge carrier into CarrierInfo ✅ - **BUT**: Break condition AST still contains `digit_pos` ❌ -### Root Cause Analysis +### Root Cause Analysis(Phase 224 時点) The break condition is an **AST node** containing: ```nyash if digit_pos < 0 { break } ``` -After promotion: +After promotion(224 時点): - CarrierInfo knows about `is_digit_pos` carrier ✅ - LoopBodyCondPromoter recorded the promotion ✅ -- **But**: AST node still says `digit_pos`, not `is_digit_pos` ❌ +- **But**: AST node still says `digit_pos`, not `is_digit_pos` → ConditionEnv から `digit_pos` が見えない ❌ -When lower_loop_with_break_minimal analyzes the condition: -```rust -let cond_scope = LoopConditionScopeBox::analyze(&conditions); -if cond_scope.has_loop_body_local() { - // ERROR: Still sees "digit_pos" in AST! -} -``` +Phase 224-D では AST を直接書き換えるのではなく、 +**ConditionAlias(old_name → carrier_name)を CarrierInfo/ConditionEnv に導入する**ことで +「`digit_pos` という名前で条件式から参照された場合も、内部的には `is_digit_pos` carrier を読む」 +というブリッジを追加している。 + +これにより: +- LoopBodyLocal 昇格後に `digit_pos < 0` のような条件があっても、 + ConditionEnvBuilder が ConditionAlias を介して `is_digit_pos` の ValueId に解決できるようになった。 +- 「LoopBodyLocal が条件にあることによる not‑bound エラー」は解消され、 + **現時点の Blocker は body‑local init MethodCall(substring など)の lowering 制約だけ**になった。 --- -## Solution Options (Phase 224-continuation) +## Solution Options (Phase 224-continuation 時点の整理) ### Option A: AST Rewriting (Comprehensive) @@ -146,30 +150,26 @@ if cond_scope.has_loop_body_local() { **Cons**: AST rewriting is complex, error-prone **Effort**: ~2-3 hours -### Option B: Promoted Variable Tracking (Surgical) +### Option B: Promoted Variable Tracking + ConditionAlias(採用済み) -**Approach**: Add metadata to track promoted variables, exclude from LoopBodyLocal check +**Approach(決定案)**: +LoopBodyLocal を carrier に昇格した事実を **CarrierInfo(promoted_loopbodylocals + ConditionAlias)** に +メタデータとして記録し、ConditionEnvBuilder 側で「元の変数名 → carrier 名」のエイリアスを解決する。 -**Implementation**: -1. Add `promoted_loopbodylocals: Vec` to CarrierInfo -2. In Phase 223.5, record `promoted_var` in CarrierInfo -3. Modify lower_loop_with_break_minimal signature: - ```rust - fn lower_loop_with_break_minimal( - ..., - promoted_vars: &[String], // NEW - ) -> Result<...> - ``` -4. In LoopConditionScopeBox::analyze(), filter out promoted_vars: - ```rust - if cond_scope.has_loop_body_local_except(promoted_vars) { - // Only error if non-promoted LoopBodyLocal exists - } - ``` +実装(Phase 224-cont / 224-D): +1. `CarrierInfo` に `promoted_loopbodylocals: Vec` と `condition_aliases: Vec` を追加。 +2. LoopBodyCondPromoter(Trim/DigitPos の両方)で昇格成功時に + `promoted_loopbodylocals.push("digit_pos")` と + `condition_aliases.push(ConditionAlias { old_name: "digit_pos", carrier_name: "is_digit_pos" })` を記録。 +3. Pattern2/4 lowerer は ConditionEnvBuilder v2 を呼ぶ際に `&carrier_info.condition_aliases` を渡す。 +4. ConditionEnvBuilder は `var_name == "digit_pos"` のような未解決変数を見つけた場合、 + ConditionAlias を使って `carrier_name == "is_digit_pos"` に解決し、その ValueId を Condition 役の Param としてバインド。 -**Pros**: Minimal changes, surgical fix -**Cons**: Adds parameter to lowerer API -**Effort**: ~1-2 hours +効果: +- LoopBodyLocal 条件パターン(A-3 Trim/A-4 DigitPos)は、 + **AST 書き換えなしで「条件式から見える名前」と「carrier 実体」を橋渡しできる**。 +- Pattern2/4 や LoopConditionScopeBox は「昇格済み LoopBodyLocal」とそれ以外を区別できるようになり、 + 不要な Fail‑Fast を避けつつ、未昇格の LoopBodyLocal には引き続き厳格に対応できる。 ### Option C: DigitPosLoopHelper Metadata (Consistent) diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index a91fa827..1de775ec 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -341,7 +341,8 @@ Local Region (1000+): - **Two-tier promotion**: Step1 で A-3 Trim 試行 → 失敗なら Step2 で A-4 DigitPos 試行 → 両方失敗で Fail-Fast。 - **DigitPosPromoter 統合**: cascading indexOf パターン(substring → indexOf → comparison)の昇格をサポート。 - **Unit test 完全成功**: 6/6 PASS(promoter 自体は完璧動作)。 - - **Lowerer Integration Gap**: lower_loop_with_break_minimal が昇格済み変数を認識せず、独立チェックでエラー検出(Phase 224-continuation で対応予定)。 + - **Phase 224-D**: ConditionAlias/CarrierInfo との連携により、昇格済み LoopBodyLocal 名(`digit_pos` 等)を ConditionEnv から見えるようにブリッジ。 + - **残りの制約**: body-local init の MethodCall(`substring` 等)の lowering は Phase 193/224-B/C のスコープ外で、今後の Phase で対応。 - 設計原則: - **Thin coordinator**: 専門 Promoter(LoopBodyCarrierPromoter / DigitPosPromoter)に昇格ロジックを委譲。 - **Pattern-agnostic**: Pattern2 (break) / Pattern4 (continue) の統一入口として機能。 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 f993fbc8..a506016f 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 @@ -210,16 +210,6 @@ impl MirBuilder { } } - // Phase 201: Allocate carrier ValueIds from Param region and SET join_id - // This ensures carriers are also in the safe Param region - // CRITICAL: We must set join_id so the lowerer can access it - for carrier in &mut carrier_info.carriers { - let carrier_join_id = join_value_space.alloc_param(); - carrier.join_id = Some(carrier_join_id); - eprintln!("[pattern2/phase201] Allocated carrier '{}' param ID: {:?}", - carrier.name, carrier_join_id); - } - // Phase 191: Create empty body-local environment // LoopBodyLocalInitLowerer will populate it during init lowering use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; @@ -228,10 +218,6 @@ impl MirBuilder { eprintln!("[pattern2/body-local] Phase 201: Created empty body-local environment (param_count={})", join_value_space.param_count()); - // Phase 201: Create alloc_join_value closure using JoinValueSpace - // This ensures all param allocations go through the unified space - let mut alloc_join_value = || join_value_space.alloc_param(); - // Debug: Log condition bindings eprintln!("[cf_loop/pattern2] Phase 201: ConditionEnv contains {} variables:", env.len()); eprintln!(" Loop param '{}' → JoinIR {:?}", loop_var_name, env.get(&loop_var_name)); @@ -352,6 +338,62 @@ impl MirBuilder { } } + // Phase 224-D: Allocate join_ids for ALL carriers (including newly merged ones) + // This must happen AFTER promotion/merge to include promoted carriers + eprintln!("[pattern2/phase224d] Allocating join_ids for {} carriers", carrier_info.carriers.len()); + for carrier in &mut carrier_info.carriers { + let carrier_join_id = join_value_space.alloc_param(); + carrier.join_id = Some(carrier_join_id); + eprintln!("[pattern2/phase224d] Allocated carrier '{}' param ID: {:?}", + carrier.name, carrier_join_id); + } + + // Phase 224-D: Save carriers with join_ids BEFORE filtering + let carriers_with_join_ids = carrier_info.carriers.clone(); + + // Phase 224-D: Add condition aliases to ConditionEnv + // This allows promoted variables to be referenced by their original names in conditions + for alias in &carrier_info.condition_aliases { + // Check if the carrier_name matches the loop_var_name (promoted as main carrier) + if alias.carrier_name == carrier_info.loop_var_name { + // Use loop variable's join_id from env + if let Some(join_id) = env.get(&carrier_info.loop_var_name) { + env.insert(alias.old_name.clone(), join_id); + eprintln!( + "[pattern2/phase224d] Added condition alias '{}' → loop_var '{}' (join_id={:?})", + alias.old_name, carrier_info.loop_var_name, join_id + ); + } + } else { + // Find the carrier's join_id in the carriers list (BEFORE filtering, with join_ids) + if let Some(carrier) = carriers_with_join_ids.iter().find(|c| c.name == alias.carrier_name) { + if let Some(join_id) = carrier.join_id { + // Add alias mapping: old_name → carrier's join_id + env.insert(alias.old_name.clone(), join_id); + eprintln!( + "[pattern2/phase224d] Added condition alias '{}' → carrier '{}' (join_id={:?})", + alias.old_name, alias.carrier_name, join_id + ); + } else { + eprintln!( + "[pattern2/phase224d] WARNING: Carrier '{}' has no join_id yet!", + alias.carrier_name + ); + } + } else { + eprintln!( + "[pattern2/phase224d] WARNING: Carrier '{}' not found in carriers list!", + alias.carrier_name + ); + } + } + } + + // Phase 201: Create alloc_join_value closure using JoinValueSpace + // This ensures all param allocations go through the unified space + // NOTE: Must be created AFTER all direct join_value_space.alloc_param() calls + let mut alloc_join_value = || join_value_space.alloc_param(); + // Phase 180-3: Delegate Trim/P5 processing to TrimLoopLowerer let effective_break_condition = if let Some(trim_result) = super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop( self, diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs index 4ee102ff..355419cd 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs @@ -73,6 +73,7 @@ impl Pattern4CarrierAnalyzer { carriers: updated_carriers, trim_helper: all_carriers.trim_helper.clone(), promoted_loopbodylocals: all_carriers.promoted_loopbodylocals.clone(), // Phase 224 + condition_aliases: all_carriers.condition_aliases.clone(), // Phase 224-D }) } diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs index 3af9b280..90b6ca50 100644 --- a/src/mir/join_ir/lowering/carrier_info.rs +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -18,6 +18,29 @@ use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism +/// Phase 224-D: Alias for promoted LoopBodyLocal in condition expressions +/// +/// When a LoopBodyLocal variable is promoted to a carrier, the original variable +/// name must still be resolvable in condition expressions for backward compatibility. +/// +/// # Example +/// +/// ```ignore +/// // Original variable: "digit_pos" +/// // Promoted carrier: "is_digit_pos" +/// ConditionAlias { +/// old_name: "digit_pos".to_string(), +/// carrier_name: "is_digit_pos".to_string(), +/// } +/// ``` +#[derive(Debug, Clone)] +pub struct ConditionAlias { + /// Original variable name (e.g., "digit_pos") + pub old_name: String, + /// Promoted carrier name (e.g., "is_digit_pos") + pub carrier_name: String, +} + /// Information about a single carrier variable #[derive(Debug, Clone)] pub struct CarrierVar { @@ -52,6 +75,12 @@ pub struct CarrierInfo { /// during condition promotion (e.g., DigitPosPromoter). The lowerer should skip /// LoopBodyLocal checks for these variables. pub promoted_loopbodylocals: Vec, + /// Phase 224-D: Condition aliases for promoted LoopBodyLocal variables + /// + /// Maps old variable names to their promoted carrier names for condition resolution. + /// This allows break/continue conditions to reference promoted variables by their + /// original names (e.g., `if digit_pos < 0` still works after promotion to "is_digit_pos"). + pub condition_aliases: Vec, } impl CarrierInfo { @@ -113,6 +142,7 @@ impl CarrierInfo { carriers, trim_helper: None, // Phase 171-C-5: No Trim pattern by default promoted_loopbodylocals: Vec::new(), // Phase 224: No promoted variables by default + condition_aliases: Vec::new(), // Phase 224-D: No aliases by default }) } @@ -171,6 +201,7 @@ impl CarrierInfo { carriers, trim_helper: None, // Phase 171-C-5: No Trim pattern by default promoted_loopbodylocals: Vec::new(), // Phase 224: No promoted variables by default + condition_aliases: Vec::new(), // Phase 224-D: No aliases by default }) } @@ -198,6 +229,7 @@ impl CarrierInfo { carriers, trim_helper: None, // Phase 171-C-5: No Trim pattern by default promoted_loopbodylocals: Vec::new(), // Phase 224: No promoted variables by default + condition_aliases: Vec::new(), // Phase 224-D: No aliases by default } } @@ -258,6 +290,13 @@ impl CarrierInfo { self.promoted_loopbodylocals.push(promoted_var.clone()); } } + + // Phase 224-D: Merge condition_aliases (deduplicate by old_name) + for alias in &other.condition_aliases { + if !self.condition_aliases.iter().any(|a| a.old_name == alias.old_name) { + self.condition_aliases.push(alias.clone()); + } + } } /// Phase 171-C-5: Get Trim pattern helper diff --git a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs index 762f68b9..ad4c3d94 100644 --- a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs @@ -83,6 +83,12 @@ impl TrimPatternInfo { // Phase 171-C-5: Attach TrimLoopHelper for pattern-specific lowering logic carrier_info.trim_helper = Some(TrimLoopHelper::from_pattern_info(self)); + // Phase 224-D: Record condition alias for promoted variable + carrier_info.condition_aliases.push(crate::mir::join_ir::lowering::carrier_info::ConditionAlias { + old_name: self.var_name.clone(), + carrier_name: self.carrier_name.clone(), + }); + carrier_info } } diff --git a/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs b/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs index ea85c846..15e5561a 100644 --- a/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs @@ -180,17 +180,37 @@ impl DigitPosPromoter { ); // Step 6: Build CarrierInfo + // For DigitPos pattern, we add a NEW carrier (not replace loop_var) let carrier_name = format!("is_{}", var_in_cond); - let carrier_info = CarrierInfo::with_carriers( - carrier_name.clone(), - ValueId(0), // Placeholder (will be remapped) - vec![], + + use crate::mir::join_ir::lowering::carrier_info::CarrierVar; + let promoted_carrier = CarrierVar { + name: carrier_name.clone(), + host_id: ValueId(0), // Placeholder (will be remapped) + join_id: None, // Will be allocated later + }; + + // Create CarrierInfo with a dummy loop_var_name (will be ignored during merge) + let mut carrier_info = CarrierInfo::with_carriers( + "__dummy_loop_var__".to_string(), // Placeholder, not used + ValueId(0), // Placeholder + vec![promoted_carrier], ); + // Phase 224-D: Record condition alias for promoted variable + carrier_info.condition_aliases.push(crate::mir::join_ir::lowering::carrier_info::ConditionAlias { + old_name: var_in_cond.clone(), + carrier_name: carrier_name.clone(), + }); + eprintln!( "[digitpos_promoter] A-4 DigitPos pattern promoted: {} → {}", var_in_cond, carrier_name ); + eprintln!( + "[digitpos_promoter] Phase 224-D: Added condition alias '{}' → '{}'", + var_in_cond, carrier_name + ); return DigitPosPromotionResult::Promoted { carrier_info,