feat(normalization): Phase 142 P0 - Loop statement-level normalization

Phase 142-loopstmt P0: Statement-level normalization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-19 05:28:49 +09:00
parent 275fe45ba4
commit 4082abb30c
23 changed files with 1610 additions and 246 deletions

View File

@ -1,14 +1,10 @@
//! ReturnValueLowererBox: Return value lowering SSOT (Phase 138 P0)
//! ReturnValueLowererBox: Return syntax lowering (Phase 140 P0)
//!
//! ## Responsibility
//!
//! Lower return values (variable, literal, expr) to ValueId for Normalized shadow paths
//! Normalize return value syntax (`return` vs `return <expr>`) for Normalized shadow paths.
//!
//! ## Supported Patterns (Phase 136-137)
//!
//! - Variable: env lookup → ValueId
//! - Integer literal: Const generation → ValueId
//! - Add expr: (var|int) + int → BinOp(Add, lhs, rhs) → ValueId
//! Expression lowering is delegated to `NormalizedExprLowererBox` (SSOT).
//!
//! ## Fallback
//!
@ -17,11 +13,12 @@
//! ## Usage
//!
//! - Phase 138 P0: loop_true_break_once.rs
//! - Phase 139 P0: post_if_post_k.rs (planned)
//! - Phase 139 P0: post_if_post_k.rs
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use super::expr_lowerer_box::NormalizedExprLowererBox;
use super::expr_lowering_contract::{ExprLoweringScope, ImpurePolicy};
use crate::mir::control_tree::step_tree::AstNodeHandle;
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
use crate::mir::join_ir::JoinInst;
use crate::mir::ValueId;
use std::collections::BTreeMap;
@ -31,8 +28,6 @@ pub struct ReturnValueLowererBox;
impl ReturnValueLowererBox {
/// Lower return value to ValueId
///
/// Phase 136-137: Support variable, integer literal, and add expression
///
/// Returns:
/// - Ok(Some(vid)): Successfully lowered to ValueId
/// - Ok(None): Out of scope (fallback to legacy routing)
@ -51,112 +46,17 @@ impl ReturnValueLowererBox {
Ok(Some(ValueId(0))) // Dummy - caller handles void separately
}
Some(ast_handle) => {
match ast_handle.0.as_ref() {
// Variable: lookup in env
ASTNode::Variable { name, .. } => {
if let Some(&vid) = env.get(name) {
Ok(Some(vid))
} else {
// Variable not in env - out of scope
Ok(None)
}
}
// Integer literal: generate Const instruction (Phase 136)
ASTNode::Literal { value: LiteralValue::Integer(i), .. } => {
let const_vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_vid,
value: ConstValue::Integer(*i),
}));
Ok(Some(const_vid))
}
// Phase 137: BinaryOp (x + 2) support
ASTNode::BinaryOp { operator, left, right, .. } => {
Self::lower_binary_op(operator, left, right, body, next_value_id, env)
}
_ => {
// Other return value types - out of scope
Ok(None)
}
}
// Phase 141 P1: allow a small allowlist of known intrinsic method calls.
NormalizedExprLowererBox::lower_expr_with_scope(
ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly),
ast_handle.0.as_ref(),
env,
body,
next_value_id,
)
}
}
}
/// Lower binary operation to ValueId (Phase 137)
fn lower_binary_op(
operator: &BinaryOperator,
left: &ASTNode,
right: &ASTNode,
body: &mut Vec<JoinInst>,
next_value_id: &mut u32,
env: &BTreeMap<String, ValueId>,
) -> Result<Option<ValueId>, String> {
// Phase 137 contract: Add only
if !matches!(operator, BinaryOperator::Add) {
return Ok(None); // out of scope
}
// Lower LHS (Variable or Integer literal)
let lhs_vid = match left {
ASTNode::Variable { name, .. } => {
// Get from env
if let Some(&vid) = env.get(name) {
vid
} else {
// Variable not in env - out of scope
return Ok(None);
}
}
ASTNode::Literal { value: LiteralValue::Integer(i), .. } => {
// Generate Const for LHS integer literal
let vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::Integer(*i),
}));
vid
}
_ => {
// Other LHS types - out of scope
return Ok(None);
}
};
// Lower RHS (Integer literal only)
let rhs_vid = match right {
ASTNode::Literal { value: LiteralValue::Integer(i), .. } => {
// Generate Const for RHS integer literal
let vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::Integer(*i),
}));
vid
}
_ => {
// Other RHS types - out of scope (e.g., return x + y)
return Ok(None);
}
};
// Generate BinOp Add
let result_vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result_vid,
op: BinOpKind::Add,
lhs: lhs_vid,
rhs: rhs_vid,
}));
Ok(Some(result_vid))
}
}
#[cfg(test)]
@ -164,37 +64,32 @@ mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::control_tree::step_tree::AstNodeHandle;
use crate::mir::join_ir::JoinInst;
fn make_span() -> Span {
Span::unknown()
}
#[test]
fn test_lower_variable() {
fn test_void_return_dummy_value_id() {
let mut body = vec![];
let mut next_value_id = 100;
let mut env = BTreeMap::new();
env.insert("x".to_string(), ValueId(42));
let var_ast = AstNodeHandle(Box::new(ASTNode::Variable {
name: "x".to_string(),
span: make_span(),
}));
let result = ReturnValueLowererBox::lower_to_value_id(
&Some(var_ast),
&None,
&mut body,
&mut next_value_id,
&env,
&BTreeMap::new(),
)
.unwrap();
assert_eq!(result, Some(ValueId(42)));
assert_eq!(body.len(), 0); // No instructions emitted
assert_eq!(result, Some(ValueId(0)));
assert!(body.is_empty());
assert_eq!(next_value_id, 100);
}
#[test]
fn test_lower_integer_literal() {
fn test_delegates_integer_literal() {
let mut body = vec![];
let mut next_value_id = 100;
let env = BTreeMap::new();
@ -218,7 +113,7 @@ mod tests {
}
#[test]
fn test_lower_add_var_plus_int() {
fn test_delegates_add_var_plus_int() {
let mut body = vec![];
let mut next_value_id = 100;
let mut env = BTreeMap::new();
@ -251,66 +146,36 @@ mod tests {
}
#[test]
fn test_lower_add_int_plus_int() {
let mut body = vec![];
let mut next_value_id = 100;
let env = BTreeMap::new();
let add_ast = AstNodeHandle(Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: make_span(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(3),
span: make_span(),
}),
span: make_span(),
}));
let result = ReturnValueLowererBox::lower_to_value_id(
&Some(add_ast),
&mut body,
&mut next_value_id,
&env,
)
.unwrap();
assert_eq!(result, Some(ValueId(102))); // BinOp result
assert_eq!(body.len(), 3); // Const(5) + Const(3) + BinOp
assert_eq!(next_value_id, 103);
}
#[test]
fn test_out_of_scope_subtract() {
fn test_delegates_known_intrinsic_method_call_length0() {
let mut body = vec![];
let mut next_value_id = 100;
let mut env = BTreeMap::new();
env.insert("x".to_string(), ValueId(1));
env.insert("s".to_string(), ValueId(1));
let sub_ast = AstNodeHandle(Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Subtract,
left: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: make_span(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(2),
let length_ast = AstNodeHandle(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: make_span(),
}),
method: "length".to_string(),
arguments: vec![],
span: make_span(),
}));
let result = ReturnValueLowererBox::lower_to_value_id(
&Some(sub_ast),
&Some(length_ast),
&mut body,
&mut next_value_id,
&env,
)
.unwrap();
assert_eq!(result, None); // Out of scope
assert_eq!(body.len(), 0); // No instructions emitted
assert_eq!(result, Some(ValueId(100)));
assert_eq!(next_value_id, 101);
assert!(matches!(
body.as_slice(),
[JoinInst::MethodCall { method, args, .. }]
if method == "length" && args.is_empty()
));
}
}

