refactor(expr_lowerer): split types and tests
This commit is contained in:
@ -1,875 +0,0 @@
|
||||
//! Phase 231: Expression Lowering with Unified Scope Management
|
||||
//!
|
||||
//! This module provides a pilot implementation of expression lowering that uses
|
||||
//! ScopeManager for variable resolution. It's a thin wrapper around existing
|
||||
//! condition_lowerer logic, focusing on API unification rather than reimplementation.
|
||||
//!
|
||||
//! ## Design Philosophy
|
||||
//!
|
||||
//! **Box-First**: ExprLowerer is a "box" that encapsulates expression lowering
|
||||
//! logic with clean boundaries: takes AST + ScopeManager, returns ValueId + instructions.
|
||||
//!
|
||||
//! **Incremental Adoption**: Phase 231 starts with Condition context only.
|
||||
//! Future phases will expand to support General expressions (method calls, etc.).
|
||||
//!
|
||||
//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers
|
||||
//! to fall back to legacy paths.
|
||||
|
||||
use super::condition_lowerer::{lower_condition_to_joinir, lower_condition_to_joinir_no_body_locals};
|
||||
use super::scope_manager::ScopeManager;
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
mod ast_support;
|
||||
mod scope_resolution;
|
||||
#[cfg(test)]
|
||||
mod test_helpers;
|
||||
|
||||
/// Phase 231: Expression lowering context
|
||||
///
|
||||
/// Defines the context in which an expression is being lowered, which affects
|
||||
/// what AST nodes are supported and how they're translated.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExprContext {
|
||||
/// Loop condition expression (limited subset: comparisons, logical ops)
|
||||
Condition,
|
||||
|
||||
/// General expression (future: method calls, box ops, etc.)
|
||||
#[allow(dead_code)] // Phase 231: Not yet implemented
|
||||
General,
|
||||
}
|
||||
|
||||
/// Phase 231: Expression lowering error
|
||||
///
|
||||
/// Explicit error types allow callers to handle different failure modes
|
||||
/// (e.g., fall back to legacy path for unsupported nodes).
|
||||
#[derive(Debug)]
|
||||
pub enum ExprLoweringError {
|
||||
/// AST node type not supported in this context
|
||||
UnsupportedNode(String),
|
||||
|
||||
/// Variable not found in any scope
|
||||
VariableNotFound(String),
|
||||
|
||||
/// Type error during lowering (e.g., non-boolean in condition)
|
||||
TypeError(String),
|
||||
|
||||
/// Internal lowering error (from condition_lowerer)
|
||||
LoweringError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExprLoweringError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExprLoweringError::UnsupportedNode(msg) => write!(f, "Unsupported node: {}", msg),
|
||||
ExprLoweringError::VariableNotFound(name) => write!(f, "Variable not found: {}", name),
|
||||
ExprLoweringError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
||||
ExprLoweringError::LoweringError(msg) => write!(f, "Lowering error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 231: Expression lowerer (pilot implementation)
|
||||
///
|
||||
/// This struct provides a unified interface for lowering AST expressions to
|
||||
/// JoinIR instructions, using ScopeManager for variable resolution.
|
||||
///
|
||||
/// ## Current Scope (Phase 231)
|
||||
///
|
||||
/// - **Context**: Condition only (loop/break conditions)
|
||||
/// - **Supported**: Literals, variables, comparisons (<, >, ==, !=, <=, >=), logical ops (and, or, not)
|
||||
/// - **Not Supported**: Method calls, NewBox, complex expressions
|
||||
///
|
||||
/// ## Usage Pattern
|
||||
///
|
||||
/// ```ignore
|
||||
/// let scope = Pattern2ScopeManager { ... };
|
||||
/// let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, builder);
|
||||
///
|
||||
/// match expr_lowerer.lower(&break_condition_ast) {
|
||||
/// Ok(value_id) => {
|
||||
/// // Use value_id in JoinIR
|
||||
/// }
|
||||
/// Err(ExprLoweringError::UnsupportedNode(_)) => {
|
||||
/// // Fall back to legacy condition_to_joinir path
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// // Handle other errors (variable not found, etc.)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
|
||||
/// Scope manager for variable resolution
|
||||
scope: &'env S,
|
||||
|
||||
/// Expression context (Condition vs General)
|
||||
context: ExprContext,
|
||||
|
||||
/// MIR builder (for ValueId allocation, not used in Phase 231)
|
||||
#[allow(dead_code)] // Phase 231: Reserved for future use
|
||||
builder: &'builder mut MirBuilder,
|
||||
|
||||
/// Debug flag (inherited from caller)
|
||||
debug: bool,
|
||||
|
||||
/// Last lowered instruction sequence (for testing/inspection)
|
||||
///
|
||||
/// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not
|
||||
/// instructions are emitted for supported patterns. Productionコードからは未使用。
|
||||
last_instructions: Vec<JoinInst>,
|
||||
}
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
/// Create a new expression lowerer
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `scope` - ScopeManager for variable resolution
|
||||
/// * `context` - Expression context (Condition or General)
|
||||
/// * `builder` - MIR builder (for future use)
|
||||
pub fn new(scope: &'env S, context: ExprContext, builder: &'builder mut MirBuilder) -> Self {
|
||||
Self {
|
||||
scope,
|
||||
context,
|
||||
builder,
|
||||
debug: false,
|
||||
last_instructions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable debug output
|
||||
pub fn with_debug(mut self, debug: bool) -> Self {
|
||||
self.debug = debug;
|
||||
self
|
||||
}
|
||||
|
||||
/// Take the last lowered instruction sequence (mainly for tests)
|
||||
///
|
||||
/// Phase 235: This allows unit tests to validate that Compare / BinOp / Not
|
||||
/// instructions are present without影響を与えずに ExprLowerer の外から観察できる。
|
||||
pub fn take_last_instructions(&mut self) -> Vec<JoinInst> {
|
||||
std::mem::take(&mut self.last_instructions)
|
||||
}
|
||||
|
||||
/// Lower an expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: This is the main entry point. Currently delegates to
|
||||
/// lower_condition for Condition context.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(ValueId)` - Expression result ValueId
|
||||
/// * `Err(ExprLoweringError)` - Lowering failed (caller can fall back to legacy)
|
||||
pub fn lower(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
match self.context {
|
||||
ExprContext::Condition => self.lower_condition(ast),
|
||||
ExprContext::General => Err(ExprLoweringError::UnsupportedNode(
|
||||
"General expression context not yet implemented (Phase 231)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower a condition expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: Thin wrapper around condition_lowerer. The main innovation
|
||||
/// is using ScopeManager for variable resolution instead of direct ConditionEnv.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(ValueId)` - Condition result ValueId (boolean)
|
||||
/// * `Err(ExprLoweringError)` - Lowering failed
|
||||
fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
// 1. Check if AST is supported in condition context
|
||||
if !ast_support::is_supported_condition(ast) {
|
||||
return Err(ExprLoweringError::UnsupportedNode(format!(
|
||||
"Unsupported condition node: {:?}",
|
||||
ast
|
||||
)));
|
||||
}
|
||||
|
||||
// 2. Build ConditionEnv from ScopeManager
|
||||
// Phase 79-1: Use BindingId-aware version when available
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let condition_env = scope_resolution::build_condition_env_from_scope_with_binding(
|
||||
self.scope,
|
||||
ast,
|
||||
self.builder,
|
||||
)?;
|
||||
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let condition_env = scope_resolution::build_condition_env_from_scope(self.scope, ast)?;
|
||||
|
||||
// 3. Delegate to existing condition_lowerer
|
||||
// Phase 231: We use the existing, well-tested lowering logic.
|
||||
let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let (result_value, instructions) =
|
||||
lower_condition_to_joinir_no_body_locals(ast, &mut alloc_value, &condition_env) // Phase 92 P2-2
|
||||
.map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
|
||||
// Phase 235: 保存しておき、テストから観察できるようにする
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
"[expr_lowerer/phase231] Lowered condition → ValueId({:?})",
|
||||
result_value
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result_value)
|
||||
}
|
||||
|
||||
/// Public helper used by Pattern2/3 callers to gate ExprLowerer usage.
|
||||
pub fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
ast_support::is_supported_condition(ast)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Phase 244: ConditionLoweringBox trait implementation
|
||||
// ============================================================================
|
||||
|
||||
use super::condition_lowering_box::{ConditionContext, ConditionLoweringBox};
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'env, 'builder, S> {
|
||||
/// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer
|
||||
///
|
||||
/// This allows ExprLowerer to be used interchangeably with other condition
|
||||
/// lowering implementations through the unified ConditionLoweringBox interface.
|
||||
///
|
||||
/// # Design
|
||||
///
|
||||
/// This implementation is a thin wrapper around the existing `lower()` method.
|
||||
/// The `ConditionContext` parameter is currently unused because ExprLowerer
|
||||
/// already has access to ScopeManager through its constructor.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Pattern 2: Use ExprLowerer via ConditionLoweringBox trait
|
||||
/// let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
///
|
||||
/// let context = ConditionContext {
|
||||
/// loop_var_name: "i".to_string(),
|
||||
/// loop_var_id: ValueId(1),
|
||||
/// scope: &scope,
|
||||
/// alloc_value: &mut alloc_fn,
|
||||
/// };
|
||||
///
|
||||
/// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?;
|
||||
/// ```
|
||||
fn lower_condition(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
context: &mut ConditionContext<S>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller.
|
||||
//
|
||||
// JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions.
|
||||
// If we allocate locally here (e.g. starting from 1000), we can collide with
|
||||
// other JoinIR value users (main params, carrier slots), and after remapping
|
||||
// this becomes a MIR-level ValueId collision.
|
||||
if !ast_support::is_supported_condition(condition) {
|
||||
return Err(format!("Unsupported condition node: {:?}", condition));
|
||||
}
|
||||
|
||||
// Build ConditionEnv from the provided scope (the caller controls the scope + allocator).
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let condition_env = scope_resolution::build_condition_env_from_scope_with_binding(
|
||||
context.scope,
|
||||
condition,
|
||||
self.builder,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let condition_env =
|
||||
scope_resolution::build_condition_env_from_scope(context.scope, condition)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT).
|
||||
// Phase 256.7: Pass current_static_box_name for this.method(...) support
|
||||
let (result_value, instructions) = lower_condition_to_joinir(
|
||||
condition,
|
||||
&mut *context.alloc_value,
|
||||
&condition_env,
|
||||
None, // body_local_env
|
||||
context.current_static_box_name.as_deref(), // Phase 256.7
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
"[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)",
|
||||
result_value
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result_value)
|
||||
}
|
||||
|
||||
/// Phase 244: Check if ExprLowerer supports a given condition pattern
|
||||
///
|
||||
/// This delegates to the existing `is_supported_condition()` static method,
|
||||
/// allowing callers to check support before attempting lowering.
|
||||
fn supports(&self, condition: &ASTNode) -> bool {
|
||||
ast_support::is_supported_condition(condition)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::test_helpers::{bin, lit_i, span, var};
|
||||
use super::*;
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::{BinOpKind, MirLikeInst, UnaryOp as JoinUnaryOp};
|
||||
|
||||
// Helper to create a test MirBuilder (Phase 231: minimal stub)
|
||||
fn create_test_builder() -> MirBuilder {
|
||||
MirBuilder::new()
|
||||
}
|
||||
|
||||
fn not(expr: ASTNode) -> ASTNode {
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(expr),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_simple_comparison() {
|
||||
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 = create_test_builder();
|
||||
|
||||
// AST: i < 10
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should lower simple comparison successfully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_variable_not_found() {
|
||||
let condition_env = ConditionEnv::new();
|
||||
|
||||
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 = create_test_builder();
|
||||
|
||||
// AST: unknown_var < 10
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "unknown_var".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ExprLoweringError::VariableNotFound(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_unsupported_node() {
|
||||
let condition_env = ConditionEnv::new();
|
||||
|
||||
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 = create_test_builder();
|
||||
|
||||
// AST: Break (unsupported in condition context)
|
||||
let ast = ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(result, Err(ExprLoweringError::UnsupportedNode(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_supported_condition() {
|
||||
// Supported: i < 10
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Supported: MethodCall
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Unsupported: Break node
|
||||
let ast = ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
}
|
||||
|
||||
// Phase 235: Additional patterns for condition lowering
|
||||
|
||||
fn make_basic_scope() -> Pattern2ScopeManager<'static> {
|
||||
// NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply.
|
||||
// テスト専用なので許容する。
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("i".to_string(), ValueId(1));
|
||||
condition_env.insert("j".to_string(), ValueId(2));
|
||||
|
||||
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
|
||||
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
|
||||
|
||||
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 boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
|
||||
Pattern2ScopeManager {
|
||||
condition_env: condition_env_ref,
|
||||
loop_body_local_env: None,
|
||||
captured_env: None,
|
||||
carrier_info: carrier_ref,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_scope_with_p_and_s() -> Pattern2ScopeManager<'static> {
|
||||
// Leak these tiny envs for test lifetime convenience only.
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("p".to_string(), ValueId(1));
|
||||
condition_env.insert("s".to_string(), ValueId(2));
|
||||
|
||||
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
|
||||
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
|
||||
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "p".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 boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
|
||||
Pattern2ScopeManager {
|
||||
condition_env: condition_env_ref,
|
||||
loop_body_local_env: None,
|
||||
captured_env: None,
|
||||
carrier_info: carrier_ref,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_has_compare(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))),
|
||||
"Expected at least one Compare instruction, got {:?}",
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op
|
||||
)),
|
||||
"Expected at least one BinOp {:?}, got {:?}",
|
||||
op,
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_not(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
op: JoinUnaryOp::Not,
|
||||
..
|
||||
})
|
||||
)),
|
||||
"Expected at least one UnaryOp::Not, got {:?}",
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_var_less_literal_generates_compare() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i < 10
|
||||
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i < 10 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_literal_less_var_generates_compare() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// 0 < i
|
||||
let ast = bin(BinaryOperator::Less, lit_i(0), var("i"));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "0 < i should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_greater_than_between_vars() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i > j
|
||||
let ast = bin(BinaryOperator::Greater, var("i"), var("j"));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i > j should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_and_combination() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i > 0 && j < 5
|
||||
let left = bin(BinaryOperator::Greater, var("i"), lit_i(0));
|
||||
let right = bin(BinaryOperator::Less, var("j"), lit_i(5));
|
||||
let ast = bin(BinaryOperator::And, left, right);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
assert_has_binop(&instructions, BinOpKind::And);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_not_of_comparison() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// !(i < 10)
|
||||
let inner = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
let ast = not(inner);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "!(i < 10) should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
assert_has_not(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() {
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("digit_pos".to_string(), ValueId(10));
|
||||
|
||||
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 = create_test_builder();
|
||||
|
||||
// digit_pos < 0
|
||||
let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"digit_pos < 0 should be supported in Pattern2 break condition"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "digit_pos < 0 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(
|
||||
!instructions.is_empty(),
|
||||
"instructions for digit_pos < 0 should not be empty"
|
||||
);
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_methodcall_unknown_method_is_rejected() {
|
||||
let scope = make_scope_with_p_and_s();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// Unknown method name should fail through MethodCallLowerer
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(var("s")),
|
||||
method: "unknown_method".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"MethodCall nodes should be routed to MethodCallLowerer for validation"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(
|
||||
matches!(result, Err(ExprLoweringError::LoweringError(msg)) if msg.contains("MethodCall")),
|
||||
"Unknown method should fail-fast via MethodCallLowerer"
|
||||
);
|
||||
}
|
||||
|
||||
// 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_supports_header_condition_with_length_call() {
|
||||
// header pattern: p < s.length()
|
||||
let length_call = ASTNode::MethodCall {
|
||||
object: Box::new(var("s")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
let ast = bin(BinaryOperator::Less, var("p"), length_call);
|
||||
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"p < s.length() should be supported for Pattern2 header condition"
|
||||
);
|
||||
|
||||
let scope = make_scope_with_p_and_s();
|
||||
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(), "p < s.length() should lower successfully");
|
||||
|
||||
let instructions = lowerer.take_last_instructions();
|
||||
assert_has_compare(&instructions);
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "length"
|
||||
)),
|
||||
"Expected BoxCall for length receiver in lowered 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);
|
||||
}
|
||||
}
|
||||
263
src/mir/join_ir/lowering/expr_lowerer/lowerer.rs
Normal file
263
src/mir/join_ir/lowering/expr_lowerer/lowerer.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use super::ast_support;
|
||||
use super::scope_resolution;
|
||||
use super::{ExprContext, ExprLoweringError};
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
use super::super::condition_lowerer::{
|
||||
lower_condition_to_joinir, lower_condition_to_joinir_no_body_locals,
|
||||
};
|
||||
use super::super::condition_lowering_box::{ConditionContext, ConditionLoweringBox};
|
||||
use super::super::scope_manager::ScopeManager;
|
||||
|
||||
/// Phase 231: Expression lowerer (pilot implementation)
|
||||
///
|
||||
/// This struct provides a unified interface for lowering AST expressions to
|
||||
/// JoinIR instructions, using ScopeManager for variable resolution.
|
||||
///
|
||||
/// ## Current Scope (Phase 231)
|
||||
///
|
||||
/// - **Context**: Condition only (loop/break conditions)
|
||||
/// - **Supported**: Literals, variables, comparisons (<, >, ==, !=, <=, >=), logical ops (and, or, not)
|
||||
/// - **Not Supported**: Method calls, NewBox, complex expressions
|
||||
///
|
||||
/// ## Usage Pattern
|
||||
///
|
||||
/// ```ignore
|
||||
/// let scope = Pattern2ScopeManager { ... };
|
||||
/// let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, builder);
|
||||
///
|
||||
/// match expr_lowerer.lower(&break_condition_ast) {
|
||||
/// Ok(value_id) => {
|
||||
/// // Use value_id in JoinIR
|
||||
/// }
|
||||
/// Err(ExprLoweringError::UnsupportedNode(_)) => {
|
||||
/// // Fall back to legacy condition_to_joinir path
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// // Handle other errors (variable not found, etc.)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
|
||||
/// Scope manager for variable resolution
|
||||
scope: &'env S,
|
||||
|
||||
/// Expression context (Condition vs General)
|
||||
context: ExprContext,
|
||||
|
||||
/// MIR builder (for ValueId allocation, not used in Phase 231)
|
||||
#[allow(dead_code)] // Phase 231: Reserved for future use
|
||||
builder: &'builder mut MirBuilder,
|
||||
|
||||
/// Debug flag (inherited from caller)
|
||||
debug: bool,
|
||||
|
||||
/// Last lowered instruction sequence (for testing/inspection)
|
||||
///
|
||||
/// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not
|
||||
/// instructions are emitted for supported patterns. Productionコードからは未使用。
|
||||
last_instructions: Vec<JoinInst>,
|
||||
}
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
/// Create a new expression lowerer
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `scope` - ScopeManager for variable resolution
|
||||
/// * `context` - Expression context (Condition or General)
|
||||
/// * `builder` - MIR builder (for future use)
|
||||
pub fn new(scope: &'env S, context: ExprContext, builder: &'builder mut MirBuilder) -> Self {
|
||||
Self {
|
||||
scope,
|
||||
context,
|
||||
builder,
|
||||
debug: false,
|
||||
last_instructions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable debug output
|
||||
pub fn with_debug(mut self, debug: bool) -> Self {
|
||||
self.debug = debug;
|
||||
self
|
||||
}
|
||||
|
||||
/// Take the last lowered instruction sequence (mainly for tests)
|
||||
///
|
||||
/// Phase 235: This allows unit tests to validate that Compare / BinOp / Not
|
||||
/// instructions are present without影響を与えずに ExprLowerer の外から観察できる。
|
||||
pub fn take_last_instructions(&mut self) -> Vec<JoinInst> {
|
||||
std::mem::take(&mut self.last_instructions)
|
||||
}
|
||||
|
||||
/// Lower an expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: This is the main entry point. Currently delegates to
|
||||
/// lower_condition for Condition context.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(ValueId)` - Expression result ValueId
|
||||
/// * `Err(ExprLoweringError)` - Lowering failed (caller can fall back to legacy)
|
||||
pub fn lower(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
match self.context {
|
||||
ExprContext::Condition => self.lower_condition(ast),
|
||||
ExprContext::General => Err(ExprLoweringError::UnsupportedNode(
|
||||
"General expression context not yet implemented (Phase 231)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower a condition expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: Thin wrapper around condition_lowerer. The main innovation
|
||||
/// is using ScopeManager for variable resolution instead of direct ConditionEnv.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(ValueId)` - Condition result ValueId (boolean)
|
||||
/// * `Err(ExprLoweringError)` - Lowering failed
|
||||
fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
// 1. Check if AST is supported in condition context
|
||||
if !ast_support::is_supported_condition(ast) {
|
||||
return Err(ExprLoweringError::UnsupportedNode(format!(
|
||||
"Unsupported condition node: {:?}",
|
||||
ast
|
||||
)));
|
||||
}
|
||||
|
||||
// 2. Build ConditionEnv from ScopeManager
|
||||
// Phase 79-1: Use BindingId-aware version when available
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let condition_env = scope_resolution::build_condition_env_from_scope_with_binding(
|
||||
self.scope,
|
||||
ast,
|
||||
self.builder,
|
||||
)?;
|
||||
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let condition_env = scope_resolution::build_condition_env_from_scope(self.scope, ast)?;
|
||||
|
||||
// 3. Delegate to existing condition_lowerer
|
||||
// Phase 231: We use the existing, well-tested lowering logic.
|
||||
let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let (result_value, instructions) =
|
||||
lower_condition_to_joinir_no_body_locals(ast, &mut alloc_value, &condition_env) // Phase 92 P2-2
|
||||
.map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
|
||||
// Phase 235: 保存しておき、テストから観察できるようにする
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
"[expr_lowerer/phase231] Lowered condition → ValueId({:?})",
|
||||
result_value
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result_value)
|
||||
}
|
||||
|
||||
/// Public helper used by Pattern2/3 callers to gate ExprLowerer usage.
|
||||
pub fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
ast_support::is_supported_condition(ast)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'env, 'builder, S> {
|
||||
/// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer
|
||||
///
|
||||
/// This allows ExprLowerer to be used interchangeably with other condition
|
||||
/// lowering implementations through the unified ConditionLoweringBox interface.
|
||||
///
|
||||
/// # Design
|
||||
///
|
||||
/// This implementation is a thin wrapper around the existing `lower()` method.
|
||||
/// The `ConditionContext` parameter is currently unused because ExprLowerer
|
||||
/// already has access to ScopeManager through its constructor.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Pattern 2: Use ExprLowerer via ConditionLoweringBox trait
|
||||
/// let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
///
|
||||
/// let context = ConditionContext {
|
||||
/// loop_var_name: "i".to_string(),
|
||||
/// loop_var_id: ValueId(1),
|
||||
/// scope: &scope,
|
||||
/// alloc_value: &mut alloc_fn,
|
||||
/// };
|
||||
///
|
||||
/// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?;
|
||||
/// ```
|
||||
fn lower_condition(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
context: &mut ConditionContext<S>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller.
|
||||
//
|
||||
// JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions.
|
||||
// If we allocate locally here (e.g. starting from 1000), we can collide with
|
||||
// other JoinIR value users (main params, carrier slots), and after remapping
|
||||
// this becomes a MIR-level ValueId collision.
|
||||
if !ast_support::is_supported_condition(condition) {
|
||||
return Err(format!("Unsupported condition node: {:?}", condition));
|
||||
}
|
||||
|
||||
// Build ConditionEnv from the provided scope (the caller controls the scope + allocator).
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let condition_env = scope_resolution::build_condition_env_from_scope_with_binding(
|
||||
context.scope,
|
||||
condition,
|
||||
self.builder,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let condition_env =
|
||||
scope_resolution::build_condition_env_from_scope(context.scope, condition)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT).
|
||||
// Phase 256.7: Pass current_static_box_name for this.method(...) support
|
||||
let (result_value, instructions) = lower_condition_to_joinir(
|
||||
condition,
|
||||
&mut *context.alloc_value,
|
||||
&condition_env,
|
||||
None, // body_local_env
|
||||
context.current_static_box_name.as_deref(), // Phase 256.7
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
"[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)",
|
||||
result_value
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result_value)
|
||||
}
|
||||
|
||||
/// Phase 244: Check if ExprLowerer supports a given condition pattern
|
||||
///
|
||||
/// This delegates to the existing `is_supported_condition()` static method,
|
||||
/// allowing callers to check support before attempting lowering.
|
||||
fn supports(&self, condition: &ASTNode) -> bool {
|
||||
ast_support::is_supported_condition(condition)
|
||||
}
|
||||
}
|
||||
28
src/mir/join_ir/lowering/expr_lowerer/mod.rs
Normal file
28
src/mir/join_ir/lowering/expr_lowerer/mod.rs
Normal file
@ -0,0 +1,28 @@
|
||||
//! Phase 231: Expression Lowering with Unified Scope Management
|
||||
//!
|
||||
//! This module provides a pilot implementation of expression lowering that uses
|
||||
//! ScopeManager for variable resolution. It's a thin wrapper around existing
|
||||
//! condition_lowerer logic, focusing on API unification rather than reimplementation.
|
||||
//!
|
||||
//! ## Design Philosophy
|
||||
//!
|
||||
//! **Box-First**: ExprLowerer is a "box" that encapsulates expression lowering
|
||||
//! logic with clean boundaries: takes AST + ScopeManager, returns ValueId + instructions.
|
||||
//!
|
||||
//! **Incremental Adoption**: Phase 231 starts with Condition context only.
|
||||
//! Future phases will expand to support General expressions (method calls, etc.).
|
||||
//!
|
||||
//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers
|
||||
//! to fall back to legacy paths.
|
||||
|
||||
mod ast_support;
|
||||
mod scope_resolution;
|
||||
#[cfg(test)]
|
||||
mod test_helpers;
|
||||
mod types;
|
||||
mod lowerer;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use lowerer::ExprLowerer;
|
||||
pub use types::{ExprContext, ExprLoweringError};
|
||||
@ -1,4 +1,5 @@
|
||||
use super::{ExprLoweringError, ScopeManager};
|
||||
use super::ExprLoweringError;
|
||||
use super::super::scope_manager::ScopeManager;
|
||||
use crate::ast::ASTNode;
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use crate::mir::builder::MirBuilder;
|
||||
|
||||
544
src/mir/join_ir/lowering/expr_lowerer/tests.rs
Normal file
544
src/mir/join_ir/lowering/expr_lowerer/tests.rs
Normal file
@ -0,0 +1,544 @@
|
||||
use super::test_helpers::{bin, lit_i, span, var};
|
||||
use super::{ExprContext, ExprLowerer, ExprLoweringError};
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::{BinOpKind, JoinInst, MirLikeInst, UnaryOp as JoinUnaryOp};
|
||||
use crate::mir::{builder::MirBuilder, ValueId};
|
||||
|
||||
// Helper to create a test MirBuilder (Phase 231: minimal stub)
|
||||
fn create_test_builder() -> MirBuilder {
|
||||
MirBuilder::new()
|
||||
}
|
||||
|
||||
fn not(expr: crate::ast::ASTNode) -> crate::ast::ASTNode {
|
||||
crate::ast::ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(expr),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_simple_comparison() {
|
||||
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 = create_test_builder();
|
||||
|
||||
// AST: i < 10
|
||||
let ast = crate::ast::ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(crate::ast::ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(crate::ast::ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should lower simple comparison successfully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_variable_not_found() {
|
||||
let condition_env = ConditionEnv::new();
|
||||
|
||||
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 = create_test_builder();
|
||||
|
||||
// AST: unknown_var < 10
|
||||
let ast = crate::ast::ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(crate::ast::ASTNode::Variable {
|
||||
name: "unknown_var".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(crate::ast::ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ExprLoweringError::VariableNotFound(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_unsupported_node() {
|
||||
let condition_env = ConditionEnv::new();
|
||||
|
||||
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 = create_test_builder();
|
||||
|
||||
// AST: Break (unsupported in condition context)
|
||||
let ast = crate::ast::ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(result, Err(ExprLoweringError::UnsupportedNode(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_supported_condition() {
|
||||
// Supported: i < 10
|
||||
let ast = crate::ast::ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(crate::ast::ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(crate::ast::ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Supported: MethodCall
|
||||
let ast = crate::ast::ASTNode::MethodCall {
|
||||
object: Box::new(crate::ast::ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Unsupported: Break node
|
||||
let ast = crate::ast::ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
}
|
||||
|
||||
// Phase 235: Additional patterns for condition lowering
|
||||
|
||||
fn make_basic_scope() -> Pattern2ScopeManager<'static> {
|
||||
// NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply.
|
||||
// テスト専用なので許容する。
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("i".to_string(), ValueId(1));
|
||||
condition_env.insert("j".to_string(), ValueId(2));
|
||||
|
||||
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
|
||||
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
|
||||
|
||||
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 boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
|
||||
Pattern2ScopeManager {
|
||||
condition_env: condition_env_ref,
|
||||
loop_body_local_env: None,
|
||||
captured_env: None,
|
||||
carrier_info: carrier_ref,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_scope_with_p_and_s() -> Pattern2ScopeManager<'static> {
|
||||
// Leak these tiny envs for test lifetime convenience only.
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("p".to_string(), ValueId(1));
|
||||
condition_env.insert("s".to_string(), ValueId(2));
|
||||
|
||||
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
|
||||
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
|
||||
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "p".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 boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
|
||||
Pattern2ScopeManager {
|
||||
condition_env: condition_env_ref,
|
||||
loop_body_local_env: None,
|
||||
captured_env: None,
|
||||
carrier_info: carrier_ref,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_has_compare(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))),
|
||||
"Expected at least one Compare instruction, got {:?}",
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op
|
||||
)),
|
||||
"Expected at least one BinOp {:?}, got {:?}",
|
||||
op,
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_not(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
op: JoinUnaryOp::Not,
|
||||
..
|
||||
})
|
||||
)),
|
||||
"Expected at least one UnaryOp::Not, got {:?}",
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_var_less_literal_generates_compare() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i < 10
|
||||
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i < 10 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_literal_less_var_generates_compare() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// 0 < i
|
||||
let ast = bin(BinaryOperator::Less, lit_i(0), var("i"));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "0 < i should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_greater_than_between_vars() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i > j
|
||||
let ast = bin(BinaryOperator::Greater, var("i"), var("j"));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i > j should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_and_combination() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i > 0 && j < 5
|
||||
let left = bin(BinaryOperator::Greater, var("i"), lit_i(0));
|
||||
let right = bin(BinaryOperator::Less, var("j"), lit_i(5));
|
||||
let ast = bin(BinaryOperator::And, left, right);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
assert_has_binop(&instructions, BinOpKind::And);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_not_of_comparison() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// !(i < 10)
|
||||
let inner = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
let ast = not(inner);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "!(i < 10) should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
assert_has_not(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() {
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("digit_pos".to_string(), ValueId(10));
|
||||
|
||||
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 = create_test_builder();
|
||||
|
||||
// digit_pos < 0
|
||||
let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"digit_pos < 0 should be supported in Pattern2 break condition"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "digit_pos < 0 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(
|
||||
!instructions.is_empty(),
|
||||
"instructions for digit_pos < 0 should not be empty"
|
||||
);
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_methodcall_unknown_method_is_rejected() {
|
||||
let scope = make_scope_with_p_and_s();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// Unknown method name should fail through MethodCallLowerer
|
||||
let ast = crate::ast::ASTNode::MethodCall {
|
||||
object: Box::new(var("s")),
|
||||
method: "unknown_method".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"MethodCall nodes should be routed to MethodCallLowerer for validation"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(
|
||||
matches!(result, Err(ExprLoweringError::LoweringError(msg)) if msg.contains("MethodCall")),
|
||||
"Unknown method should fail-fast via MethodCallLowerer"
|
||||
);
|
||||
}
|
||||
|
||||
// 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_supports_header_condition_with_length_call() {
|
||||
// header pattern: p < s.length()
|
||||
let length_call = crate::ast::ASTNode::MethodCall {
|
||||
object: Box::new(var("s")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
let ast = bin(BinaryOperator::Less, var("p"), length_call);
|
||||
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"p < s.length() should be supported for Pattern2 header condition"
|
||||
);
|
||||
|
||||
let scope = make_scope_with_p_and_s();
|
||||
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(), "p < s.length() should lower successfully");
|
||||
|
||||
let instructions = lowerer.take_last_instructions();
|
||||
assert_has_compare(&instructions);
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "length"
|
||||
)),
|
||||
"Expected BoxCall for length receiver in lowered 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);
|
||||
}
|
||||
43
src/mir/join_ir/lowering/expr_lowerer/types.rs
Normal file
43
src/mir/join_ir/lowering/expr_lowerer/types.rs
Normal file
@ -0,0 +1,43 @@
|
||||
/// Phase 231: Expression lowering context
|
||||
///
|
||||
/// Defines the context in which an expression is being lowered, which affects
|
||||
/// what AST nodes are supported and how they're translated.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExprContext {
|
||||
/// Loop condition expression (limited subset: comparisons, logical ops)
|
||||
Condition,
|
||||
|
||||
/// General expression (future: method calls, box ops, etc.)
|
||||
#[allow(dead_code)] // Phase 231: Not yet implemented
|
||||
General,
|
||||
}
|
||||
|
||||
/// Phase 231: Expression lowering error
|
||||
///
|
||||
/// Explicit error types allow callers to handle different failure modes
|
||||
/// (e.g., fall back to legacy path for unsupported nodes).
|
||||
#[derive(Debug)]
|
||||
pub enum ExprLoweringError {
|
||||
/// AST node type not supported in this context
|
||||
UnsupportedNode(String),
|
||||
|
||||
/// Variable not found in any scope
|
||||
VariableNotFound(String),
|
||||
|
||||
/// Type error during lowering (e.g., non-boolean in condition)
|
||||
TypeError(String),
|
||||
|
||||
/// Internal lowering error (from condition_lowerer)
|
||||
LoweringError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExprLoweringError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExprLoweringError::UnsupportedNode(msg) => write!(f, "Unsupported node: {}", msg),
|
||||
ExprLoweringError::VariableNotFound(name) => write!(f, "Variable not found: {}", name),
|
||||
ExprLoweringError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
||||
ExprLoweringError::LoweringError(msg) => write!(f, "Lowering error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user