refactor(normalized_shadow): Phase 138 - extract ReturnValueLowererBox (no behavior change)

Extract return value lowering logic to shared Box for SSOT:

New Files:
- common/return_value_lowerer_box.rs (~300 lines)
  - ReturnValueLowererBox::lower_to_value_id()
  - Supports: Variable, Integer literal, Add expression
  - 5 comprehensive unit tests
- common/mod.rs (module export)

Modified Files:
- loop_true_break_once.rs
  - Removed lower_return_value_to_vid() method (~115 lines)
  - Added import: use super::common::return_value_lowerer_box::ReturnValueLowererBox
  - Updated 2 call sites (post_k, k_exit)
  - Updated SSOT documentation
- mod.rs
  - Added pub mod common;

Code Reduction: ~115 lines removed from loop_true_break_once.rs

Tests:
- cargo test --lib: 1194 tests PASS (+5 new unit tests)
- Phase 137 regression: 6/6 PASS
- Phase 97 regression: 2/2 PASS
- Phase 131/135/136 regression: 3/3 PASS

Behavior: Unchanged (all Phase 136/137 fixtures/smokes PASS)

SSOT: common/return_value_lowerer_box.rs

Next: Phase 139 P0 - Migrate post_if_post_k.rs to ReturnValueLowererBox

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-19 00:55:51 +09:00
parent 962d1803c9
commit 95daf230c4
4 changed files with 331 additions and 126 deletions

View File

@ -0,0 +1,3 @@
//! Common utilities for Normalized shadow (Phase 138+)
pub mod return_value_lowerer_box;

View File

@ -0,0 +1,316 @@
//! ReturnValueLowererBox: Return value lowering SSOT (Phase 138 P0)
//!
//! ## Responsibility
//!
//! Lower return values (variable, literal, expr) to ValueId 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
//!
//! ## Fallback
//!
//! Out-of-scope patterns return `Ok(None)` for legacy routing
//!
//! ## Usage
//!
//! - Phase 138 P0: loop_true_break_once.rs
//! - Phase 139 P0: post_if_post_k.rs (planned)
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::control_tree::step_tree::AstNodeHandle;
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
use crate::mir::ValueId;
use std::collections::BTreeMap;
/// Box-First: Return value lowering for Normalized shadow
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)
/// - Err(_): Internal error (should not happen for valid AST)
///
/// Note: Does NOT return Err for unsupported patterns - returns Ok(None) instead
pub fn lower_to_value_id(
value_ast: &Option<AstNodeHandle>,
body: &mut Vec<JoinInst>,
next_value_id: &mut u32,
env: &BTreeMap<String, ValueId>,
) -> Result<Option<ValueId>, String> {
match value_ast {
None => {
// void return
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)
}
}
}
}
}
/// 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)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::control_tree::step_tree::AstNodeHandle;
fn make_span() -> Span {
Span::unknown()
}
#[test]
fn test_lower_variable() {
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),
&mut body,
&mut next_value_id,
&env,
)
.unwrap();
assert_eq!(result, Some(ValueId(42)));
assert_eq!(body.len(), 0); // No instructions emitted
}
#[test]
fn test_lower_integer_literal() {
let mut body = vec![];
let mut next_value_id = 100;
let env = BTreeMap::new();
let int_ast = AstNodeHandle(Box::new(ASTNode::Literal {
value: LiteralValue::Integer(7),
span: make_span(),
}));
let result = ReturnValueLowererBox::lower_to_value_id(
&Some(int_ast),
&mut body,
&mut next_value_id,
&env,
)
.unwrap();
assert_eq!(result, Some(ValueId(100)));
assert_eq!(body.len(), 1); // Const instruction emitted
assert_eq!(next_value_id, 101);
}
#[test]
fn test_lower_add_var_plus_int() {
let mut body = vec![];
let mut next_value_id = 100;
let mut env = BTreeMap::new();
env.insert("x".to_string(), ValueId(1));
let add_ast = AstNodeHandle(Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: make_span(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(2),
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(101))); // BinOp result
assert_eq!(body.len(), 2); // Const(2) + BinOp
assert_eq!(next_value_id, 102);
}
#[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() {
let mut body = vec![];
let mut next_value_id = 100;
let mut env = BTreeMap::new();
env.insert("x".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),
span: make_span(),
}),
span: make_span(),
}));
let result = ReturnValueLowererBox::lower_to_value_id(
&Some(sub_ast),
&mut body,
&mut next_value_id,
&env,
)
.unwrap();
assert_eq!(result, None); // Out of scope
assert_eq!(body.len(), 0); // No instructions emitted
}
}

View File