View File

@ -841,4 +841,7 @@ mod tests {
"loop-only bridged MIR must have a single consistent k_exit arg[0]"
);
}
// Phase 141 P1 covers MethodCall as a "known intrinsic" on a small allowlist.
// End-to-end behavior is pinned by integration smokes in phase-141 docs.
}

View File

@ -19,7 +19,7 @@
//!
//! ## Scope
//!
//! - Post-if: Return(Variable) only (Phase 124/125/126 baseline)
//! - Post-if: Return value lowering uses `ReturnValueLowererBox` (Phase 138 SSOT)
//! - If body: Assign(int literal) only (Phase 128 baseline)
//! - Condition: minimal compare only (Phase 123 baseline)
//!
@ -30,6 +30,7 @@
use super::env_layout::EnvLayout;
use super::legacy::LegacyLowerer;
use super::common::return_value_lowerer_box::ReturnValueLowererBox;
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind, StepTree};
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
use crate::mir::join_ir::lowering::error_tags;
@ -251,17 +252,22 @@ impl PostIfPostKBuilderBox {
match n {
StepNode::Stmt { kind, .. } => match kind {
StepStmtKind::Return { value_ast } => {
// Phase 124/125/126: Return(Variable) support
if LegacyLowerer::lower_return_value(
value_ast,
&mut post_k_func.body,
&mut next_value_id,
&env_post_k,
&step_tree.contract,
)
.is_err()
{
return Ok(None);
if let Some(_ast_handle) = value_ast {
match ReturnValueLowererBox::lower_to_value_id(
value_ast,
&mut post_k_func.body,
&mut next_value_id,
&env_post_k,
)? {
Some(vid) => {
post_k_func.body.push(JoinInst::Ret { value: Some(vid) });
}
None => {
return Ok(None);
}
}
} else {
post_k_func.body.push(JoinInst::Ret { value: None });
}
}
_ => {