feat(control_tree): Phase 123 P1 return integer literal in Normalized if-only

- Add value_ast to StepStmtKind::Return for payload tracking
- Implement lower_return_value for Integer literal → Compute(Const) + Ret
- Add 3 unit tests: integer literal, void, variable fail-fast
- All tests passing (7 passed)
This commit is contained in:
nyash-codex
2025-12-18 05:37:13 +09:00
parent 51ed137339
commit f72064f35a
2 changed files with 259 additions and 15 deletions

View File

@ -61,7 +61,7 @@ impl StepTreeNormalizedShadowLowererBox {
}
}
/// Lower if-only StepTree to Normalized JoinModule (Phase 122)
/// Lower if-only StepTree to Normalized JoinModule (Phase 122-123)
///
/// ## Design
///
@ -69,6 +69,12 @@ impl StepTreeNormalizedShadowLowererBox {
/// - merge 形式: `join_k(env)` への tail-callPHI 禁止)
/// - 対応ノード: If/Return/Assign(最小セット)
///
/// ## Phase 123 Node Support
///
/// - Return(Integer literal): `Const + Ret(Some(vid))`
/// - Return(Variable): Fail-Fast (needs reads fact)
/// - Return(void): `Ret(None)`
///
/// ## Returns
///
/// - `Ok((module, meta))`: Normalized JoinModule生成成功
@ -80,11 +86,8 @@ impl StepTreeNormalizedShadowLowererBox {
use crate::mir::ValueId;
use std::collections::BTreeMap;
// Phase 122 P1: 最小実装 - main関数1つ + Ret のみ
// env レイアウト: writes から決定的に決める
// Phase 122: env レイアウト
let env_fields: Vec<String> = step_tree.contract.writes.iter().cloned().collect();
// 関数ID生成Phase 122では固定ID使用
let main_func_id = JoinFuncId::new(0);
// env フィールドに対応する引数ValueIdを生成
@ -98,16 +101,19 @@ impl StepTreeNormalizedShadowLowererBox {
})
.collect();
// main 関数生成(最小: Ret のみ)
// main 関数生成
let mut main_func = JoinFunction::new(
main_func_id,
"main".to_string(),
env_params.clone(),
);
// Phase 122 P1: 最小実装 - Return void のみ
// TODO Phase 122 P2-P4: If/Assign/条件式の lowering 実装
main_func.body.push(JoinInst::Ret { value: None });
// Phase 123: Return node lowering
Self::lower_return_from_tree(
&step_tree.root,
&mut main_func.body,
&mut next_value_id,
)?;
// JoinModule 構築
let mut module = JoinModule::new();
@ -121,6 +127,120 @@ impl StepTreeNormalizedShadowLowererBox {
Ok((module, meta))
}
/// Phase 123 P1: Lower Return node from StepTree
///
/// ## Support (Phase 123)
///
/// - Return(Integer literal): Generate Const + Ret(Some(vid))
/// - Return(void): Ret(None)
/// - Return(other): Fail-Fast with structured error
fn lower_return_from_tree(
node: &crate::mir::control_tree::step_tree::StepNode,
body: &mut Vec<crate::mir::join_ir::JoinInst>,
next_value_id: &mut u32,
) -> Result<(), String> {
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind};
use crate::mir::join_ir::JoinInst;
use crate::mir::ValueId;
match node {
StepNode::Block(nodes) => {
// Find first Return in block (Phase 123 minimal: single return only)
for n in nodes {
if let StepNode::Stmt {
kind: StepStmtKind::Return { value_ast },
..
} = n
{
return Self::lower_return_value(value_ast, body, next_value_id);
}
}
// No return found - default to void
body.push(JoinInst::Ret { value: None });
Ok(())
}
StepNode::Stmt {
kind: StepStmtKind::Return { value_ast },
..
} => Self::lower_return_value(value_ast, body, next_value_id),
_ => {
// No return in tree - default to void
body.push(JoinInst::Ret { value: None });
Ok(())
}
}
}
/// Phase 123 P1-P2: Lower return value
///
/// ## Support
///
/// - Integer literal: Generate Const + Ret(Some(vid))
/// - None: Ret(None)
/// - Variable: Fail-Fast (needs reads fact - Phase 124)
/// - Other: Fail-Fast (out of scope)
fn lower_return_value(
value_ast: &Option<crate::mir::control_tree::step_tree::AstNodeHandle>,
body: &mut Vec<crate::mir::join_ir::JoinInst>,
next_value_id: &mut u32,
) -> Result<(), String> {
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst};
use crate::mir::ValueId;
match value_ast {
None => {
body.push(JoinInst::Ret { value: None });
Ok(())
}
Some(ast_handle) => {
let ast = &ast_handle.0;
match &**ast {
ASTNode::Literal { value, .. } => match value {
LiteralValue::Integer(i) => {
// Phase 123 P1: Integer literal → Const + Ret(Some(vid))
let const_vid = ValueId(*next_value_id);
*next_value_id += 1;
// Generate Const instruction (wrapped in Compute)
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_vid,
value: ConstValue::Integer(*i),
}));
// Generate Ret instruction
body.push(JoinInst::Ret {
value: Some(const_vid),
});
Ok(())
}
_ => {
// Phase 123: Other literals not supported
Err(format!(
"[phase123/return/literal_unsupported] Phase 123 only supports integer literals. Hint: Use integer literal or wait for Phase 124"
))
}
},
ASTNode::Variable { name, .. } => {
// Phase 123 P2: Variable not supported (needs reads fact)
Err(format!(
"[phase123/return/var_unsupported] Phase 123 only supports return with integer literals (found variable: {}). Hint: Add reads fact (Phase 124) or return literal only",
name
))
}
_ => {
// Phase 123: Other expressions not supported
Err(format!(
"[phase123/return/expr_unsupported] Phase 123 only supports integer literals. Hint: Simplify to literal or wait for Phase 124"
))
}
}
}
}
}
/// Get shadow lowering status string for dev logging
///
/// ## Contract
@ -152,7 +272,9 @@ impl StepTreeNormalizedShadowLowererBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::control_tree::step_tree::{StepNode, StepTreeFeatures, StepTreeSignature};
use crate::mir::control_tree::step_tree::{
StepNode, StepStmtKind, StepTreeFeatures, StepTreeSignature,
};
use crate::mir::control_tree::step_tree_contract_box::StepTreeContract;
fn make_if_only_tree() -> StepTree {
@ -230,4 +352,115 @@ mod tests {
assert!(status.contains("shadow_lowered=false"));
assert!(status.contains("reason=\"contains loop"));
}
#[test]
fn test_return_integer_literal() {
use crate::ast::{ASTNode, LiteralValue, Span};
use crate::mir::control_tree::step_tree::AstNodeHandle;
// Create StepTree with "return 7"
let return_ast = Box::new(ASTNode::Literal {
value: LiteralValue::Integer(7),
span: Span::unknown(),
});
let mut tree = make_if_only_tree();
tree.root = StepNode::Stmt {
kind: StepStmtKind::Return {
value_ast: Some(AstNodeHandle(return_ast)),
},
span: Span::unknown(),
};
// Lower to JoinModule
let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree);
assert!(result.is_ok());
let (module, _meta) = result.unwrap().expect("Should generate JoinModule");
// Verify Const + Ret instructions
assert_eq!(module.functions.len(), 1);
let func = &module.functions.values().next().unwrap();
assert_eq!(func.body.len(), 2, "Should have Const + Ret");
// Check Const instruction
use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst};
use crate::mir::ValueId;
if let JoinInst::Compute(MirLikeInst::Const { dst, value }) = &func.body[0] {
assert_eq!(*dst, ValueId(1));
if let ConstValue::Integer(i) = value {
assert_eq!(*i, 7);
} else {
panic!("Expected Integer const");
}
} else {
panic!("Expected Const instruction");
}
// Check Ret instruction
if let JoinInst::Ret { value } = &func.body[1] {
assert_eq!(value, &Some(ValueId(1)));
} else {
panic!("Expected Ret instruction");
}
}
#[test]
fn test_return_void() {
use crate::ast::Span;
// Create StepTree with "return" (no value)
let mut tree = make_if_only_tree();
tree.root = StepNode::Stmt {
kind: StepStmtKind::Return { value_ast: None },
span: Span::unknown(),
};
// Lower to JoinModule
let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree);
assert!(result.is_ok());
let (module, _meta) = result.unwrap().expect("Should generate JoinModule");
// Verify Ret(None) instruction
assert_eq!(module.functions.len(), 1);
let func = &module.functions.values().next().unwrap();
assert_eq!(func.body.len(), 1, "Should have only Ret");
// Check Ret instruction
use crate::mir::join_ir::JoinInst;
if let JoinInst::Ret { value } = &func.body[0] {
assert_eq!(value, &None);
} else {
panic!("Expected Ret instruction");
}
}
#[test]
fn test_return_variable_fails() {
use crate::ast::{ASTNode, Span};
use crate::mir::control_tree::step_tree::AstNodeHandle;
// Create StepTree with "return x" (variable)
let return_ast = Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
});
let mut tree = make_if_only_tree();
tree.root = StepNode::Stmt {
kind: StepStmtKind::Return {
value_ast: Some(AstNodeHandle(return_ast)),
},
span: Span::unknown(),
};
// Lower to JoinModule - should fail
let result = StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("phase123/return/var_unsupported"));
assert!(err.contains("Phase 124"));
}
}