@ -26,9 +26,10 @@
//! - Post-loop (Phase 132-P4): One assignment + return (reuse Phase 130's lower_assign_stmt) //! - Post-loop (Phase 132-P4): One assignment + return (reuse Phase 130's lower_assign_stmt)
//! - Post-loop (Phase 133-P0): Multiple assignments + return (extend Phase 132-P4) //! - Post-loop (Phase 133-P0): Multiple assignments + return (extend Phase 132-P4)
//! //!
//! ## Return Value Lowering SSOT (Phase 137+) //! ## Return Value Lowering SSOT (Phase 138+)
//! //!
//! - Function: `lower_return_value_to_vid()` //! - **SSOT Location**: `common/return_value_lowerer_box.rs`
//! - Function: `ReturnValueLowererBox::lower_to_value_id()`
//! - Responsibility: Lower return values (variable, literal, expr) to ValueId //! - Responsibility: Lower return values (variable, literal, expr) to ValueId
//! - Supported patterns: //! - Supported patterns:
//! - Variable: env lookup //! - Variable: env lookup
@ -36,21 +37,21 @@
//! - Add expr (Phase 137): x + 2 → BinOp(Add, env[x], Const(2)) //! - Add expr (Phase 137): x + 2 → BinOp(Add, env[x], Const(2))
//! - Fallback: Out-of-scope patterns return `Ok(None)` for legacy routing //! - Fallback: Out-of-scope patterns return `Ok(None)` for legacy routing
//! //!
//! ### Boxification Trigger //! ### Usage
//! //!
//! If 2+ files need identical return lowering logic, promote to: //! - Phase 138 P0: loop_true_break_once.rs
//! - `normalized_shadow/common/return_value_lowerer_box.rs` //! - Phase 139 P0: post_if_post_k.rs (planned)
//! - Single responsibility: return value → ValueId conversion
//! //!
//! ## Fail-Fast //! ## Fail-Fast
//! //!
//! - Out of scope → Ok(None) (fallback to legacy) //! - Out of scope → Ok(None) (fallback to legacy)
//! - In scope but conversion failed → Err (with freeze_with_hint in strict mode) //! - In scope but conversion failed → Err (with freeze_with_hint in strict mode)
use super::common::return_value_lowerer_box::ReturnValueLowererBox;
use super::env_layout::EnvLayout; use super::env_layout::EnvLayout;
use super::legacy::LegacyLowerer; use super::legacy::LegacyLowerer;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; use crate::ast::{ASTNode, LiteralValue};
use crate::mir::control_tree::step_tree::{AstNodeHandle, StepNode, StepStmtKind, StepTree}; 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::carrier_info::JoinFragmentMeta;
use crate::mir::join_ir::lowering::error_tags; use crate::mir::join_ir::lowering::error_tags;
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinFunction, JoinFuncId, JoinInst, JoinModule, MirLikeInst}; use crate::mir::join_ir::{BinOpKind, ConstValue, JoinFunction, JoinFuncId, JoinInst, JoinModule, MirLikeInst};
@ -413,7 +414,7 @@ impl LoopTrueBreakOnceBuilderBox {
// Lower post-loop return (Phase 136: support variable + integer literal) // Lower post-loop return (Phase 136: support variable + integer literal)
if let Some(_ast_handle) = value_ast { if let Some(_ast_handle) = value_ast {
// Return with value // Return with value
match Self::lower_return_value_to_vid(value_ast, &mut post_k_func.body, &mut next_value_id, &env_post_k)? { match ReturnValueLowererBox::lower_to_value_id(value_ast, &mut post_k_func.body, &mut next_value_id, &env_post_k)? {
Some(vid) => { Some(vid) => {
post_k_func.body.push(JoinInst::Ret { value: Some(vid) }); post_k_func.body.push(JoinInst::Ret { value: Some(vid) });
} }
@ -477,7 +478,7 @@ impl LoopTrueBreakOnceBuilderBox {
StepStmtKind::Return { value_ast } => { StepStmtKind::Return { value_ast } => {
if let Some(_ast_handle) = value_ast { if let Some(_ast_handle) = value_ast {
// Return with value (Phase 136: variable + integer literal) // Return with value (Phase 136: variable + integer literal)
match Self::lower_return_value_to_vid(value_ast, &mut k_exit_func.body, &mut next_value_id, &env_k_exit)? { match ReturnValueLowererBox::lower_to_value_id(value_ast, &mut k_exit_func.body, &mut next_value_id, &env_k_exit)? {
Some(vid) => { Some(vid) => {
k_exit_func.body.push(JoinInst::Ret { value: Some(vid) }); k_exit_func.body.push(JoinInst::Ret { value: Some(vid) });
} }
@ -625,122 +626,6 @@ impl LoopTrueBreakOnceBuilderBox {
_ => false, _ => false,
} }
} }
/// Lower return value (variable or integer literal) to ValueId
///
/// Phase 136 P0: Support return variable and return integer literal
///
/// Returns:
/// - Ok(Some(vid)): variable from env or newly generated const
/// - Ok(None): out of scope (fallback to default behavior)
///
/// Note: Does NOT return Err - unsupported patterns return Ok(None) for fallback
fn lower_return_value_to_vid(
value_ast: &Option<AstNodeHandle>,
body: &mut Vec<JoinInst>,
next_value_id: &mut u32,
env: &BTreeMap<String, ValueId>,
) -> Result<Option<ValueId>, String> {
match value_ast {
None => {
// void return
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 123 pattern)
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 P0: BinaryOp (x + 2) support
ASTNode::BinaryOp { operator, left, right, .. } => {
// 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.as_ref() {
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.as_ref() {
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))
}
_ => {
// Other return value types - out of scope
Ok(None)
}
}
}
}
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -41,6 +41,7 @@ pub mod dev_pipeline;
pub mod parity_contract; pub mod parity_contract;
pub mod available_inputs_collector; // Phase 126: available_inputs SSOT pub mod available_inputs_collector; // Phase 126: available_inputs SSOT
pub mod exit_reconnector; // Phase 131 P1.5: Direct variable_map reconnection (Option B) pub mod exit_reconnector; // Phase 131 P1.5: Direct variable_map reconnection (Option B)
pub mod common; // Phase 138: Common utilities (ReturnValueLowererBox)
pub use builder::StepTreeNormalizedShadowLowererBox; pub use builder::StepTreeNormalizedShadowLowererBox;
pub use contracts::{CapabilityCheckResult, UnsupportedCapability}; pub use contracts::{CapabilityCheckResult, UnsupportedCapability};