feat(joinir): Phase 240-EX - Pattern2 header condition ExprLowerer integration
Implementation: - Add make_pattern2_scope_manager() helper for DRY - Header conditions use ExprLowerer for supported patterns - Legacy fallback for unsupported patterns - Fail-Fast on supported patterns that fail Tests: - 4 new tests (all pass) - test_expr_lowerer_supports_simple_header_condition_i_less_literal - test_expr_lowerer_supports_header_condition_var_less_var - test_expr_lowerer_header_condition_generates_expected_instructions - test_pattern2_header_condition_via_exprlowerer Also: Archive old phase documentation (34k lines removed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -268,7 +268,8 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
/// Check if an AST node is supported in condition context
|
||||
///
|
||||
/// Phase 231: Conservative whitelist. We only support patterns we know work.
|
||||
fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
/// Phase 240-EX: Made public to allow callers to check before attempting lowering.
|
||||
pub fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
match ast {
|
||||
// Literals: Integer, Bool
|
||||
ASTNode::Literal { .. } => true,
|
||||
@ -727,4 +728,69 @@ mod tests {
|
||||
"MethodCall should fail-fast with UnsupportedNode"
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 240-EX: Header condition patterns
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_supports_simple_header_condition_i_less_literal() {
|
||||
// header pattern: i < 10
|
||||
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"i < 10 should be supported for Pattern2 header condition"
|
||||
);
|
||||
|
||||
// lower and verify success
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
|
||||
let result = lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i < 10 should lower successfully");
|
||||
|
||||
// Compare instruction should be present
|
||||
let instructions = lowerer.take_last_instructions();
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_supports_header_condition_var_less_var() {
|
||||
// header pattern: i < n (variable vs variable)
|
||||
let ast = bin(BinaryOperator::Less, var("i"), var("j"));
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"i < n should be supported for Pattern2 header condition"
|
||||
);
|
||||
|
||||
// lower and verify success
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
|
||||
let result = lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i < n should lower successfully");
|
||||
|
||||
// Compare instruction should be present
|
||||
let instructions = lowerer.take_last_instructions();
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_header_condition_generates_expected_instructions() {
|
||||
// Test that header condition i < 10 generates proper Compare instruction
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
|
||||
let result = lowerer.lower(&ast);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let instructions = lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "Should generate instructions");
|
||||
|
||||
// Should have Compare instruction
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,9 +72,29 @@ use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScope
|
||||
use crate::mir::loop_pattern_detection::error_messages::{
|
||||
format_unsupported_condition_error, extract_body_local_names,
|
||||
};
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
|
||||
/// Phase 240-EX: Helper to build Pattern2ScopeManager with minimal dependencies
|
||||
///
|
||||
/// This helper creates a Pattern2ScopeManager for use in ExprLowerer, providing
|
||||
/// a clean way to reuse scope management setup for both header and break conditions.
|
||||
fn make_pattern2_scope_manager<'a>(
|
||||
condition_env: &'a ConditionEnv,
|
||||
body_local_env: Option<&'a LoopBodyLocalEnv>,
|
||||
captured_env: Option<&'a CapturedEnv>,
|
||||
carrier_info: &'a CarrierInfo,
|
||||
) -> Pattern2ScopeManager<'a> {
|
||||
Pattern2ScopeManager {
|
||||
condition_env,
|
||||
loop_body_local_env: body_local_env,
|
||||
captured_env,
|
||||
carrier_info,
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower Pattern 2 (Loop with Conditional Break) to JoinIR
|
||||
///
|
||||
/// # Phase 188-Impl-2: Pure JoinIR Fragment Generation
|
||||
@ -240,13 +260,49 @@ pub(crate) fn lower_loop_with_break_minimal(
|
||||
i_param, carrier_param_ids
|
||||
);
|
||||
|
||||
// Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
|
||||
// This will allocate ValueIds dynamically based on condition complexity
|
||||
let (cond_value, mut cond_instructions) = lower_condition_to_joinir(
|
||||
condition,
|
||||
&mut alloc_value,
|
||||
env,
|
||||
)?;
|
||||
// Phase 169 / Phase 171-fix / Phase 240-EX: Lower condition
|
||||
//
|
||||
// Phase 240-EX: Try ExprLowerer first for supported patterns, fall back to legacy path
|
||||
let (cond_value, mut cond_instructions) = {
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
|
||||
// Build minimal ScopeManager for header condition
|
||||
let empty_body_env = LoopBodyLocalEnv::new();
|
||||
let empty_captured_env = CapturedEnv::new();
|
||||
|
||||
let scope_manager = make_pattern2_scope_manager(
|
||||
env,
|
||||
Some(&empty_body_env),
|
||||
Some(&empty_captured_env),
|
||||
carrier_info,
|
||||
);
|
||||
|
||||
if ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(condition) {
|
||||
// Phase 240-EX: ExprLowerer path (for simple conditions)
|
||||
let mut dummy_builder = MirBuilder::new();
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||
|
||||
match expr_lowerer.lower(condition) {
|
||||
Ok(value_id) => {
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
eprintln!("[joinir/pattern2/phase240] Header condition via ExprLowerer: {} instructions", instructions.len());
|
||||
(value_id, instructions)
|
||||
}
|
||||
Err(e) => {
|
||||
// Fail-Fast: If ExprLowerer says it's supported but fails, this is a bug
|
||||
return Err(format!(
|
||||
"[joinir/pattern2/phase240] ExprLowerer failed on supported condition: {:?}",
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy path: condition_to_joinir (for complex conditions not yet supported)
|
||||
eprintln!("[joinir/pattern2/phase240] Header condition via legacy path (not yet supported by ExprLowerer)");
|
||||
lower_condition_to_joinir(condition, &mut alloc_value, env)?
|
||||
}
|
||||
};
|
||||
|
||||
// After condition lowering, allocate remaining ValueIds
|
||||
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
|
||||
@ -260,9 +316,6 @@ pub(crate) fn lower_loop_with_break_minimal(
|
||||
// 従来経路と構造的に同等になることを期待している。
|
||||
let (break_cond_value, mut break_cond_instructions) = {
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
|
||||
// Phase 236-EX: ExprLowerer は MirBuilder 参照を要求するが、
|
||||
@ -275,12 +328,12 @@ pub(crate) fn lower_loop_with_break_minimal(
|
||||
let empty_body_env = LoopBodyLocalEnv::new();
|
||||
let empty_captured_env = CapturedEnv::new();
|
||||
|
||||
let scope_manager = Pattern2ScopeManager {
|
||||
condition_env: env,
|
||||
loop_body_local_env: Some(&empty_body_env),
|
||||
captured_env: Some(&empty_captured_env),
|
||||
let scope_manager = make_pattern2_scope_manager(
|
||||
env,
|
||||
Some(&empty_body_env),
|
||||
Some(&empty_captured_env),
|
||||
carrier_info,
|
||||
};
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||
let value_id = match expr_lowerer.lower(break_condition) {
|
||||
@ -742,4 +795,74 @@ mod tests {
|
||||
assert!(vars.contains("ch")); // The problematic body-local variable
|
||||
}
|
||||
|
||||
// Phase 240-EX: Integration test for ExprLowerer header path
|
||||
|
||||
#[test]
|
||||
fn test_pattern2_header_condition_via_exprlowerer() {
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar, CarrierRole, CarrierInit};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
|
||||
// Simple header condition: i < 10
|
||||
let loop_cond = binop_node(var_node("i"), int_literal_node(10));
|
||||
// Simple break condition: i >= 5
|
||||
let break_cond = binop_node(var_node("i"), int_literal_node(5));
|
||||
|
||||
let scope = minimal_scope();
|
||||
|
||||
// Setup ConditionEnv
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("i".to_string(), ValueId(100));
|
||||
|
||||
// Setup CarrierInfo
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
};
|
||||
|
||||
// Setup carrier_updates (empty for simple loop)
|
||||
let carrier_updates = BTreeMap::new();
|
||||
|
||||
// Setup JoinValueSpace
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
// Call lower_loop_with_break_minimal
|
||||
let result = lower_loop_with_break_minimal(
|
||||
scope,
|
||||
&loop_cond,
|
||||
&break_cond,
|
||||
&condition_env,
|
||||
&carrier_info,
|
||||
&carrier_updates,
|
||||
&[], // Empty body AST
|
||||
None, // No body-local env
|
||||
&mut join_value_space,
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "Should lower successfully with ExprLowerer for header condition");
|
||||
|
||||
let (join_module, _fragment_meta) = result.unwrap();
|
||||
|
||||
// Verify JoinModule structure
|
||||
assert_eq!(join_module.functions.len(), 3, "Should have 3 functions: main, loop_step, k_exit");
|
||||
|
||||
// Verify that loop_step has Compare instructions for both header and break conditions
|
||||
let loop_step_func = join_module.functions.values()
|
||||
.find(|f| f.name == "loop_step")
|
||||
.expect("Should have loop_step function");
|
||||
|
||||
let compare_count = loop_step_func.body.iter()
|
||||
.filter(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. })))
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
compare_count >= 2,
|
||||
"Should have at least 2 Compare instructions (header + break), got {}",
|
||||
compare_count
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user