//! JoinIR routing logic for loop lowering
use super::trace;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
/// Pattern 選択の SSOT 入口
///
/// 既存の分散した選択ロジックをここに集約する。
/// 将来的には Canonicalizer decision に委譲する。
///
/// Phase 137-6-S1: 現時点では既存の router ロジック(LoopFeatures ベース)を使用
/// Phase 137-6-S2: dev-only で canonicalizer decision を提案として受け取る
pub(in crate::mir::builder) fn choose_pattern_kind(
condition: &ASTNode,
body: &[ASTNode],
) -> crate::mir::loop_pattern_detection::LoopPatternKind {
use crate::mir::builder::control_flow::joinir::patterns::ast_feature_extractor as ast_features;
use crate::mir::builder::control_flow::joinir::patterns::policies::balanced_depth_scan_policy_box::BalancedDepthScanPolicyBox;
use crate::mir::builder::control_flow::joinir::patterns::policies::PolicyDecision;
use crate::mir::loop_pattern_detection;
// Phase 107: Route balanced depth-scan (return-in-loop) to Pattern2 via policy.
//
// This keeps Pattern routing structural: no by-name dispatch, no silent fallback.
match BalancedDepthScanPolicyBox::decide(condition, body) {
PolicyDecision::Use(_) => {
return loop_pattern_detection::LoopPatternKind::Pattern2Break;
}
PolicyDecision::Reject(_reason) => {
// In strict mode, treat "close-but-unsupported" as a fail-fast
// Pattern2 route so the policy can surface the precise contract violation.
if crate::config::env::joinir_dev::strict_enabled() {
return loop_pattern_detection::LoopPatternKind::Pattern2Break;
}
}
PolicyDecision::None => {}
}
// Phase 193: Use AST Feature Extractor Box for break/continue detection
let has_continue = ast_features::detect_continue_in_body(body);
let has_break = ast_features::detect_break_in_body(body);
let has_return = ast_features::detect_return_in_body(body);
// Phase 188.3: Pattern6 selection for 1-level nested loops
// SSOT: All Pattern6 detection happens here (no dev dependency)
//
// Strategy: Cheap check → StepTree → Full AST validation
// Only select Pattern6 if lowering is guaranteed to work
// Step 1: Cheap check - does body contain any Loop node?
let has_inner_loop = body.iter().any(|stmt| matches!(stmt, ASTNode::Loop { .. }));
if has_inner_loop {
// Step 2: Build StepTree to get nesting depth (cost: acceptable for nested loops only)
use crate::ast::Span;
use crate::mir::control_tree::StepTreeBuilderBox;
let loop_ast = ASTNode::Loop {
condition: Box::new(condition.clone()),
body: body.to_vec(),
span: Span::unknown(),
};
let tree = StepTreeBuilderBox::build_from_ast(&loop_ast);
// Step 3: Check if exactly 1-level nesting (depth == 2)
if tree.features.max_loop_depth == 2 {
// Step 4: Full AST validation (Pattern1-compatible requirements)
if is_pattern6_lowerable(&tree, body) {
// Pattern6 selected - lowering MUST succeed
trace::trace().dev(
"choose_pattern_kind",
"[routing] Pattern6 selected: 1-level nested loop validated"
);
return loop_pattern_detection::LoopPatternKind::Pattern6NestedLoopMinimal;
}
// Validation failed - not Pattern6, fall through to router_choice
}
}
// Phase 110: StepTree parity check (structure-only SSOT).
//
// This is dev-only; strict mode turns mismatch into a fail-fast.
if crate::config::env::joinir_dev_enabled() {
use crate::ast::Span;
use crate::mir::control_tree::StepTreeBuilderBox;
let loop_ast = ASTNode::Loop {
condition: Box::new(condition.clone()),
body: body.to_vec(),
span: Span::unknown(),
};
let tree = StepTreeBuilderBox::build_from_ast(&loop_ast);
if tree.features.has_break != has_break
|| tree.features.has_continue != has_continue
|| tree.features.has_return != has_return
{
let msg = format!(
"[choose_pattern_kind/STEPTREE_PARITY] step_tree(break={}, cont={}, ret={}) != extractor(break={}, cont={}, ret={})",
tree.features.has_break,
tree.features.has_continue,
tree.features.has_return,
has_break,
has_continue,
has_return
);
if crate::config::env::joinir_dev::strict_enabled() {
panic!("{}", msg);
} else {
trace::trace().dev("choose_pattern_kind/step_tree_parity", &msg);
}
}
}
// Phase 193: Extract features using modularized extractor
let features = ast_features::extract_features(condition, body, has_continue, has_break);
// Phase 192: Classify pattern based on features (既存の router 結果)
let router_choice = loop_pattern_detection::classify(&features);
// Phase 137-6-S2: dev-only で Canonicalizer の提案を取得
if crate::config::env::joinir_dev_enabled() {
use crate::ast::Span;
use crate::mir::loop_canonicalizer::canonicalize_loop_expr;
let loop_ast = ASTNode::Loop {
condition: Box::new(condition.clone()),
body: body.to_vec(),
span: Span::unknown(),
};
if let Ok((_skeleton, decision)) = canonicalize_loop_expr(&loop_ast) {
if let Some(canonical_choice) = decision.chosen {
// parity check
if canonical_choice != router_choice {
let msg = format!(
"[choose_pattern_kind/PARITY] router={:?}, canonicalizer={:?}",
router_choice, canonical_choice
);
if crate::config::env::joinir_dev::strict_enabled() {
// strict mode: 不一致は Fail-Fast
panic!("{}", msg);
} else {
// debug mode: ログのみ
trace::trace().dev("choose_pattern_kind/parity", &msg);
}
} else {
// Patterns match - success!
trace::trace().dev(
"choose_pattern_kind/parity",
&format!(
"[choose_pattern_kind/PARITY] OK: canonical and actual agree on {:?}",
canonical_choice
),
);
}
// TODO (Phase 137-6-S3): ここで canonical_choice を返す
// 現時点では router_choice を維持(既定挙動不変)
//
// 有効化条件(将来実装):
// 1. joinir_dev_enabled() && 新フラグ(例: canonicalizer_preferred())
// 2. または joinir_dev_enabled() をそのまま使用
//
// 注意: 有効化時は全 Pattern の parity が green であること
//
// 有効化後のコード例:
// ```rust
// if crate::config::env::canonicalizer_preferred() {
// return canonical_choice;
// }
// ```
}
}
}
router_choice
}
/// Phase 188.3: Validate nested loop meets ALL Pattern6 requirements
///
/// Returns true ONLY if Pattern6 lowering is guaranteed to succeed.
/// False → fall through to other patterns (NOT an error)
fn is_pattern6_lowerable(tree: &crate::mir::control_tree::StepTree, body: &[crate::ast::ASTNode]) -> bool {
use crate::ast::ASTNode;
// Requirement 1: Outer loop has no break (Pattern1 requirement)
if tree.features.has_break {
return false;
}
// Requirement 2: Outer loop has no continue (Pattern1 requirement)
if tree.features.has_continue {
return false;
}
// Requirement 3: Extract inner loop(s) - must have exactly 1
let mut inner_loop: Option<&ASTNode> = None;
for stmt in body.iter() {
if matches!(stmt, ASTNode::Loop { .. }) {
if inner_loop.is_some() {
// Multiple inner loops - not supported
return false;
}
inner_loop = Some(stmt);
}
}
let inner_loop = match inner_loop {
Some(l) => l,
None => return false, // No inner loop found (shouldn't happen, but defensive)
};
// Requirement 4: Inner loop has no break (Pattern1 requirement)
use crate::mir::control_tree::StepTreeBuilderBox;
let inner_tree = StepTreeBuilderBox::build_from_ast(inner_loop);
if inner_tree.features.has_break {
return false;
}
// Requirement 5: Inner loop has no continue (Pattern1 requirement)
if inner_tree.features.has_continue {
return false;
}
// All requirements met - Pattern6 lowering will succeed
true
}
impl MirBuilder {
/// Phase 49: Try JoinIR Frontend for mainline integration
///
/// Returns `Ok(Some(value))` if the loop is successfully lowered via JoinIR,
/// `Ok(None)` if no JoinIR pattern matched (unsupported loop structure).
/// Phase 187-2: Legacy LoopBuilder removed - all loops must use JoinIR.
///
/// # Phase 49-4: Multi-target support
///
/// Targets are enabled via separate dev flags:
/// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0
/// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2
///
/// Note: Arity in function names does NOT include implicit `me` receiver.
/// - Instance method `print_tokens()` → `/0` (no explicit params)
/// - Static method `filter(arr, pred)` → `/2` (two params)
pub(in crate::mir::builder) fn try_cf_loop_joinir(
&mut self,
condition: &ASTNode,
body: &[ASTNode],
) -> Result