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:
nyash-codex
2025-12-11 00:21:29 +09:00
parent 13a676d406
commit 448bf3d8c5
44 changed files with 1372 additions and 79 deletions

View File

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