use ir_lang::{BinOp, Builder, Inst, Terminator, Type, UnOp};
#[test]
fn test_lower_if_expression_to_diamond() {
let mut b = Builder::new("max", &[Type::Int, Type::Int], Type::Int);
let a = b.block_params(b.entry())[0];
let bb = b.block_params(b.entry())[1];
let join = b.create_block(&[Type::Int]);
let then_blk = b.create_block(&[]);
let else_blk = b.create_block(&[]);
let cond = b.bin(BinOp::Lt, a, bb);
b.branch(cond, then_blk, &[], else_blk, &[]);
b.switch_to(then_blk);
b.jump(join, &[bb]);
b.switch_to(else_blk);
b.jump(join, &[a]);
b.switch_to(join);
let result = b.block_params(join)[0];
b.ret(Some(result));
let func = b.finish();
assert_eq!(func.validate(), Ok(()));
assert_eq!(func.block_count(), 4);
assert_eq!(func.params(), &[Type::Int, Type::Int]);
assert_eq!(func.ret(), Type::Int);
assert!(matches!(
func.terminator(func.entry()),
Some(Terminator::Branch { .. })
));
assert_eq!(func.value_type(cond), Some(Type::Bool));
}
#[test]
fn test_lower_negation_in_a_branch() {
let mut b = Builder::new("abs", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
let join = b.create_block(&[Type::Int]);
let neg_blk = b.create_block(&[]);
let pos_blk = b.create_block(&[]);
let zero = b.iconst(0);
let is_neg = b.bin(BinOp::Lt, x, zero);
b.branch(is_neg, neg_blk, &[], pos_blk, &[]);
b.switch_to(neg_blk);
let negated = b.un(UnOp::Neg, x);
b.jump(join, &[negated]);
b.switch_to(pos_blk);
b.jump(join, &[x]);
b.switch_to(join);
let result = b.block_params(join)[0];
b.ret(Some(result));
let func = b.finish();
assert_eq!(func.validate(), Ok(()));
assert!(matches!(func.inst(negated), Some(Inst::Un(UnOp::Neg, _))));
}
#[test]
fn test_lower_countdown_loop() {
let mut b = Builder::new("countdown", &[Type::Int], Type::Unit);
let start = b.block_params(b.entry())[0];
let header = b.create_block(&[Type::Int]);
let body = b.create_block(&[]);
let exit = b.create_block(&[]);
b.jump(header, &[start]);
b.switch_to(header);
let i = b.block_params(header)[0];
let zero = b.iconst(0);
let more = b.bin(BinOp::Gt, i, zero);
b.branch(more, body, &[], exit, &[]);
b.switch_to(body);
let one = b.iconst(1);
let next = b.bin(BinOp::Sub, i, one);
b.jump(header, &[next]);
b.switch_to(exit);
b.ret(None);
let func = b.finish();
assert_eq!(func.validate(), Ok(()));
assert_eq!(func.block_params(header).len(), 1);
}
#[test]
fn test_display_reflects_built_structure() {
let mut b = Builder::new("poly", &[Type::Int, Type::Int], Type::Int);
let a = b.block_params(b.entry())[0];
let bb = b.block_params(b.entry())[1];
let sum = b.bin(BinOp::Add, a, bb);
let diff = b.bin(BinOp::Sub, a, bb);
let product = b.bin(BinOp::Mul, sum, diff);
b.ret(Some(product));
let text = b.finish().to_string();
assert!(text.contains("fn poly(int, int) -> int {"));
assert!(text.contains("b0(v0: int, v1: int):"));
assert!(text.contains("v2: int = add v0, v1"));
assert!(text.contains("v3: int = sub v0, v1"));
assert!(text.contains("v4: int = mul v2, v3"));
assert!(text.contains("return v4"));
}
#[test]
fn test_building_is_deterministic() {
fn build() -> ir_lang::Function {
let mut b = Builder::new("k", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
let two = b.iconst(2);
let doubled = b.bin(BinOp::Mul, x, two);
b.ret(Some(doubled));
b.finish()
}
assert_eq!(build(), build());
}