diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 7b59da1d..69bb0ab8 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -357,14 +357,25 @@ 38. **Follow-up(コミット済み✅ `0aad016b` 2025-12-13)**: legacy promoted lookup の deprecation warning を局所化 - `ScopeManager::lookup` 内の legacy 呼び出しを `#[allow(deprecated)]` で包み、全ビルドでの警告を抑制 +39. **Phase 78-A(コミット済み✅ `10e78fa3` 2025-12-13)**: PromotedBindingRecorder の整理(dev-only) + - binding_map の受け渡しを Box 化し、検出器(Detector)と記録(Recorder)の責務を分離。 + - 以後の “BindingId を供給する導線” の議論を単純化するための整地。 + +40. **Phase 79(コミット済み✅ `48bdf2fb` 2025-12-13)**: Detector/Recorder separation + BindingMapProvider + - `BindingMapProvider` を導入し、`#[cfg(feature="normalized_dev")]` の散在を抑制する方向へ。 + --- ## 🚀 次フェーズ候補(Phase 78+) -### Phase 78(dev-only): BindingId Migration を “完結” に寄せる -- Pattern3/4 で binding_id を供給する導線を追加し、name fallback を観測可能に縮退 -- Phase 77 で先送りした E2E tests 4本を追加(DigitPos/Trim/P3/P4) -- 余裕があれば: legacy name-based promoted lookup の撤去計画を docs に明記(削除は Phase 79 でも可) +### Phase 78-B(dev-only): “promoted carriers” を BindingId で接続する(進行中) +- SSOT: `docs/development/current/main/phase78-bindingid-promoted-carriers.md` +- 目的: `BindingId(original)` → `BindingId(promoted)` → `ValueId(join)` の鎖を作り、name-hack 依存を減らす。 +- 残タスク(優先順): + 1) ExprLowerer/ConditionLoweringBox の call-site で `lookup_with_binding()` を実際に使う(BindingId が効く状態にする) + 2) Trim/Pattern4 側の “BindingId → join” を E2E で固定(DigitPos 以外の回帰ガード) + 3) Pattern3/4 の本番導線へ拡張(P3 if-sum / P4 continue の binding 供給) + 4) legacy name-based promoted lookup の縮退 → 撤去計画を docs に固定(削除自体は Phase 80+ でも可) --- diff --git a/docs/development/current/main/phase78-bindingid-promoted-carriers.md b/docs/development/current/main/phase78-bindingid-promoted-carriers.md new file mode 100644 index 00000000..b8b94f1f --- /dev/null +++ b/docs/development/current/main/phase78-bindingid-promoted-carriers.md @@ -0,0 +1,61 @@ +## Phase 78: BindingId for Promoted Carriers (dev-only) + +### Goal + +Make LoopBodyLocal promotion (DigitPos/Trim) **BindingId-aware** without relying on fragile name-based hacks like `format!("is_{}", name)`. + +This phase introduces a dev-only identity link: + +- `BindingId(original LoopBodyLocal)` → `BindingId(promoted carrier)` → `ValueId(join carrier param)` + +### Problem + +Promotion creates synthetic carrier names (例: `is_digit_pos`, `is_ch_match`) that do not exist as source-level bindings. + +- `PromotedBindingRecorder` expects both original/promoted names in `MirBuilder.binding_map`. +- For promoted carriers this is not true by construction. +- For LoopBodyLocal variables, promotion can happen before their `local` is lowered, so the original name may also be absent. + +### Solution (current state) + +#### 1) CarrierVar gets optional BindingId (dev-only) + +- `src/mir/join_ir/lowering/carrier_info.rs` + - `CarrierVar.binding_id: Option` (feature `normalized_dev`) + +#### 2) CarrierBindingAssigner box + +- `src/mir/join_ir/lowering/carrier_binding_assigner.rs` + - Ensures both sides have BindingIds: + - If `original_name` is missing in `builder.binding_map`, allocate a temporary BindingId. + - If `promoted_carrier_name` is missing, allocate a temporary BindingId. + - Records: + - `carrier_info.promoted_bindings[original_bid] = promoted_bid` + - `CarrierVar.binding_id = Some(promoted_bid)` for the promoted carrier + - Restores `builder.binding_map` after recording (synthetic names do not leak into the source map). + +#### 3) Pattern2/Pattern4 wiring + +- Pattern2 (break): `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` + - Calls `CarrierBindingAssigner::assign_promoted_binding()` immediately after promotion. + - Registers `BindingId → ValueId` via `ConditionEnv.register_carrier_binding()` after carrier join_id allocation. + - Keeps legacy name-based aliasing (`digit_pos` → `is_digit_pos`) for now, but removes local “guess naming” by using the promoted carrier name returned by the promoter. + +- Pattern4 (continue): `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` + - Calls `CarrierBindingAssigner::assign_promoted_binding()` after promotion (analysis metadata only, since Pattern4’s current lowering path does not use JoinValueSpace params). + +### Tests + +- Unit test (dev-only): `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` + - `phase78_promoted_binding_is_recorded_for_digitpos` + - Asserts: + - `promoted_bindings` has one mapping + - promoted carrier has `binding_id` + - `ConditionEnv.binding_id_map[promoted_bid] == promoted_carrier.join_id` + +### Open Follow-ups (Phase 79+) + +1. Wire `ScopeManager::lookup_with_binding()` into ExprLowerer/ConditionLoweringBox call-sites (so BindingId actually drives resolution). +2. Extend coverage to Trim and other promotion shapes (additional unit/E2E tests). +3. Shrink/remove legacy name-based promoted lookup (`resolve_promoted_join_id`) after call-sites consistently provide BindingId. + diff --git a/src/mir/builder/control_flow/joinir/patterns/common_init.rs b/src/mir/builder/control_flow/joinir/patterns/common_init.rs index f86283c7..654978ea 100644 --- a/src/mir/builder/control_flow/joinir/patterns/common_init.rs +++ b/src/mir/builder/control_flow/joinir/patterns/common_init.rs @@ -164,6 +164,8 @@ impl CommonPatternInitializer { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Default init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228: Default + #[cfg(feature = "normalized_dev")] + binding_id: None, }) } else { None diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs index 2f3e1de9..17947d0e 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding.rs @@ -144,6 +144,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -187,6 +189,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "sum".to_string(), @@ -194,6 +198,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, ], ); @@ -240,6 +246,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -270,6 +278,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -300,6 +310,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -333,6 +345,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs index 207bd437..669311e6 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs @@ -108,6 +108,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs index 3d1d118d..8082f753 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs @@ -92,6 +92,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -131,6 +133,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "sum".to_string(), @@ -138,6 +142,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, ], ); diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs index a9b56b71..4b234285 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs @@ -68,6 +68,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -89,6 +91,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "sum".to_string(), @@ -96,6 +100,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, ], ); @@ -120,6 +126,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -142,6 +150,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); @@ -164,6 +174,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }], ); 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 bf9f4dbf..c37ae70e 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 @@ -242,6 +242,8 @@ fn promote_and_prepare_carriers( Some(&inputs.scope), ); + let mut promoted_pairs: Vec<(String, String)> = Vec::new(); + if cond_scope.has_loop_body_local() { let promotion_req = ConditionPromotionRequest { loop_param_name: &inputs.loop_var_name, @@ -260,6 +262,26 @@ fn promote_and_prepare_carriers( promoted_var, carrier_name, } => { + promoted_pairs.push((promoted_var.clone(), carrier_name.clone())); + + #[cfg(feature = "normalized_dev")] + { + use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner; + let mut promoted_carrier = promoted_carrier; + CarrierBindingAssigner::assign_promoted_binding( + builder, + &mut promoted_carrier, + &promoted_var, + &carrier_name, + ) + .map_err(|e| format!("[phase78/binding_assign] {:?}", e))?; + inputs.carrier_info.merge_from(&promoted_carrier); + } + #[cfg(not(feature = "normalized_dev"))] + { + inputs.carrier_info.merge_from(&promoted_carrier); + } + log_pattern2( verbose, "cond_promoter", @@ -273,7 +295,6 @@ fn promote_and_prepare_carriers( .carrier_info .promoted_loopbodylocals .push(promoted_var.clone()); - inputs.carrier_info.merge_from(&promoted_carrier); log_pattern2( verbose, @@ -346,6 +367,12 @@ fn promote_and_prepare_carriers( for carrier in &mut inputs.carrier_info.carriers { let carrier_join_id = inputs.join_value_space.alloc_param(); carrier.join_id = Some(carrier_join_id); + #[cfg(feature = "normalized_dev")] + if let Some(binding_id) = carrier.binding_id { + inputs + .env + .register_carrier_binding(binding_id, carrier_join_id); + } log_pattern2( verbose, "phase224d", @@ -356,50 +383,26 @@ fn promote_and_prepare_carriers( ); } - for promoted_var in &inputs.carrier_info.promoted_loopbodylocals { - let candidate_names = vec![ - format!("is_{}", promoted_var), - format!("is_{}_match", promoted_var), - ]; - - for carrier_name in candidate_names { - if carrier_name == inputs.carrier_info.loop_var_name { - if let Some(join_id) = inputs.env.get(&inputs.carrier_info.loop_var_name) { - inputs - .env - .insert(promoted_var.clone(), join_id); - log_pattern2( - verbose, - "phase229", - format!( - "Dynamically resolved promoted '{}' → loop_var '{}' (join_id={:?})", - promoted_var, inputs.carrier_info.loop_var_name, join_id - ), - ); - break; - } - } - - if let Some(carrier) = inputs - .carrier_info - .carriers - .iter() - .find(|c| c.name == carrier_name) - { - if let Some(join_id) = carrier.join_id { - inputs.env.insert(promoted_var.clone(), join_id); - log_pattern2( - verbose, - "phase229", - format!( - "Dynamically resolved promoted '{}' → carrier '{}' (join_id={:?})", - promoted_var, carrier_name, join_id - ), - ); - break; - } - } - } + for (promoted_var, promoted_carrier_name) in promoted_pairs { + let join_id = inputs + .carrier_info + .find_carrier(&promoted_carrier_name) + .and_then(|c| c.join_id) + .ok_or_else(|| { + format!( + "[phase229] promoted carrier '{}' has no join_id", + promoted_carrier_name + ) + })?; + inputs.env.insert(promoted_var.clone(), join_id); + log_pattern2( + verbose, + "phase229", + format!( + "Resolved promoted '{}' → carrier '{}' (join_id={:?})", + promoted_var, promoted_carrier_name, join_id + ), + ); } // ExprLowerer validation (unchanged) @@ -903,6 +906,127 @@ mod tests { } } + #[test] + #[cfg(feature = "normalized_dev")] + fn phase78_promoted_binding_is_recorded_for_digitpos() { + use super::super::pattern_pipeline::{build_pattern_context, PatternVariant}; + use crate::ast::Span; + use crate::mir::ValueId; + + let mut builder = MirBuilder::new(); + builder.variable_map.insert("i".to_string(), ValueId(1)); + builder.variable_map.insert("len".to_string(), ValueId(2)); + builder.variable_map.insert("s".to_string(), ValueId(3)); + builder.variable_map.insert("digits".to_string(), ValueId(4)); + builder.variable_map.insert("result".to_string(), ValueId(5)); + + let condition = bin(BinaryOperator::Less, var("i"), var("len")); + + let local_ch = ASTNode::Local { + variables: vec!["ch".to_string()], + initial_values: vec![Some(Box::new(ASTNode::MethodCall { + object: Box::new(var("s")), + method: "substring".to_string(), + arguments: vec![], + span: Span::unknown(), + }))], + span: Span::unknown(), + }; + let local_digit_pos = ASTNode::Local { + variables: vec!["digit_pos".to_string()], + initial_values: vec![Some(Box::new(ASTNode::MethodCall { + object: Box::new(var("digits")), + method: "indexOf".to_string(), + arguments: vec![var("ch")], + span: Span::unknown(), + }))], + span: Span::unknown(), + }; + + let break_if = ASTNode::If { + condition: Box::new(bin(BinaryOperator::Less, var("digit_pos"), lit_i(0))), + then_body: vec![ASTNode::Break { span: Span::unknown() }], + else_body: None, + span: Span::unknown(), + }; + + let body = vec![ + local_ch, + local_digit_pos, + break_if, + ASTNode::Assignment { + target: Box::new(var("result")), + value: Box::new(bin( + BinaryOperator::Add, + bin(BinaryOperator::Multiply, var("result"), lit_i(10)), + var("digit_pos"), + )), + span: Span::unknown(), + }, + ASTNode::Assignment { + target: Box::new(var("i")), + value: Box::new(bin(BinaryOperator::Add, var("i"), lit_i(1))), + span: Span::unknown(), + }, + ]; + + let ctx = build_pattern_context(&mut builder, &condition, &body, PatternVariant::Pattern2) + .expect("build_pattern_context"); + let mut inputs = + prepare_pattern2_inputs(&builder, &condition, &body, None, &ctx, false) + .expect("prepare_pattern2_inputs"); + + promote_and_prepare_carriers(&mut builder, &condition, &body, &mut inputs, false, false) + .expect("promote_and_prepare_carriers"); + + assert!( + inputs + .carrier_info + .promoted_loopbodylocals + .contains(&"digit_pos".to_string()), + "digit_pos should be recorded as promoted" + ); + assert_eq!( + inputs.carrier_info.promoted_bindings.len(), + 1, + "promoted_bindings should contain exactly one mapping" + ); + + let (original_bid, promoted_bid) = inputs + .carrier_info + .promoted_bindings + .iter() + .next() + .map(|(k, v)| (*k, *v)) + .unwrap(); + + let promoted_carrier = inputs + .carrier_info + .find_carrier("is_digit_pos") + .expect("promoted carrier exists"); + + assert_eq!( + promoted_carrier.binding_id, + Some(promoted_bid), + "CarrierVar.binding_id should be set for promoted carrier" + ); + + let promoted_join_id = promoted_carrier.join_id.expect("join_id allocated"); + assert_eq!( + inputs.env.binding_id_map.get(&promoted_bid).copied(), + Some(promoted_join_id), + "ConditionEnv should register promoted binding_id -> join_id" + ); + assert_eq!( + inputs.env.get("digit_pos"), + Some(promoted_join_id), + "Name-based alias (digit_pos -> is_digit_pos) should be installed for legacy paths" + ); + + // Ensure the mapping is not degenerate. + assert_ne!(original_bid, promoted_bid); + } + #[test] fn parse_number_like_loop_is_routed_to_pattern2() { let condition = bin(BinaryOperator::Less, var("p"), var("len")); 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 2f6d88a5..fcdcb5ee 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 @@ -278,6 +278,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "sum".to_string(), @@ -285,6 +287,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "M".to_string(), @@ -292,6 +296,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, ], trim_helper: None, 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 010193ce..fc48fb7c 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 @@ -257,7 +257,23 @@ fn prepare_pattern4_context( ), ); - carrier_info.merge_from(&promoted_carrier); + #[cfg(feature = "normalized_dev")] + { + use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner; + let mut promoted_carrier = promoted_carrier; + CarrierBindingAssigner::assign_promoted_binding( + builder, + &mut promoted_carrier, + &promoted_var, + &carrier_name, + ) + .map_err(|e| format!("[phase78/binding_assign] {:?}", e))?; + carrier_info.merge_from(&promoted_carrier); + } + #[cfg(not(feature = "normalized_dev"))] + { + carrier_info.merge_from(&promoted_carrier); + } trace::trace().debug( "pattern4/cond_promoter", diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs b/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs index 81515fdd..de84bbde 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs @@ -403,6 +403,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "count".to_string(), @@ -410,6 +412,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }, ], trim_helper: None, diff --git a/src/mir/join_ir/lowering/carrier_binding_assigner.rs b/src/mir/join_ir/lowering/carrier_binding_assigner.rs new file mode 100644 index 00000000..fb0c3aeb --- /dev/null +++ b/src/mir/join_ir/lowering/carrier_binding_assigner.rs @@ -0,0 +1,335 @@ +//! CarrierBindingAssigner - Assigns BindingIds to promoted carriers +//! +//! Solves the "synthetic carrier has no BindingId" problem by allocating +//! dedicated BindingIds for promoted carriers (e.g., is_digit_pos, is_ch_match). +//! +//! # Problem +//! +//! Phase 77's PromotedBindingRecorder expects both original and promoted names +//! to exist in builder.binding_map, but promoted names are synthetic and don't exist. +//! +//! # Solution (Phase 78) +//! +//! This box allocates BindingIds for synthetic carriers and records the mapping: +//! +//! 1. Get original BindingId from builder.binding_map (Fail-Fast if missing) +//! 2. Allocate new BindingId for promoted carrier +//! 3. Record mapping in carrier_info.promoted_bindings +//! 4. Set binding_id field on the promoted CarrierVar +//! +//! # Integration Point (Important!) +//! +//! **DO NOT call this from DigitPosPromoter or TrimPromoter**. Those run during +//! pattern detection phase and don't have access to MirBuilder. +//! +//! **INSTEAD, call this from Pattern2/4 lowering code** after receiving the +//! promoted CarrierInfo: +//! +//! ```ignore +//! // In Pattern2WithBreak::lower() or similar lowering code that has builder access: +//! +//! // After receiving promoted CarrierInfo from DigitPosPromoter +//! if let Some(promoted_var_name) = /* extract from promotion result */ { +//! // Assign BindingId for promoted carrier +//! CarrierBindingAssigner::assign_promoted_binding( +//! builder, +//! &mut carrier_info, +//! "digit_pos", // original_name (from promotion result) +//! "is_digit_pos" // promoted_carrier_name (from carrier_info) +//! )?; +//! } +//! +//! // Continue with normal lowering... +//! ``` +//! +//! # Example +//! +//! ```ignore +//! // DigitPos promotion: "digit_pos" → "is_digit_pos" +//! CarrierBindingAssigner::assign_promoted_binding( +//! builder, +//! carrier_info, +//! "digit_pos", // original_name +//! "is_digit_pos" // promoted_carrier_name +//! )?; +//! // Result: +//! // - carrier_info.promoted_bindings[BindingId(5)] = BindingId(10) +//! // - carrier_info.carriers["is_digit_pos"].binding_id = Some(BindingId(10)) +//! ``` + +use crate::mir::MirBuilder; +use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; + +#[derive(Debug)] +pub enum CarrierBindingError { + CarrierNotFound(String), +} + +pub struct CarrierBindingAssigner; + +impl CarrierBindingAssigner { + /// Assign BindingIds to promoted carriers and record in promoted_bindings. + /// + /// For DigitPos: original_name="digit_pos", promoted_carrier_name="is_digit_pos" + /// For Trim: original_name="ch", promoted_carrier_name="is_ch_match" + #[cfg(feature = "normalized_dev")] + pub fn assign_promoted_binding( + builder: &mut MirBuilder, + carrier_info: &mut CarrierInfo, + original_name: &str, + promoted_carrier_name: &str, + ) -> Result<(), CarrierBindingError> { + let prev_original = builder.binding_map.get(original_name).copied(); + let prev_promoted = builder.binding_map.get(promoted_carrier_name).copied(); + + // Ensure original BindingId exists (LoopBodyLocal promotion may happen before `local` is lowered). + let original_bid = match prev_original { + Some(bid) => bid, + None => { + let bid = builder.allocate_binding_id(); + builder + .binding_map + .insert(original_name.to_string(), bid); + bid + } + }; + + // Ensure promoted carrier BindingId exists (synthetic names are never in source binding_map). + let promoted_bid = match prev_promoted { + Some(bid) => bid, + None => { + let bid = builder.allocate_binding_id(); + builder + .binding_map + .insert(promoted_carrier_name.to_string(), bid); + bid + } + }; + + carrier_info.record_promoted_binding(original_bid, promoted_bid); + + let Some(carrier) = carrier_info + .carriers + .iter_mut() + .find(|c| c.name == promoted_carrier_name) + else { + // Restore builder.binding_map before returning. + match prev_original { + Some(prev) => { + builder.binding_map.insert(original_name.to_string(), prev); + } + None => { + builder.binding_map.remove(original_name); + } + } + match prev_promoted { + Some(prev) => { + builder + .binding_map + .insert(promoted_carrier_name.to_string(), prev); + } + None => { + builder.binding_map.remove(promoted_carrier_name); + } + } + return Err(CarrierBindingError::CarrierNotFound( + promoted_carrier_name.to_string(), + )); + }; + carrier.binding_id = Some(promoted_bid); + + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() + || std::env::var("JOINIR_TEST_DEBUG").is_ok() + { + eprintln!( + "[phase78/carrier_assigner] '{}' (BindingId({})) → '{}' (BindingId({}))", + original_name, + original_bid.0, + promoted_carrier_name, + promoted_bid.0 + ); + } + + // Restore builder.binding_map to avoid leaking synthetic names into the source map. + match prev_original { + Some(prev) => { + builder.binding_map.insert(original_name.to_string(), prev); + } + None => { + builder.binding_map.remove(original_name); + } + } + match prev_promoted { + Some(prev) => { + builder + .binding_map + .insert(promoted_carrier_name.to_string(), prev); + } + None => { + builder.binding_map.remove(promoted_carrier_name); + } + } + + Ok(()) + } + + /// No-op in non-dev builds + #[cfg(not(feature = "normalized_dev"))] + pub fn assign_promoted_binding( + _builder: &mut MirBuilder, + _carrier_info: &mut CarrierInfo, + _original_name: &str, + _promoted_carrier_name: &str, + ) -> Result<(), CarrierBindingError> { + Ok(()) + } +} + +#[cfg(all(test, feature = "normalized_dev"))] +mod tests { + use super::*; + use crate::mir::ValueId; + + #[test] + fn test_assign_promoted_binding_success() { + use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit}; + + // Create a MirBuilder with binding_map + let mut builder = MirBuilder::new(); + + // Add original binding to builder.binding_map + let original_bid = builder.allocate_binding_id(); + builder.binding_map.insert("digit_pos".to_string(), original_bid); + + // Create CarrierInfo with promoted carrier + let mut carrier_info = CarrierInfo::with_carriers( + "test_loop".to_string(), + ValueId(0), + vec![CarrierVar { + name: "is_digit_pos".to_string(), + host_id: ValueId(0), + join_id: None, + role: CarrierRole::ConditionOnly, + init: CarrierInit::BoolConst(false), + binding_id: None, // Should be set by assigner + }], + ); + + // Assign promoted binding + let result = CarrierBindingAssigner::assign_promoted_binding( + &mut builder, + &mut carrier_info, + "digit_pos", + "is_digit_pos", + ); + + assert!(result.is_ok()); + + // Verify: promoted_bindings contains the mapping + let promoted_bid = carrier_info.resolve_promoted_with_binding(original_bid); + assert!(promoted_bid.is_some()); + + // Verify: carrier.binding_id was set + let carrier = carrier_info.carriers.iter().find(|c| c.name == "is_digit_pos").unwrap(); + assert_eq!(carrier.binding_id, promoted_bid); + } + + #[test] + fn test_assign_promoted_binding_missing_original() { + use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole, CarrierVar}; + + // Create MirBuilder WITHOUT adding "digit_pos" to binding_map + let mut builder = MirBuilder::new(); + + let mut carrier_info = CarrierInfo::with_carriers("test_loop".to_string(), ValueId(0), vec![ + CarrierVar { + name: "is_digit_pos".to_string(), + host_id: ValueId(0), + join_id: None, + role: CarrierRole::ConditionOnly, + init: CarrierInit::BoolConst(false), + binding_id: None, + }, + ]); + + // Should succeed (original/promoted BindingId are allocated on demand). + let result = CarrierBindingAssigner::assign_promoted_binding( + &mut builder, + &mut carrier_info, + "digit_pos", + "is_digit_pos", + ); + + assert!(result.is_ok()); + assert_eq!(carrier_info.promoted_bindings.len(), 1); + + let carrier = carrier_info + .carriers + .iter() + .find(|c| c.name == "is_digit_pos") + .unwrap(); + assert!(carrier.binding_id.is_some()); + } + + #[test] + fn test_assign_promoted_binding_multiple_carriers() { + use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit}; + let mut builder = MirBuilder::new(); + + // Add two original bindings + let digit_pos_bid = builder.allocate_binding_id(); + builder.binding_map.insert("digit_pos".to_string(), digit_pos_bid); + + let ch_bid = builder.allocate_binding_id(); + builder.binding_map.insert("ch".to_string(), ch_bid); + + let mut carrier_info = CarrierInfo::with_carriers( + "test_loop".to_string(), + ValueId(0), + vec![ + CarrierVar { + name: "is_digit_pos".to_string(), + host_id: ValueId(0), + join_id: None, + role: CarrierRole::ConditionOnly, + init: CarrierInit::BoolConst(false), + binding_id: None, + }, + CarrierVar { + name: "is_ch_match".to_string(), + host_id: ValueId(0), + join_id: None, + role: CarrierRole::ConditionOnly, + init: CarrierInit::BoolConst(false), + binding_id: None, + }, + ], + ); + + // Assign both promoted bindings + CarrierBindingAssigner::assign_promoted_binding( + &mut builder, + &mut carrier_info, + "digit_pos", + "is_digit_pos", + ).unwrap(); + + CarrierBindingAssigner::assign_promoted_binding( + &mut builder, + &mut carrier_info, + "ch", + "is_ch_match", + ).unwrap(); + + // Verify both mappings exist + assert!(carrier_info.resolve_promoted_with_binding(digit_pos_bid).is_some()); + assert!(carrier_info.resolve_promoted_with_binding(ch_bid).is_some()); + + // Verify both carriers have binding_id set + let is_digit_pos = carrier_info.carriers.iter().find(|c| c.name == "is_digit_pos").unwrap(); + assert!(is_digit_pos.binding_id.is_some()); + + let is_ch_match = carrier_info.carriers.iter().find(|c| c.name == "is_ch_match").unwrap(); + assert!(is_ch_match.binding_id.is_some()); + } +} diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs index 3e231498..705fd24e 100644 --- a/src/mir/join_ir/lowering/carrier_info.rs +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -25,7 +25,7 @@ use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism #[cfg(feature = "normalized_dev")] -use crate::mir::BindingId; +use crate::mir::BindingId; // Phase 76+78: BindingId for promoted carriers /// Phase 227: CarrierRole - Distinguishes loop state carriers from condition-only carriers /// @@ -120,6 +120,33 @@ pub struct CarrierVar { /// - `FromHost`: Use host_id value (regular carriers) /// - `BoolConst(false)`: Initialize with false (promoted LoopBodyLocal carriers) pub init: CarrierInit, + /// Phase 78: BindingId for this carrier (dev-only) + /// + /// For promoted carriers (e.g., is_digit_pos), this is allocated separately + /// by CarrierBindingAssigner. For source-derived carriers, this comes from + /// builder.binding_map. + /// + /// Enables type-safe lookup: BindingId → ValueId (join_id) in ConditionEnv. + /// + /// # Example + /// + /// ```ignore + /// // Source-derived carrier + /// CarrierVar { + /// name: "sum", + /// binding_id: Some(BindingId(5)), // from builder.binding_map["sum"] + /// .. + /// } + /// + /// // Promoted carrier + /// CarrierVar { + /// name: "is_digit_pos", + /// binding_id: Some(BindingId(10)), // allocated by CarrierBindingAssigner + /// .. + /// } + /// ``` + #[cfg(feature = "normalized_dev")] + pub binding_id: Option, } impl CarrierVar { @@ -134,6 +161,8 @@ impl CarrierVar { join_id: None, role: CarrierRole::LoopState, init: CarrierInit::FromHost, // Phase 228: Default to FromHost + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: No BindingId by default } } @@ -145,6 +174,8 @@ impl CarrierVar { join_id: None, role, init: CarrierInit::FromHost, // Phase 228: Default to FromHost + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: No BindingId by default } } @@ -161,6 +192,8 @@ impl CarrierVar { join_id: None, role, init, + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: No BindingId by default } } } @@ -275,6 +308,8 @@ impl CarrierInfo { join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation role: CarrierRole::LoopState, // Phase 227: Default to LoopState init: CarrierInit::FromHost, // Phase 228: Default to FromHost + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: Set by CarrierBindingAssigner }) .collect(); @@ -338,6 +373,8 @@ impl CarrierInfo { join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation role: CarrierRole::LoopState, // Phase 227: Default to LoopState init: CarrierInit::FromHost, // Phase 228: Default to FromHost + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: Set by CarrierBindingAssigner }); } @@ -613,10 +650,12 @@ impl CarrierInfo { /// we integrate BindingId tracking into the promotion pipeline. #[cfg(feature = "normalized_dev")] pub fn record_promoted_binding(&mut self, original_binding: BindingId, promoted_binding: BindingId) { - eprintln!( - "[digitpos_promoter/phase76] Recorded promoted binding: {} → {}", - original_binding, promoted_binding - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + eprintln!( + "[binding_pilot/promoted_bindings] {} → {}", + original_binding, promoted_binding + ); + } self.promoted_bindings.insert(original_binding, promoted_binding); } } @@ -809,6 +848,8 @@ mod tests { join_id: None, // Phase 177-STRUCT-1 role: CarrierRole::LoopState, // Phase 227: Default to LoopState init: CarrierInit::FromHost, // Phase 228: Default to FromHost + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: No BindingId by default } } diff --git a/src/mir/join_ir/lowering/carrier_update_emitter.rs b/src/mir/join_ir/lowering/carrier_update_emitter.rs index dc713804..f2ea3dd3 100644 --- a/src/mir/join_ir/lowering/carrier_update_emitter.rs +++ b/src/mir/join_ir/lowering/carrier_update_emitter.rs @@ -401,6 +401,8 @@ mod tests { join_id: None, // Phase 177-STRUCT-1 role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, } } diff --git a/src/mir/join_ir/lowering/condition_env.rs b/src/mir/join_ir/lowering/condition_env.rs index 1cc130d5..a7065cae 100644 --- a/src/mir/join_ir/lowering/condition_env.rs +++ b/src/mir/join_ir/lowering/condition_env.rs @@ -172,6 +172,33 @@ impl ConditionEnv { } } + /// Phase 78: Register a carrier binding (BindingId → ValueId) + /// + /// Used by Pattern2/4 lowering to register promoted carriers so they can + /// be looked up via BindingId. + /// + /// # Arguments + /// + /// * `binding_id` - BindingId for the carrier (allocated by CarrierBindingAssigner) + /// * `join_value_id` - JoinIR-local ValueId for this carrier (after header PHI) + /// + /// # Example + /// + /// ```ignore + /// // After allocating carrier join_id + /// if let Some(binding_id) = carrier.binding_id { + /// condition_env.register_carrier_binding(binding_id, carrier.join_id.unwrap()); + /// } + /// ``` + #[cfg(feature = "normalized_dev")] + pub fn register_carrier_binding( + &mut self, + binding_id: BindingId, + join_value_id: ValueId, + ) { + self.binding_id_map.insert(binding_id, join_value_id); + } + /// Phase 75: Resolve variable with BindingId priority (dev-only, pilot integration) /// /// Look up variable by BindingId first, falling back to name-based lookup. diff --git a/src/mir/join_ir/lowering/loop_update_analyzer.rs b/src/mir/join_ir/lowering/loop_update_analyzer.rs index 34429445..8a246e14 100644 --- a/src/mir/join_ir/lowering/loop_update_analyzer.rs +++ b/src/mir/join_ir/lowering/loop_update_analyzer.rs @@ -301,6 +301,8 @@ mod tests { join_id: None, // Phase 177-STRUCT-1 role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }]; let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); @@ -360,6 +362,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }]; let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); @@ -420,6 +424,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }]; let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); @@ -480,6 +486,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + #[cfg(feature = "normalized_dev")] + binding_id: None, }]; let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); @@ -519,6 +527,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, + #[cfg(feature = "normalized_dev")] + binding_id: None, }]; let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); @@ -605,6 +615,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, + #[cfg(feature = "normalized_dev")] + binding_id: None, }, CarrierVar { name: "i".to_string(), @@ -612,6 +624,8 @@ mod tests { join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, + #[cfg(feature = "normalized_dev")] + binding_id: None, }, ]; diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index dd415e0b..e88f14c7 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -20,6 +20,7 @@ //! - `loop_pattern_router.rs`: Phase 33-12 Loop pattern routing (Pattern 1-4 dispatcher) pub(crate) mod bool_expr_lowerer; // Phase 168: Boolean expression lowering (unused - candidate for removal) +pub mod carrier_binding_assigner; // Phase 78: BindingId assignment for promoted carriers (dev-only) pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering pub(crate) mod carrier_update_emitter; // Phase 179: Carrier update instruction emission pub(crate) mod common; // Internal lowering utilities diff --git a/src/mir/join_ir/lowering/scope_manager.rs b/src/mir/join_ir/lowering/scope_manager.rs index e1f25823..0273a636 100644 --- a/src/mir/join_ir/lowering/scope_manager.rs +++ b/src/mir/join_ir/lowering/scope_manager.rs @@ -361,6 +361,8 @@ mod tests { join_id: Some(ValueId(101)), role: CarrierRole::LoopState, init: CarrierInit::FromHost, + #[cfg(feature = "normalized_dev")] + binding_id: None, }], trim_helper: None, promoted_loopbodylocals: vec![], @@ -393,6 +395,8 @@ mod tests { join_id: Some(ValueId(102)), role: CarrierRole::ConditionOnly, init: CarrierInit::BoolConst(false), + #[cfg(feature = "normalized_dev")] + binding_id: None, }], trim_helper: None, promoted_loopbodylocals: vec!["digit_pos".to_string()], diff --git a/src/mir/join_ir/ownership/plan_to_lowering.rs b/src/mir/join_ir/ownership/plan_to_lowering.rs index 58fda7ef..9f15767d 100644 --- a/src/mir/join_ir/ownership/plan_to_lowering.rs +++ b/src/mir/join_ir/ownership/plan_to_lowering.rs @@ -71,6 +71,8 @@ pub fn plan_to_p2_inputs( init: CarrierInit::FromHost, // Default (Phase 228) host_id: crate::mir::ValueId(0), // Placeholder - not used in dev analysis join_id: None, + #[cfg(feature = "normalized_dev")] + binding_id: None, }); } @@ -128,6 +130,8 @@ pub fn plan_to_p2_inputs_with_relay( init: CarrierInit::FromHost, host_id: crate::mir::ValueId(0), join_id: None, + #[cfg(feature = "normalized_dev")] + binding_id: None, }); } @@ -175,6 +179,8 @@ pub fn plan_to_p2_inputs_with_relay( init: CarrierInit::FromHost, host_id: crate::mir::ValueId(0), join_id: None, + #[cfg(feature = "normalized_dev")] + binding_id: None, }); } @@ -237,6 +243,8 @@ pub fn plan_to_p3_inputs( init: CarrierInit::FromHost, // Default (Phase 228) host_id: crate::mir::ValueId(0), // Placeholder - not used in dev analysis join_id: None, + #[cfg(feature = "normalized_dev")] + binding_id: None, }); } 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 13fb91d2..cf934f7c 100644 --- a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs @@ -173,11 +173,13 @@ impl LoopBodyCarrierPromoter { }; } - eprintln!( - "[promoter/pattern5] Phase 171-C: Found {} LoopBodyLocal variables: {:?}", - body_locals.len(), - body_locals - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + eprintln!( + "[promoter/pattern5] Phase 171-C: Found {} LoopBodyLocal variables: {:?}", + body_locals.len(), + body_locals + ); + } // 2. break 条件を取得 if request.break_cond.is_none() { @@ -194,10 +196,12 @@ impl LoopBodyCarrierPromoter { // Phase 79: Use TrimDetector for pure detection logic if let Some(detection) = TrimDetector::detect(break_cond, request.loop_body, var_name) { - eprintln!( - "[promoter/pattern5] Trim pattern detected! var='{}', literals={:?}", - detection.match_var, detection.comparison_literals - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + eprintln!( + "[promoter/pattern5] Trim pattern detected! var='{}', literals={:?}", + detection.match_var, detection.comparison_literals + ); + } // 昇格成功! let trim_info = TrimPatternInfo { @@ -224,21 +228,23 @@ impl LoopBodyCarrierPromoter { // - extract_equality_literals } -/// Phase 78: Log promotion errors with clear messages (for Trim pattern) +/// Phase 78: Log promotion errors with clear messages (for Trim pattern, gated) #[cfg(feature = "normalized_dev")] fn log_trim_promotion_error(error: &BindingRecordError) { - match error { - BindingRecordError::OriginalNotFound(name) => { - eprintln!( - "[trim_lowerer/warning] Original variable '{}' not found in binding_map", - name - ); - } - BindingRecordError::PromotedNotFound(name) => { - eprintln!( - "[trim_lowerer/warning] Promoted variable '{}' not found in binding_map", - name - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + match error { + BindingRecordError::OriginalNotFound(name) => { + eprintln!( + "[binding_pilot/trim] Original variable '{}' not found in binding_map", + name + ); + } + BindingRecordError::PromotedNotFound(name) => { + eprintln!( + "[binding_pilot/trim] Promoted variable '{}' not found in binding_map", + name + ); + } } } } 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 60302031..cc0d5d75 100644 --- a/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs @@ -129,11 +129,13 @@ impl DigitPosPromoter { }; } - eprintln!( - "[digitpos_promoter] Phase 224: Found {} LoopBodyLocal variables: {:?}", - body_locals.len(), - body_locals - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + eprintln!( + "[digitpos_promoter] Phase 224: Found {} LoopBodyLocal variables: {:?}", + body_locals.len(), + body_locals + ); + } // Step 2: Get condition AST let condition = req.break_cond.or(req.continue_cond); @@ -160,10 +162,12 @@ impl DigitPosPromoter { } let detection = detection.unwrap(); - eprintln!( - "[digitpos_promoter] Pattern detected: {} → {} (bool) + {} (int)", - detection.var_name, detection.bool_carrier_name, detection.int_carrier_name - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + eprintln!( + "[digitpos_promoter] Pattern detected: {} → {} (bool) + {} (int)", + detection.var_name, detection.bool_carrier_name, detection.int_carrier_name + ); + } // Step 4: Build CarrierInfo use crate::mir::join_ir::lowering::carrier_info::{ @@ -177,6 +181,8 @@ impl DigitPosPromoter { join_id: None, // Will be allocated later role: CarrierRole::ConditionOnly, // Phase 227: DigitPos is condition-only init: CarrierInit::BoolConst(false), // Phase 228: Initialize with false + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: Set by CarrierBindingAssigner }; // Integer carrier (loop-state, for NumberAccumulation) @@ -186,6 +192,8 @@ impl DigitPosPromoter { join_id: None, // Will be allocated later role: CarrierRole::LoopState, // Phase 247-EX: LoopState for accumulation init: CarrierInit::LoopLocalZero, // Derived in-loop carrier (no host binding) + #[cfg(feature = "normalized_dev")] + binding_id: None, // Phase 78: Set by CarrierBindingAssigner }; // Create CarrierInfo with a dummy loop_var_name (will be ignored during merge) @@ -214,14 +222,16 @@ impl DigitPosPromoter { log_promotion_error(&e); } - eprintln!( - "[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {} → {} (bool) + {} (i64)", - detection.var_name, detection.bool_carrier_name, detection.int_carrier_name - ); - eprintln!( - "[digitpos_promoter] Phase 229: Recorded promoted variable '{}' (carriers: '{}', '{}')", - detection.var_name, detection.bool_carrier_name, detection.int_carrier_name - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + eprintln!( + "[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {} → {} (bool) + {} (i64)", + detection.var_name, detection.bool_carrier_name, detection.int_carrier_name + ); + eprintln!( + "[digitpos_promoter] Phase 229: Recorded promoted variable '{}' (carriers: '{}', '{}')", + detection.var_name, detection.bool_carrier_name, detection.int_carrier_name + ); + } DigitPosPromotionResult::Promoted { carrier_info, @@ -239,21 +249,23 @@ impl DigitPosPromoter { // - is_substring_method_call } -/// Phase 78: Log promotion errors with clear messages +/// Phase 78: Log promotion errors with clear messages (gated) #[cfg(feature = "normalized_dev")] fn log_promotion_error(error: &BindingRecordError) { - match error { - BindingRecordError::OriginalNotFound(name) => { - eprintln!( - "[digitpos_promoter/warning] Original variable '{}' not found in binding_map", - name - ); - } - BindingRecordError::PromotedNotFound(name) => { - eprintln!( - "[digitpos_promoter/warning] Promoted variable '{}' not found in binding_map", - name - ); + if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + match error { + BindingRecordError::OriginalNotFound(name) => { + eprintln!( + "[binding_pilot/digitpos] Original variable '{}' not found in binding_map", + name + ); + } + BindingRecordError::PromotedNotFound(name) => { + eprintln!( + "[binding_pilot/digitpos] Promoted variable '{}' not found in binding_map", + name + ); + } } } }