refactor(joinir): unify policy decisions and trim routing
This commit is contained in:
@ -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<String> },
|
||||
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<BodyLocalRoute> {
|
||||
let vars: Vec<String> = 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}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String> = 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!(
|
||||
|
||||
@ -85,8 +85,8 @@
|
||||
- Reject理由は`error_tags::freeze()`でタグ付与
|
||||
|
||||
### Decision型の統一
|
||||
- `Decision` enum(None / Use(...) / Reject(String))を統一パターンとして使用
|
||||
- 例: `P5bEscapeDerivedDecision`, `TrimDecision`(将来)
|
||||
- `PolicyDecision<T>`(Use / Reject / None)をSSOTにする
|
||||
- 例: `P5bEscapeDerivedDecision = PolicyDecision<BodyLocalDerivedRecipe>`, `TrimPolicyResult`
|
||||
|
||||
## 使用パターン
|
||||
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
//! - ルーティング決定: 適用可能なLoweringパターンを決定
|
||||
//! - Recipe生成: Pattern固有の情報(ConditionOnlyRecipe, BodyLocalDerivedRecipe, etc.)を生成
|
||||
//!
|
||||
//! ## 決定型のSSOT
|
||||
//! - `PolicyDecision<T>` に統一(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<T> {
|
||||
Use(T),
|
||||
Reject(String),
|
||||
None,
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) mod p5b_escape_derived_policy;
|
||||
pub(in crate::mir::builder) mod trim_policy;
|
||||
|
||||
@ -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<BodyLocalDerivedRecipe>;
|
||||
|
||||
/// 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);
|
||||
|
||||
@ -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<CondVarInfo>,
|
||||
}
|
||||
|
||||
pub fn classify_trim_like_loop(
|
||||
scope: &LoopScopeShape,
|
||||
loop_cond: &ASTNode,
|
||||
break_cond: &ASTNode,
|
||||
loop_var_name: &str,
|
||||
) -> PolicyDecision<TrimPolicyResult> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
@ -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",
|
||||
|
||||
Reference in New Issue
Block a user