View File

@ -57,7 +57,10 @@ pub enum StepStmtKind {
LocalDecl { vars: Vec<String> },
Assign { target: Option<String> },
Print,
Return,
Return {
/// Phase 123: return value AST (for Normalized lowering)
value_ast: Option<AstNodeHandle>,
},
Break,
Continue,
Other(&'static str),
@ -190,7 +193,13 @@ impl StepStmtKind {
None => "assign(?)".to_string(),
},
StepStmtKind::Print => "print".to_string(),
StepStmtKind::Return => "return".to_string(),
StepStmtKind::Return { value_ast } => {
if value_ast.is_some() {
"return(value)".to_string()
} else {
"return(void)".to_string()
}
}
StepStmtKind::Break => "break".to_string(),
StepStmtKind::Continue => "continue".to_string(),
StepStmtKind::Other(name) => format!("other:{name}"),
@ -322,9 +331,11 @@ impl StepTreeBuilderBox {
let (node, features) = Self::build_block_node(body, if_depth, loop_depth);
(node.with_span(span.clone()), features)
}
ASTNode::Return { span, .. } => (
ASTNode::Return { value, span } => (
StepNode::Stmt {
kind: StepStmtKind::Return,
kind: StepStmtKind::Return {
value_ast: value.as_ref().map(|v| AstNodeHandle(v.clone())),
},
span: span.clone(),
},
StepTreeFeatures {
@ -497,7 +508,7 @@ fn walk_for_facts(node: &StepNode, facts: &mut StepTreeFacts) {
}
}
StepStmtKind::Print => {}
StepStmtKind::Return => {
StepStmtKind::Return { .. } => {
facts.add_exit(ExitKind::Return);
}
StepStmtKind::Break => {