docs(joinir): Phase 232-239 documentation and ExprLowerer refinements
Documentation: - Move completion reports to docs/archive/reports/ - Add phase232-238 design/inventory documents - Update joinir-architecture-overview.md - Add doc-status-policy.md Code refinements: - ExprLowerer: condition catalog improvements - ScopeManager: boundary clarifications - CarrierInfo: cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -17,6 +17,7 @@
|
||||
|
||||
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::{JoinInst, MirLikeInst, BinOpKind, UnaryOp as JoinUnaryOp};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use super::scope_manager::ScopeManager;
|
||||
use super::condition_lowerer::lower_condition_to_joinir;
|
||||
@ -108,6 +109,12 @@ pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
|
||||
|
||||
/// 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> {
|
||||
@ -124,6 +131,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
context,
|
||||
builder,
|
||||
debug: false,
|
||||
last_instructions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +141,14 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
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
|
||||
@ -184,12 +200,15 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
id
|
||||
};
|
||||
|
||||
let (result_value, _instructions) = lower_condition_to_joinir(
|
||||
let (result_value, instructions) = lower_condition_to_joinir(
|
||||
ast,
|
||||
&mut alloc_value,
|
||||
&condition_env,
|
||||
).map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
|
||||
// Phase 235: 保存しておき、テストから観察できるようにする
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
|
||||
}
|
||||
@ -292,7 +311,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Span, LiteralValue};
|
||||
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, VarScopeKind};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
|
||||
@ -301,6 +320,41 @@ mod tests {
|
||||
MirBuilder::new()
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@ -451,4 +505,226 @@ mod tests {
|
||||
};
|
||||
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![],
|
||||
};
|
||||
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![],
|
||||
};
|
||||
|
||||
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_pattern2_break_methodcall_is_unsupported() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// s.length() (MethodCall) is not supported for Pattern2 break condition
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(var("s")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
assert!(
|
||||
!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"MethodCall should be rejected for Pattern2 break condition"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(
|
||||
matches!(result, Err(ExprLoweringError::UnsupportedNode(_))),
|
||||
"MethodCall should fail-fast with UnsupportedNode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user