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:
nyash-codex
2025-12-11 00:33:04 +09:00
parent 448bf3d8c5
commit a7dbc15878
116 changed files with 401 additions and 18 deletions

View File

@ -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);
}
}

View File

@ -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
);
}
}