diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index 1ff0aa9f..8fc40e75 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -60,6 +60,7 @@ pub(in crate::mir::builder) mod escape_pattern_recognizer; // Phase 91 P5b pub(in crate::mir::builder) mod common_init; pub(in crate::mir::builder) mod loop_true_counter_extractor; // Phase 104: loop(true) counter extraction for Pattern2 pub(in crate::mir::builder) mod read_digits_break_condition_box; // Phase 104: break cond normalization for read_digits(loop(true)) +pub(in crate::mir::builder) mod pattern2; // Phase 263 P0.2: Pattern2 module (api/ SSOT entry point) pub(in crate::mir::builder) mod pattern2_break_condition_policy_router; // Phase 105: policy router box for Pattern2 break condition pub(in crate::mir::builder) mod pattern2_policy_router; // Phase 108: unified Pattern2 policy router (balanced/read_digits/default) pub(in crate::mir::builder) mod pattern2_inputs_facts_box; // Phase 105: Pattern2 input facts (analysis only) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2/api/mod.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2/api/mod.rs new file mode 100644 index 00000000..f89236b8 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2/api/mod.rs @@ -0,0 +1,23 @@ +//! Phase 263 P0.2: Pattern2 promotion API (entry point SSOT) +//! +//! This module is the single entry point for Pattern2 promotion logic. +//! All callers should use this module's exports instead of accessing internals. +//! +//! # Usage +//! +//! ```ignore +//! use super::pattern2::api::{try_promote, PromoteDecision}; +//! +//! match try_promote(builder, condition, body, inputs, debug, verbose)? { +//! PromoteDecision::Promoted(result) => { /* ... */ } +//! PromoteDecision::NotApplicable => { /* fallback to next path */ } +//! PromoteDecision::Freeze(reason) => { /* fail-fast */ } +//! } +//! ``` + +mod promote_decision; +mod promote_runner; + +// Re-export the SSOT types and functions +pub(in crate::mir::builder) use promote_decision::{PromoteDecision, PromoteStepResult}; +pub(in crate::mir::builder) use promote_runner::try_promote; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2/api/promote_decision.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2/api/promote_decision.rs new file mode 100644 index 00000000..b92e5948 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2/api/promote_decision.rs @@ -0,0 +1,26 @@ +//! Phase 263 P0.2: Promotion decision types (SSOT) +//! +//! PromoteDecision enum eliminates Option<_> wrapping ambiguity by making +//! the decision explicit. All Pattern2 promotion logic flows through this type. + +use super::super::super::pattern2_inputs_facts_box::Pattern2Inputs; + +pub(crate) struct PromoteStepResult { + pub inputs: Pattern2Inputs, +} + +/// Phase 263 P0.1: Promotion decision for Pattern2 LoopBodyLocal handling +/// +/// Eliminates Option<_> wrapping ambiguity by making decision explicit. +pub(crate) enum PromoteDecision { + /// Promotion succeeded - Pattern2 can proceed + Promoted(PromoteStepResult), + + /// Pattern2 not applicable (e.g., reassigned LoopBodyLocal, no promotable pattern) + /// → Router should try next path (legacy binding, etc.) + NotApplicable, + + /// Pattern2 should handle this but implementation is missing + /// → Fail-Fast with error message + Freeze(String), +} diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2/api/promote_runner.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2/api/promote_runner.rs new file mode 100644 index 00000000..aa9b44a9 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2/api/promote_runner.rs @@ -0,0 +1,180 @@ +//! Phase 263 P0.2: Pattern2 promotion runner (SSOT entry point) +//! +//! Single entry point for all Pattern2 promotion logic. +//! All callers should use `try_promote()` instead of accessing internals directly. + +use crate::ast::ASTNode; +use crate::mir::builder::MirBuilder; + +use super::super::super::body_local_policy::{classify_for_pattern2, BodyLocalRoute}; +use super::super::super::pattern2_inputs_facts_box::Pattern2Inputs; +use super::super::super::policies::PolicyDecision; + +use super::promote_decision::{PromoteDecision, PromoteStepResult}; + +/// Phase 263 P0.2: Try to promote LoopBodyLocal variables for Pattern2 +/// +/// This is the single entry point for Pattern2 promotion logic. +/// Returns PromoteDecision to indicate success, applicability, or freeze. +pub(in crate::mir::builder) fn try_promote( + builder: &mut MirBuilder, + condition: &ASTNode, + body: &[ASTNode], + inputs: Pattern2Inputs, + debug: bool, + verbose: bool, +) -> Result { + let mut inputs = inputs; + use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer; + use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; + + let cond_scope = LoopConditionScopeBox::analyze( + &inputs.loop_var_name, + &vec![condition, &inputs.break_condition_node], + Some(&inputs.scope), + ); + + let mut promoted_pairs: Vec<(String, String)> = Vec::new(); + let cond_body_local_vars: Vec = cond_scope + .vars + .iter() + .filter(|v| matches!(v.scope, crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope::LoopBodyLocal)) + .map(|v| v.name.clone()) + .collect(); + + if cond_scope.has_loop_body_local() { + // Policy-controlled: some families must not run promotion/slot heuristics here. + // Example: balanced depth-scan uses derived vars and doesn't have a break-guard node. + if matches!( + inputs.body_local_handling, + crate::mir::builder::control_flow::joinir::patterns::pattern2_inputs_facts_box::BodyLocalHandlingPolicy::SkipPromotion + ) { + // no-op: lowerers will populate LoopBodyLocalEnv via init/derived emission. + } else if !inputs.is_loop_true_read_digits { + match classify_for_pattern2( + builder, + &inputs.loop_var_name, + &inputs.scope, + &inputs.break_condition_node, + &cond_scope, + body, + ) { + PolicyDecision::Use(BodyLocalRoute::Promotion { + promoted_carrier, + promoted_var, + carrier_name, + }) => { + let is_trim_promotion = promoted_carrier.trim_helper().is_some(); + if !is_trim_promotion { + 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); + } + + inputs + .carrier_info + .promoted_loopbodylocals + .push(promoted_var.clone()); + + if !is_trim_promotion { + inputs.break_condition_node = DigitPosConditionNormalizer::normalize( + &inputs.break_condition_node, + &promoted_var, + &carrier_name, + ); + } + } + PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => { + inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()]; + inputs.read_only_body_local_slot = Some(slot); + } + PolicyDecision::Reject(reason) => { + // Phase 263 P0.1: Reject を PromoteDecision で二分化(型安全) + if reason.contains("not_readonly") + || reason.contains("No promotable pattern detected") + { + // 対象外: Pattern2 で処理できない形 → NotApplicable で後続経路へ + #[cfg(debug_assertions)] + { + eprintln!( + "[pattern2/api/promote] Pattern2 対象外(LoopBodyLocal {:?}): {}. 後続経路へfallback.", + cond_body_local_vars, reason + ); + } + return Ok(PromoteDecision::NotApplicable); + } else { + // 対象だが未対応(freeze級): 実装バグ or 将来実装予定 → Freeze で Fail-Fast + return Ok(PromoteDecision::Freeze(format!( + "[pattern2/api/promote] Pattern2 未対応エラー(LoopBodyLocal {:?}): {}", + cond_body_local_vars, reason + ))); + } + } + PolicyDecision::None => {} + } + } + } + + // Allocate join_ids for carriers and register bindings. + 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 { + use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + match carrier.role { + CarrierRole::ConditionOnly => inputs.env.register_condition_binding(binding_id, carrier_join_id), + CarrierRole::LoopState => inputs.env.register_carrier_binding(binding_id, carrier_join_id), + } + } + } + + 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, join_id); + } + + // ExprLowerer validation (best-effort; unchanged behavior) + { + use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError}; + use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; + + let scope_manager = Pattern2ScopeManager { + condition_env: &inputs.env, + loop_body_local_env: Some(&inputs.body_local_env), + captured_env: Some(&inputs.captured_env), + carrier_info: &inputs.carrier_info, + }; + + match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder) + .with_debug(debug) + .lower(&inputs.break_condition_node) + { + Ok(_) => {} + Err(ExprLoweringError::UnsupportedNode(_)) => {} + Err(_) => {} + } + } + + Ok(PromoteDecision::Promoted(PromoteStepResult { inputs })) +} diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2/mod.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2/mod.rs new file mode 100644 index 00000000..9a95cc4b --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2/mod.rs @@ -0,0 +1,6 @@ +//! Phase 263 P0.2: Pattern2 module structure +//! +//! This module organizes Pattern2 logic with a clear SSOT structure: +//! - `api/` - Public entry point for promotion logic (SSOT) + +pub(in crate::mir::builder) mod api; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_lowering_orchestrator.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_lowering_orchestrator.rs index 5003bca7..bd4d24da 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_lowering_orchestrator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_lowering_orchestrator.rs @@ -62,9 +62,9 @@ impl Pattern2LoweringOrchestrator { let facts = GatherFactsStepBox::gather(builder, condition, body, fn_body, &ctx, verbose)?; let inputs = ApplyPolicyStepBox::apply(condition, body, facts)?; - // Phase 263 P0.1: PromoteDecision で分岐を1箇所に固定(型安全) - use super::pattern2_steps::promote_step_box::PromoteDecision; - let mut inputs = match PromoteStepBox::run(builder, condition, body, inputs, debug, verbose)? { + // Phase 263 P0.2: pattern2::api 経由で try_promote を呼び出し(入口SSOT) + use super::pattern2::api::{try_promote, PromoteDecision}; + let mut inputs = match try_promote(builder, condition, body, inputs, debug, verbose)? { PromoteDecision::Promoted(result) => { result.inputs } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/promote_step_box.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/promote_step_box.rs index 26c7c9fa..569ad0ce 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/promote_step_box.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/promote_step_box.rs @@ -1,40 +1,24 @@ //! PromoteStepBox (Phase 106) //! +//! Phase 263 P0.2: This is now a thin wrapper over pattern2::api::try_promote. +//! All promotion logic has been moved to pattern2::api for SSOT. +//! //! Responsibility: -//! - promotion / read-only slot routing for body-local vars in conditions -//! - carrier preparation for JoinIR emission +//! - Backward compatibility wrapper for existing callers +//! - Delegates to pattern2::api::try_promote for actual implementation use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; -use crate::mir::loop_pattern_detection::error_messages; -use super::super::body_local_policy::{classify_for_pattern2, BodyLocalRoute}; +use super::super::pattern2::api::{try_promote, PromoteDecision, PromoteStepResult}; use super::super::pattern2_inputs_facts_box::Pattern2Inputs; -use super::super::policies::PolicyDecision; - -pub(crate) struct PromoteStepResult { - pub inputs: Pattern2Inputs, -} - -/// Phase 263 P0.1: Promotion decision for Pattern2 LoopBodyLocal handling -/// -/// Eliminates Option<_> wrapping ambiguity by making decision explicit. -pub(crate) enum PromoteDecision { - /// Promotion succeeded - Pattern2 can proceed - Promoted(PromoteStepResult), - - /// Pattern2 not applicable (e.g., reassigned LoopBodyLocal, no promotable pattern) - /// → Router should try next path (legacy binding, etc.) - NotApplicable, - - /// Pattern2 should handle this but implementation is missing - /// → Fail-Fast with error message - Freeze(String), -} pub(crate) struct PromoteStepBox; impl PromoteStepBox { + /// Phase 263 P0.2: Thin wrapper over pattern2::api::try_promote + /// + /// This method is deprecated. Use pattern2::api::try_promote directly. pub(crate) fn run( builder: &mut MirBuilder, condition: &ASTNode, @@ -43,170 +27,6 @@ impl PromoteStepBox { debug: bool, verbose: bool, ) -> Result { - Self::promote_and_prepare_carriers(builder, condition, body, inputs, debug, verbose) + try_promote(builder, condition, body, inputs, debug, verbose) } - - pub(in crate::mir::builder) fn promote_and_prepare_carriers( - builder: &mut MirBuilder, - condition: &ASTNode, - body: &[ASTNode], - inputs: Pattern2Inputs, - debug: bool, - verbose: bool, - ) -> Result { - let mut inputs = inputs; - use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer; - use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; - - let cond_scope = LoopConditionScopeBox::analyze( - &inputs.loop_var_name, - &vec![condition, &inputs.break_condition_node], - Some(&inputs.scope), - ); - - let mut promoted_pairs: Vec<(String, String)> = Vec::new(); - let cond_body_local_vars: Vec = cond_scope - .vars - .iter() - .filter(|v| matches!(v.scope, crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope::LoopBodyLocal)) - .map(|v| v.name.clone()) - .collect(); - - if cond_scope.has_loop_body_local() { - // Policy-controlled: some families must not run promotion/slot heuristics here. - // Example: balanced depth-scan uses derived vars and doesn't have a break-guard node. - if matches!( - inputs.body_local_handling, - crate::mir::builder::control_flow::joinir::patterns::pattern2_inputs_facts_box::BodyLocalHandlingPolicy::SkipPromotion - ) { - // no-op: lowerers will populate LoopBodyLocalEnv via init/derived emission. - } else if !inputs.is_loop_true_read_digits { - match classify_for_pattern2( - builder, - &inputs.loop_var_name, - &inputs.scope, - &inputs.break_condition_node, - &cond_scope, - body, - ) { - PolicyDecision::Use(BodyLocalRoute::Promotion { - promoted_carrier, - promoted_var, - carrier_name, - }) => { - let is_trim_promotion = promoted_carrier.trim_helper().is_some(); - if !is_trim_promotion { - 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); - } - - inputs - .carrier_info - .promoted_loopbodylocals - .push(promoted_var.clone()); - - if !is_trim_promotion { - inputs.break_condition_node = DigitPosConditionNormalizer::normalize( - &inputs.break_condition_node, - &promoted_var, - &carrier_name, - ); - } - } - PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => { - inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()]; - inputs.read_only_body_local_slot = Some(slot); - } - PolicyDecision::Reject(reason) => { - // Phase 263 P0.1: Reject を PromoteDecision で二分化(型安全) - if reason.contains("not_readonly") - || reason.contains("No promotable pattern detected") - { - // 対象外: Pattern2 で処理できない形 → NotApplicable で後続経路へ - #[cfg(debug_assertions)] - { - eprintln!( - "[pattern2/promote_step] Pattern2 対象外(LoopBodyLocal {:?}): {}. 後続経路へfallback.", - cond_body_local_vars, reason - ); - } - return Ok(PromoteDecision::NotApplicable); - } else { - // 対象だが未対応(freeze級): 実装バグ or 将来実装予定 → Freeze で Fail-Fast - return Ok(PromoteDecision::Freeze(format!( - "[pattern2/promote_step] Pattern2 未対応エラー(LoopBodyLocal {:?}): {}", - cond_body_local_vars, reason - ))); - } - } - PolicyDecision::None => {} - } - } - } - - // Allocate join_ids for carriers and register bindings. - 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 { - use crate::mir::join_ir::lowering::carrier_info::CarrierRole; - match carrier.role { - CarrierRole::ConditionOnly => inputs.env.register_condition_binding(binding_id, carrier_join_id), - CarrierRole::LoopState => inputs.env.register_carrier_binding(binding_id, carrier_join_id), - } - } - } - - 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, join_id); - } - - // ExprLowerer validation (best-effort; unchanged behavior) - { - use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError}; - use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; - - let scope_manager = Pattern2ScopeManager { - condition_env: &inputs.env, - loop_body_local_env: Some(&inputs.body_local_env), - captured_env: Some(&inputs.captured_env), - carrier_info: &inputs.carrier_info, - }; - - match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder) - .with_debug(debug) - .lower(&inputs.break_condition_node) - { - Ok(_) => {} - Err(ExprLoweringError::UnsupportedNode(_)) => {} - Err(_) => {} - } - } - - Ok(PromoteDecision::Promoted(PromoteStepResult { inputs })) - } - }