Files
hakorune/src/mir/control_tree/step_tree/tests.rs

238 lines
8.1 KiB
Rust

use super::*;
use crate::ast::{Span, ASTNode, BinaryOperator, LiteralValue};
fn str_lit(s: &str) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::String(s.to_string()),
span: Span::unknown(),
}
}
fn eq(a: ASTNode, b: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(a),
right: Box::new(b),
span: Span::unknown(),
}
}
fn assign_x(num: i64) -> ASTNode {
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(num),
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
#[test]
fn build_step_tree_if_only_nested_if_is_structural() {
// Equivalent shape to Phase103 "if-only merge" fixture:
//
// local x = 0
// if "x" == "x" { if "y" == "z" { x=1 } else { x=2 } } else { x=3 }
// print(x)
let ast = vec![
ASTNode::Local {
variables: vec!["x".to_string()],
initial_values: vec![Some(Box::new(ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
}))],
span: Span::unknown(),
},
ASTNode::If {
condition: Box::new(eq(str_lit("x"), str_lit("x"))),
then_body: vec![ASTNode::If {
condition: Box::new(eq(str_lit("y"), str_lit("z"))),
then_body: vec![assign_x(1)],
else_body: Some(vec![assign_x(2)]),
span: Span::unknown(),
}],
else_body: Some(vec![assign_x(3)]),
span: Span::unknown(),
},
ASTNode::Print {
expression: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
},
];
let tree = StepTreeBuilderBox::build_from_block(&ast);
assert!(tree.features.has_if);
assert!(!tree.features.has_loop);
assert_eq!(tree.features.max_if_depth, 2);
assert_eq!(tree.contract.exits.len(), 0);
assert!(tree.contract.writes.contains("x"));
assert!(tree.contract.required_caps.contains(&StepCapability::If));
assert!(tree.contract.required_caps.contains(&StepCapability::NestedIf));
let basis = tree.signature_basis_string();
assert_eq!(
basis,
"kinds=Block,Stmt(local(x)),If,Block,If,Block,Stmt(assign(x)),Block,Stmt(assign(x)),Block,Stmt(assign(x)),Stmt(print);exits=;writes=x;reads=;caps=If,NestedIf;conds=(lit:str:x == lit:str:x)|(lit:str:y == lit:str:z)"
);
let tree2 = StepTreeBuilderBox::build_from_block(&ast);
assert_eq!(tree.signature, tree2.signature);
match tree.root {
StepNode::Block(nodes) => {
assert_eq!(nodes.len(), 3);
match &nodes[1] {
StepNode::If { then_branch, cond_ast, .. } => {
// cond_ast should be populated
assert!(matches!(&cond_ast.0.as_ref(), ASTNode::BinaryOp { .. }));
match &**then_branch {
StepNode::Block(inner_nodes) => match &inner_nodes[0] {
StepNode::If { cond_ast: inner_cond_ast, .. } => {
// inner cond_ast should also be populated
assert!(matches!(&inner_cond_ast.0.as_ref(), ASTNode::BinaryOp { .. }));
}
other => panic!("expected nested If, got {other:?}"),
},
other => panic!("expected Block in then_branch, got {other:?}"),
}
}
other => panic!("expected If at index 1, got {other:?}"),
}
}
other => panic!("expected root Block, got {other:?}"),
}
}
#[test]
fn step_tree_cond_ast_is_populated() {
// Phase 119: cond_ast should hold AST reference.
let ast = vec![ASTNode::If {
condition: Box::new(eq(str_lit("a"), str_lit("b"))),
then_body: vec![assign_x(1)],
else_body: None,
span: Span::unknown(),
}];
let tree = StepTreeBuilderBox::build_from_block(&ast);
match &tree.root {
StepNode::Block(nodes) => match &nodes[0] {
StepNode::If { cond_ast, .. } => {
// cond_ast should be populated with BinaryOp
assert!(matches!(&cond_ast.0.as_ref(), ASTNode::BinaryOp { .. }));
}
other => panic!("expected If, got {other:?}"),
},
other => panic!("expected root Block, got {other:?}"),
}
}
#[test]
fn step_tree_signature_is_stable_with_cond_ast() {
// Phase 119: cond_ast should NOT affect signature stability.
// Signature is based on cond_sig (AstSummary), not cond_ast.
let ast = vec![
ASTNode::Loop {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}),
body: vec![assign_x(1)],
span: Span::unknown(),
},
];
let tree1 = StepTreeBuilderBox::build_from_block(&ast);
let tree2 = StepTreeBuilderBox::build_from_block(&ast);
// Signature should be identical (deterministic)
assert_eq!(tree1.signature, tree2.signature);
// cond_ast should be populated
match &tree1.root {
StepNode::Block(nodes) => match &nodes[0] {
StepNode::Loop { cond_ast, .. } => {
assert!(matches!(&cond_ast.0.as_ref(), ASTNode::Literal { .. }));
}
other => panic!("expected Loop, got {other:?}"),
},
other => panic!("expected root Block, got {other:?}"),
}
}
#[test]
fn contract_extracts_loop_exits_and_writes_minimal() {
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: Span::unknown(),
}
}
fn int_lit(v: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(v),
span: Span::unknown(),
}
}
fn bin(op: BinaryOperator, lhs: ASTNode, rhs: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: op,
left: Box::new(lhs),
right: Box::new(rhs),
span: Span::unknown(),
}
}
fn assign(name: &str, value: ASTNode) -> ASTNode {
ASTNode::Assignment {
target: Box::new(var(name)),
value: Box::new(value),
span: Span::unknown(),
}
}
// local i=0; local x=0;
// loop(i < 3) { x = x + 1; if x == 2 { break } i = i + 1 }
let ast = vec![
ASTNode::Local {
variables: vec!["i".to_string()],
initial_values: vec![Some(Box::new(int_lit(0)))],
span: Span::unknown(),
},
ASTNode::Local {
variables: vec!["x".to_string()],
initial_values: vec![Some(Box::new(int_lit(0)))],
span: Span::unknown(),
},
ASTNode::Loop {
condition: Box::new(bin(BinaryOperator::Less, var("i"), int_lit(3))),
body: vec![
assign("x", bin(BinaryOperator::Add, var("x"), int_lit(1))),
ASTNode::If {
condition: Box::new(bin(BinaryOperator::Equal, var("x"), int_lit(2))),
then_body: vec![ASTNode::Break { span: Span::unknown() }],
else_body: None,
span: Span::unknown(),
},
assign("i", bin(BinaryOperator::Add, var("i"), int_lit(1))),
],
span: Span::unknown(),
},
];
let tree = StepTreeBuilderBox::build_from_block(&ast);
assert!(tree.features.has_loop);
assert!(tree.contract.exits.contains(&ExitKind::Break));
assert!(tree.contract.writes.contains("i"));
assert!(tree.contract.writes.contains("x"));
assert!(tree.contract.required_caps.contains(&StepCapability::Loop));
assert!(tree.contract.required_caps.contains(&StepCapability::If));
}