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:
@ -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()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
Reference in New Issue
Block a user