diff --git a/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs b/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs index 7c64ef80..002ce3b1 100644 --- a/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs +++ b/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs @@ -5,6 +5,7 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; +use crate::mir::builder::control_flow::joinir::patterns::policies::PolicyDecision; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; use crate::mir::join_ir::lowering::common::body_local_slot::{ ReadOnlyBodyLocalSlot, ReadOnlyBodyLocalSlotBox, @@ -19,14 +20,13 @@ use crate::mir::loop_pattern_detection::loop_condition_scope::{CondVarScope, Loo /// /// This is a "route" decision (not a fallback): we choose exactly one of the supported /// strategies and reject otherwise. -pub enum BodyLocalPolicyDecision { - UsePromotion { +pub enum BodyLocalRoute { + Promotion { promoted_carrier: CarrierInfo, promoted_var: String, carrier_name: String, }, - UseReadOnlySlot(ReadOnlyBodyLocalSlot), - Reject { reason: String, vars: Vec }, + ReadOnlySlot(ReadOnlyBodyLocalSlot), } pub fn classify_for_pattern2( @@ -36,7 +36,7 @@ pub fn classify_for_pattern2( break_condition_node: &ASTNode, cond_scope: &LoopConditionScope, body: &[ASTNode], -) -> BodyLocalPolicyDecision { +) -> PolicyDecision { let vars: Vec = cond_scope .vars .iter() @@ -60,19 +60,18 @@ pub fn classify_for_pattern2( carrier_info: promoted_carrier, promoted_var, carrier_name, - } => BodyLocalPolicyDecision::UsePromotion { + } => PolicyDecision::Use(BodyLocalRoute::Promotion { promoted_carrier, promoted_var, carrier_name, - }, + }), ConditionPromotionResult::CannotPromote { reason, .. } => { match extract_body_local_inits_for_conditions(&vars, body) { - Ok(Some(slot)) => BodyLocalPolicyDecision::UseReadOnlySlot(slot), - Ok(None) => BodyLocalPolicyDecision::Reject { reason, vars }, - Err(slot_err) => BodyLocalPolicyDecision::Reject { - reason: format!("{reason}; read-only-slot rejected: {slot_err}"), - vars, - }, + Ok(Some(slot)) => PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)), + Ok(None) => PolicyDecision::Reject(reason), + Err(slot_err) => PolicyDecision::Reject(format!( + "{reason}; read-only-slot rejected: {slot_err}" + )), } } } 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 1a78d9fe..db36ea10 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 @@ -5,7 +5,7 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit}; use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv}; -use super::body_local_policy::{classify_for_pattern2, BodyLocalPolicyDecision}; +use super::body_local_policy::{classify_for_pattern2, BodyLocalRoute}; use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlot; use crate::mir::join_ir::lowering::common::body_local_derived_emitter::BodyLocalDerivedRecipe; use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; @@ -20,6 +20,7 @@ use crate::mir::ValueId; use super::policies::p5b_escape_derived_policy::{ classify_p5b_escape_derived, P5bEscapeDerivedDecision, }; +use super::policies::PolicyDecision; use std::collections::BTreeMap; struct Pattern2DebugLog { @@ -283,6 +284,12 @@ fn promote_and_prepare_carriers( let log = Pattern2DebugLog::new(verbose); 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() { match classify_for_pattern2( @@ -293,11 +300,11 @@ fn promote_and_prepare_carriers( &cond_scope, body, ) { - BodyLocalPolicyDecision::UsePromotion { + PolicyDecision::Use(BodyLocalRoute::Promotion { promoted_carrier, promoted_var, carrier_name, - } => { + }) => { // Phase 133 P1: Check if this is a Trim promotion (A-3 pattern) // Trim promotions are handled by TrimLoopLowerer (apply_trim_and_normalize) // which provides SSOT for env/join_id, so we defer to that path. @@ -399,7 +406,7 @@ fn promote_and_prepare_carriers( ); } } - BodyLocalPolicyDecision::UseReadOnlySlot(slot) => { + PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => { log.log( "body_local_slot", format!( @@ -410,11 +417,12 @@ fn promote_and_prepare_carriers( inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()]; inputs.read_only_body_local_slot = Some(slot); } - BodyLocalPolicyDecision::Reject { reason, vars } => { + PolicyDecision::Reject(reason) => { return Err(error_messages::format_error_pattern2_promotion_failed( - &vars, &reason, + &cond_body_local_vars, &reason, )); } + PolicyDecision::None => {} } } @@ -817,7 +825,7 @@ impl MirBuilder { // Phase 94: Detect P5b escape-derived (`ch` reassignment + escape counter). // Route is explicit; in strict mode, partial matches that require derived support must fail-fast. match classify_p5b_escape_derived(analysis_body, &inputs.loop_var_name) { - P5bEscapeDerivedDecision::UseDerived(recipe) => { + P5bEscapeDerivedDecision::Use(recipe) => { log.log( "phase94", format!( diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/README.md b/src/mir/builder/control_flow/joinir/patterns/policies/README.md index 439ad155..31ae2e82 100644 --- a/src/mir/builder/control_flow/joinir/patterns/policies/README.md +++ b/src/mir/builder/control_flow/joinir/patterns/policies/README.md @@ -85,8 +85,8 @@ - Reject理由は`error_tags::freeze()`でタグ付与 ### Decision型の統一 -- `Decision` enum(None / Use(...) / Reject(String))を統一パターンとして使用 -- 例: `P5bEscapeDerivedDecision`, `TrimDecision`(将来) +- `PolicyDecision`(Use / Reject / None)をSSOTにする +- 例: `P5bEscapeDerivedDecision = PolicyDecision`, `TrimPolicyResult` ## 使用パターン diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs b/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs index a4e9034a..29e88067 100644 --- a/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs @@ -8,6 +8,10 @@ //! - ルーティング決定: 適用可能なLoweringパターンを決定 //! - Recipe生成: Pattern固有の情報(ConditionOnlyRecipe, BodyLocalDerivedRecipe, etc.)を生成 //! +//! ## 決定型のSSOT +//! - `PolicyDecision` に統一(Use / Reject / None) +//! - BodyLocal, Trim, P5b escape などすべてここ経由で route することで Pattern2 側の分岐を簡潔に保つ +//! //! ## 設計原則 //! - **単一判断の原則**: 各policy箱は1つのパターン判断のみ //! - **非破壊的判断**: 入力を変更せず、Decision型で結果を返す @@ -17,11 +21,14 @@ //! policies/ は「認識とルーティング決定(policy)」を分離する受け皿です。 //! Phase 94(P5b derived)から段階的に移設を開始しました。 //! -//! ### 段階的な移行計画 -//! - Phase 1: ディレクトリ準備 ✅ -//! - Phase 2: 既存policy箱の移動(進行中) -//! - Phase 3: インターフェース統一(将来) -//! //! 詳細は [README.md](README.md) を参照してください。 +#[derive(Debug, Clone)] +pub enum PolicyDecision { + Use(T), + Reject(String), + None, +} + pub(in crate::mir::builder) mod p5b_escape_derived_policy; +pub(in crate::mir::builder) mod trim_policy; diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/p5b_escape_derived_policy.rs b/src/mir/builder/control_flow/joinir/patterns/policies/p5b_escape_derived_policy.rs index d7c63453..d83c87c8 100644 --- a/src/mir/builder/control_flow/joinir/patterns/policies/p5b_escape_derived_policy.rs +++ b/src/mir/builder/control_flow/joinir/patterns/policies/p5b_escape_derived_policy.rs @@ -13,13 +13,9 @@ use crate::config::env::joinir_dev; use crate::mir::builder::control_flow::joinir::patterns::escape_pattern_recognizer::EscapeSkipPatternInfo; use crate::mir::join_ir::lowering::common::body_local_derived_emitter::BodyLocalDerivedRecipe; use crate::mir::join_ir::lowering::error_tags; +use super::PolicyDecision; -#[derive(Debug)] -pub enum P5bEscapeDerivedDecision { - None, - UseDerived(BodyLocalDerivedRecipe), - Reject(String), -} +pub type P5bEscapeDerivedDecision = PolicyDecision; /// Detect a P5b derived body-local (`ch`) recipe from a Pattern2 loop body. /// @@ -43,7 +39,7 @@ pub fn classify_p5b_escape_derived( } match build_recipe_from_info(body, &info) { - Ok(Some(recipe)) => P5bEscapeDerivedDecision::UseDerived(recipe), + Ok(Some(recipe)) => P5bEscapeDerivedDecision::Use(recipe), Ok(None) => { // Escape pattern exists but there is no body-local reassignment to cover. P5bEscapeDerivedDecision::None @@ -257,7 +253,7 @@ mod tests { ]; match classify_p5b_escape_derived(&body, "i") { - P5bEscapeDerivedDecision::UseDerived(recipe) => { + P5bEscapeDerivedDecision::Use(recipe) => { assert_eq!(recipe.name, "ch"); assert_eq!(recipe.loop_counter_name, "i"); assert_eq!(recipe.pre_delta, 1); diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/trim_policy.rs b/src/mir/builder/control_flow/joinir/patterns/policies/trim_policy.rs new file mode 100644 index 00000000..849dff51 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/policies/trim_policy.rs @@ -0,0 +1,51 @@ +//! Trim pattern policy box (判定専用) +//! +//! 目的: Trim 形状かどうかを判定し、ConditionScope を返すだけに責務を絞る。 +//! 生成(lowering)は従来通り TrimLoopLowerer 側が担当する。 + +use crate::ast::ASTNode; +use crate::mir::builder::control_flow::joinir::patterns::trim_loop_lowering::TrimLoopLowerer; +use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::loop_pattern_detection::loop_condition_scope::{ + CondVarInfo, CondVarScope, LoopConditionScope, LoopConditionScopeBox, +}; + +use super::PolicyDecision; + +/// 判定結果(生成に必要な最小情報だけ運ぶ) +#[derive(Debug, Clone)] +pub struct TrimPolicyResult { + pub cond_scope: LoopConditionScope, + pub condition_body_locals: Vec, +} + +pub fn classify_trim_like_loop( + scope: &LoopScopeShape, + loop_cond: &ASTNode, + break_cond: &ASTNode, + loop_var_name: &str, +) -> PolicyDecision { + let cond_scope = + LoopConditionScopeBox::analyze(loop_var_name, &[loop_cond, break_cond], Some(scope)); + + if !cond_scope.has_loop_body_local() { + return PolicyDecision::None; + } + + let condition_body_locals: Vec<_> = cond_scope + .vars + .iter() + .filter(|v| v.scope == CondVarScope::LoopBodyLocal) + .filter(|v| TrimLoopLowerer::is_var_used_in_condition(&v.name, break_cond)) + .cloned() + .collect(); + + if condition_body_locals.is_empty() { + return PolicyDecision::None; + } + + PolicyDecision::Use(TrimPolicyResult { + cond_scope, + condition_body_locals, + }) +} diff --git a/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs b/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs index 40887aba..4cd7fd09 100644 --- a/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs +++ b/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs @@ -44,8 +44,9 @@ use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::loop_pattern_detection::loop_body_carrier_promoter::{ LoopBodyCarrierPromoter, PromotionRequest, PromotionResult, }; -use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; use crate::mir::ValueId; +use super::policies::trim_policy::{classify_trim_like_loop, TrimPolicyResult}; +use super::policies::PolicyDecision; /// Trim pattern lowering orchestrator /// @@ -96,7 +97,10 @@ impl TrimLoopLowerer { /// # Returns /// /// `true` if `var_name` appears anywhere in `cond_node`, `false` otherwise - fn is_var_used_in_condition(var_name: &str, cond_node: &ASTNode) -> bool { + pub(in crate::mir::builder::control_flow::joinir::patterns) fn is_var_used_in_condition( + var_name: &str, + cond_node: &ASTNode, + ) -> bool { match cond_node { ASTNode::Variable { name, .. } => name == var_name, ASTNode::BinaryOp { left, right, .. } => { @@ -179,63 +183,14 @@ impl TrimLoopLowerer { let trace = crate::mir::builder::control_flow::joinir::trace::trace(); let verbose = crate::config::env::joinir_dev_enabled() || trace.is_joinir_enabled(); - // Phase 180-2: Skeleton implementation - // TODO: Phase 180-3 will implement full logic from Pattern2 - - // Step 1: Check if condition references LoopBodyLocal variables - let cond_scope = - LoopConditionScopeBox::analyze(loop_var_name, &[loop_cond, break_cond], Some(scope)); - - trace.emit_if( - "trim", - "scope", - &format!( - "Analyzing condition scope: {} variables", - cond_scope.vars.len() - ), - verbose, - ); - - if !cond_scope.has_loop_body_local() { - // Not a Trim pattern - normal loop - trace.emit_if( - "trim", - "scope", - "No LoopBodyLocal detected, skipping Trim lowering", - verbose, - ); - return Ok(None); - } - - trace.emit_if( - "trim", - "scope", - "LoopBodyLocal detected in condition scope", - verbose, - ); - - // Phase 183-2: Filter to only condition LoopBodyLocal (skip body-only) - use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope; - let condition_body_locals: Vec<_> = cond_scope - .vars - .iter() - .filter(|v| v.scope == CondVarScope::LoopBodyLocal) - .filter(|v| { - // Check if variable is actually used in break condition - Self::is_var_used_in_condition(&v.name, break_cond) - }) - .collect(); - - if condition_body_locals.is_empty() { - // All LoopBodyLocal are body-only (not in conditions) → Not a Trim pattern - trace.emit_if( - "trim", - "phase183", - "All LoopBodyLocal are body-only (not in conditions), skipping Trim lowering", - verbose, - ); - return Ok(None); - } + let TrimPolicyResult { + cond_scope, + condition_body_locals, + } = match classify_trim_like_loop(scope, loop_cond, break_cond, loop_var_name) { + PolicyDecision::Use(res) => res, + PolicyDecision::None => return Ok(None), + PolicyDecision::Reject(reason) => return Err(reason), + }; trace.emit_if( "trim",