Files
hakorune/src/mir/join_ir/lowering/condition_lowering_box.rs
nyash-codex c18dde238a feat(joinir): Phase 75 - BindingId pilot lookup (dev-only)
Phase 75 pilots BindingId-based variable lookup in the ScopeManager and
ConditionEnv components. Demonstrates "BindingId priority → name fallback"
strategy with comprehensive testing and zero production impact.

Changes:
- scope_manager.rs: Added lookup_with_binding() trait method
- condition_env.rs: Implemented resolve_var_with_binding() with 3-tier fallback
- expr_lowerer.rs: Integrated pilot lookup paths
- condition_lowering_box.rs: Updated with BindingId support

Tests: 3/3 new pilot tests PASS (priority/fallback/legacy)
Tests: lib 958/958 PASS, normalized_dev 54/54 PASS (no regressions)

Design: Feature-gated with normalized_dev, enables Phase 76 expansion.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 05:35:04 +09:00

298 lines
9.5 KiB
Rust

//! Phase 244: Unified Condition Lowering Interface
//!
//! This module provides a trait-based abstraction for condition lowering,
//! allowing different implementations (ExprLowerer, legacy lowerers) to
//! be used interchangeably.
//!
//! ## Design Philosophy
//!
//! **Box-First**: ConditionLoweringBox is a "box" trait that encapsulates
//! all condition lowering logic with a single, unified interface.
//!
//! **Single Responsibility**: Implementations ONLY perform AST → ValueId lowering.
//! They do NOT manage scopes, extract variables, or handle PHI generation.
//!
//! **Fail-Safe**: Implementations return explicit errors for unsupported patterns,
//! allowing callers to fall back to alternative paths.
use super::scope_manager::ScopeManager;
use crate::ast::ASTNode;
use crate::mir::ValueId;
/// Phase 244: Context for condition lowering
///
/// This struct encapsulates all the necessary context for lowering a condition
/// expression to JoinIR, including loop variable information and scope access.
///
/// # Fields
///
/// * `loop_var_name` - Name of the loop variable (e.g., "i")
/// * `loop_var_id` - ValueId of the loop variable in JoinIR space
/// * `scope` - Reference to ScopeManager for variable lookup
/// * `alloc_value` - ValueId allocator function
///
/// # Example
///
/// ```ignore
/// let context = ConditionContext {
/// loop_var_name: "i".to_string(),
/// loop_var_id: ValueId(1),
/// scope: &scope_manager,
/// alloc_value: &mut alloc_fn,
/// };
///
/// let value_id = lowerer.lower_condition(&ast, &context)?;
/// ```
pub struct ConditionContext<'a, S: ScopeManager> {
/// Name of the loop variable (e.g., "i", "pos")
pub loop_var_name: String,
/// ValueId of the loop variable in JoinIR space
pub loop_var_id: ValueId,
/// Scope manager for variable resolution
pub scope: &'a S,
/// ValueId allocator function
pub alloc_value: &'a mut dyn FnMut() -> ValueId,
}
/// Phase 244: Unified condition lowering interface
///
/// This trait provides a common interface for all condition lowering implementations.
/// It allows Pattern 2/3/4 to use any lowering strategy (ExprLowerer, legacy, etc.)
/// without coupling to specific implementation details.
///
/// # Design Principles
///
/// 1. **Single Method**: `lower_condition()` is the only required method
/// 2. **Context-Based**: All necessary information passed via ConditionContext
/// 3. **Fail-Fast**: Errors returned immediately (no silent fallbacks)
/// 4. **Stateless**: Implementations should be reusable across multiple calls
///
/// # Example Implementation
///
/// ```ignore
/// impl<S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'_, '_, S> {
/// fn lower_condition(
/// &mut self,
/// condition: &ASTNode,
/// context: &ConditionContext<S>,
/// ) -> Result<ValueId, String> {
/// // Delegate to existing ExprLowerer::lower() method
/// self.lower(condition)
/// }
///
/// fn supports(&self, condition: &ASTNode) -> bool {
/// Self::is_supported_condition(condition)
/// }
/// }
/// ```
pub trait ConditionLoweringBox<S: ScopeManager> {
/// Lower condition AST to ValueId
///
/// This method translates an AST condition expression (e.g., `i < 10`,
/// `digit_pos < 0`) into a JoinIR ValueId representing the boolean result.
///
/// # Arguments
///
/// * `condition` - AST node representing the boolean condition
/// * `context` - Lowering context (loop var, scope, allocator)
///
/// # Returns
///
/// * `Ok(ValueId)` - Boolean result ValueId
/// * `Err(String)` - Lowering error (unsupported pattern, variable not found, etc.)
///
/// # Fail-Fast Principle
///
/// Implementations MUST return `Err` immediately for unsupported patterns.
/// Callers can then decide whether to fall back to alternative lowering paths.
///
/// # Example
///
/// ```ignore
/// // Pattern 2: Break condition lowering
/// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?;
///
/// // Use cond_value in conditional Jump
/// let break_jump = JoinInst::Jump {
/// target: k_exit,
/// args: vec![loop_var_id],
/// condition: Some(cond_value),
/// };
/// ```
fn lower_condition(
&mut self,
condition: &ASTNode,
context: &ConditionContext<S>,
) -> Result<ValueId, String>;
/// Check if this lowerer supports the given condition pattern
///
/// This method allows callers to check support BEFORE attempting lowering,
/// enabling early fallback to alternative strategies.
///
/// # Arguments
///
/// * `condition` - AST node to check
///
/// # Returns
///
/// * `true` - This lowerer can handle this pattern
/// * `false` - This lowerer cannot handle this pattern (caller should use alternative)
///
/// # Example
///
/// ```ignore
/// if lowerer.supports(&break_cond_ast) {
/// let value = lowerer.lower_condition(&break_cond_ast, &context)?;
/// } else {
/// // Fall back to legacy lowering
/// let value = legacy_lower_condition(&break_cond_ast, env)?;
/// }
/// ```
fn supports(&self, condition: &ASTNode) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinaryOperator, LiteralValue, Span};
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
fn span() -> Span {
Span::unknown()
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: span(),
}
}
fn lit_i(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: span(),
}
}
fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: op,
left: Box::new(left),
right: Box::new(right),
span: span(),
}
}
#[test]
fn test_condition_lowering_box_trait_exists() {
// This test verifies that the ConditionLoweringBox trait can be used
// with ExprLowerer (implementation added in Step 2).
let mut condition_env = ConditionEnv::new();
condition_env.insert("i".to_string(), ValueId(100));
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
let scope = Pattern2ScopeManager {
condition_env: &condition_env,
loop_body_local_env: None,
captured_env: None,
carrier_info: &carrier_info,
};
let mut builder = MirBuilder::new();
let mut alloc_counter = 1000u32;
let mut alloc_fn = || {
let id = ValueId(alloc_counter);
alloc_counter += 1;
id
};
let context = ConditionContext {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
scope: &scope,
alloc_value: &mut alloc_fn,
};
// AST: i < 10
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
// ExprLowerer implements ConditionLoweringBox (Step 2)
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
// Check support
assert!(
expr_lowerer.supports(&ast),
"ExprLowerer should support i < 10"
);
// Lower condition via ConditionLoweringBox trait (Step 2 implemented)
let result = expr_lowerer.lower_condition(&ast, &context);
assert!(result.is_ok(), "i < 10 should lower successfully via trait");
}
#[test]
fn test_condition_context_structure() {
// Verify ConditionContext can be constructed and fields are accessible
let mut condition_env = ConditionEnv::new();
condition_env.insert("i".to_string(), ValueId(100));
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
let scope = Pattern2ScopeManager {
condition_env: &condition_env,
loop_body_local_env: None,
captured_env: None,
carrier_info: &carrier_info,
};
let mut alloc_counter = 1000u32;
let mut alloc_fn = || {
let id = ValueId(alloc_counter);
alloc_counter += 1;
id
};
let context = ConditionContext {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
scope: &scope,
alloc_value: &mut alloc_fn,
};
assert_eq!(context.loop_var_name, "i");
assert_eq!(context.loop_var_id, ValueId(1));
// Verify allocator works
let vid = (context.alloc_value)();
assert_eq!(vid, ValueId(1000));
}
}