use crate::ast::{BinOp, Literal, Spanned};
use crate::ir::hir::{BuiltinCtor, BuiltinIntrinsic};
use crate::types::Type;
use super::super::expr::{
MirBinOp, MirCall, MirCallee, MirConstruct, MirCtor, MirExpr, MirLet, MirMatch, MirMatchArm,
MirPattern,
};
use super::super::program::MirProgram;
use super::dead_code::is_pure;
pub fn const_fold(mut program: MirProgram) -> MirProgram {
let builtins = program.builtins.clone();
for mir_fn in program.fns.values_mut() {
fold_in_place(&mut mir_fn.body, &builtins);
}
program
}
fn fold_in_place(expr: &mut Spanned<MirExpr>, builtins: &[String]) {
walk_children(&mut expr.node, builtins);
if let Some(folded) = try_fold(&expr.node) {
let span = literal_span(folded, expr);
expr.node = MirExpr::Literal(span);
}
if let Some(replacement) = try_struct_fold(expr, builtins) {
*expr = replacement;
}
}
fn literal_span(lit: Literal, source: &Spanned<MirExpr>) -> Spanned<Literal> {
let ty = std::sync::OnceLock::new();
if let Some(t) = source.ty() {
let _ = ty.set(t.clone());
}
Spanned {
node: lit,
line: source.line,
ty,
}
}
fn walk_children(node: &mut MirExpr, builtins: &[String]) {
match node {
MirExpr::Literal(_) | MirExpr::Local(_) | MirExpr::FnValue(_) => {}
MirExpr::Neg(inner) => fold_in_place(inner, builtins),
MirExpr::BinOp(spanned_bop) => {
let bop: &mut MirBinOp = &mut spanned_bop.node;
fold_in_place(&mut bop.lhs, builtins);
fold_in_place(&mut bop.rhs, builtins);
}
MirExpr::Let(spanned_let) => {
let let_node: &mut MirLet = &mut spanned_let.node;
fold_in_place(&mut let_node.value, builtins);
fold_in_place(&mut let_node.body, builtins);
}
MirExpr::Call(spanned_call) => {
let call: &mut MirCall = &mut spanned_call.node;
for arg in &mut call.args {
fold_in_place(arg, builtins);
}
}
MirExpr::TailCall(spanned_tc) => {
for arg in &mut spanned_tc.node.args {
fold_in_place(arg, builtins);
}
}
MirExpr::Match(spanned_match) => {
fold_in_place(&mut spanned_match.node.subject, builtins);
for arm in &mut spanned_match.node.arms {
fold_arm(arm, builtins);
}
}
MirExpr::IfThenElse(spanned_ite) => {
fold_in_place(&mut spanned_ite.node.cond, builtins);
fold_in_place(&mut spanned_ite.node.then_branch, builtins);
fold_in_place(&mut spanned_ite.node.else_branch, builtins);
}
MirExpr::Construct(spanned_ctor) => {
let ctor: &mut MirConstruct = &mut spanned_ctor.node;
for arg in &mut ctor.args {
fold_in_place(arg, builtins);
}
}
MirExpr::RecordCreate(spanned_rec) => {
for f in &mut spanned_rec.node.fields {
fold_in_place(&mut f.value, builtins);
}
}
MirExpr::RecordUpdate(spanned_upd) => {
fold_in_place(&mut spanned_upd.node.base, builtins);
for f in &mut spanned_upd.node.updates {
fold_in_place(&mut f.value, builtins);
}
}
MirExpr::Project(spanned_proj) => fold_in_place(&mut spanned_proj.node.base, builtins),
MirExpr::Try(inner) => fold_in_place(inner, builtins),
MirExpr::Return(inner) => fold_in_place(inner, builtins),
MirExpr::Box(inner) | MirExpr::Unbox(inner) => fold_in_place(inner, builtins),
MirExpr::List(items) | MirExpr::Tuple(items) => {
for item in items {
fold_in_place(item, builtins);
}
}
MirExpr::MapLiteral(entries) => {
for (k, v) in entries {
fold_in_place(k, builtins);
fold_in_place(v, builtins);
}
}
MirExpr::InterpolatedStr(parts) => {
for part in parts {
if let super::super::expr::MirStrPart::Expr(e) = part {
fold_in_place(e, builtins);
}
}
}
MirExpr::IndependentProduct(spanned_ip) => {
for item in &mut spanned_ip.node.items {
fold_in_place(item, builtins);
}
}
}
}
fn fold_arm(arm: &mut MirMatchArm, builtins: &[String]) {
let _ = &arm.pattern;
let _: &MirPattern = &arm.pattern;
fold_in_place(&mut arm.body, builtins);
}
fn try_fold(node: &MirExpr) -> Option<Literal> {
match node {
MirExpr::Neg(inner) => {
let lit = literal_of(&inner.node)?;
fold_neg(lit)
}
MirExpr::BinOp(spanned_bop) => {
let bop = &spanned_bop.node;
let lhs = literal_of(&bop.lhs.node)?;
let rhs = literal_of(&bop.rhs.node)?;
fold_binop(bop.op, lhs, rhs)
}
_ => None,
}
}
fn literal_of(node: &MirExpr) -> Option<&Literal> {
if let MirExpr::Literal(spanned) = node {
Some(&spanned.node)
} else {
None
}
}
const DIV_BY_ZERO: &str = "division by zero";
const WILDCARD_SLOT: u32 = u16::MAX as u32;
fn builtin_callee_name<'a>(callee: &MirCallee, builtins: &'a [String]) -> Option<&'a str> {
if let MirCallee::Builtin(id) = callee {
builtins.get(id.0 as usize).map(String::as_str)
} else {
None
}
}
fn bare<T>(node: T) -> Spanned<T> {
Spanned::bare(node)
}
fn typed(node: MirExpr, ty: Option<&Type>) -> Spanned<MirExpr> {
let s = Spanned::bare(node);
if let Some(t) = ty {
s.set_ty(t.clone());
}
s
}
fn euclid_call(
intrinsic: BuiltinIntrinsic,
a: Spanned<MirExpr>,
k: Spanned<MirExpr>,
) -> Spanned<MirExpr> {
typed(
MirExpr::Call(bare(MirCall {
callee: MirCallee::Intrinsic(intrinsic),
args: vec![a, k],
})),
Some(&Type::Int),
)
}
fn builtin_ctor(
ctor: BuiltinCtor,
args: Vec<Spanned<MirExpr>>,
result_ty: Option<&Type>,
) -> Spanned<MirExpr> {
typed(
MirExpr::Construct(bare(MirConstruct {
ctor: MirCtor::Builtin(ctor),
args,
})),
result_ty,
)
}
fn div_by_zero_err(result_ty: Option<&Type>) -> Spanned<MirExpr> {
builtin_ctor(
BuiltinCtor::ResultErr,
vec![typed(
MirExpr::Literal(Spanned::bare(Literal::Str(DIV_BY_ZERO.to_string()))),
Some(&Type::Str),
)],
result_ty,
)
}
fn try_struct_fold(expr: &mut Spanned<MirExpr>, builtins: &[String]) -> Option<Spanned<MirExpr>> {
fold_a_partial_int_builtin(expr, builtins).or_else(|| fold_b_consume_ctor(expr, builtins))
}
fn fold_a_partial_int_builtin(
expr: &mut Spanned<MirExpr>,
builtins: &[String],
) -> Option<Spanned<MirExpr>> {
let MirExpr::Call(spanned_call) = &expr.node else {
return None;
};
let call = &spanned_call.node;
if call.args.len() != 2 {
return None;
}
let name = builtin_callee_name(&call.callee, builtins)?;
let is_div = match name {
"Int.div" => true,
"Int.mod" => false,
_ => return None,
};
let Literal::Int(k) = literal_of(&call.args[1].node)? else {
return None;
};
let k = *k;
if k == 0 && !is_pure(&call.args[0]) {
return None;
}
let result_ty = expr.ty().cloned();
let MirExpr::Call(spanned_call) = &mut expr.node else {
unreachable!("guarded above");
};
let mut drain = std::mem::take(&mut spanned_call.node.args).into_iter();
let a = drain.next()?;
let divisor = drain.next()?;
Some(if is_div {
match k {
0 => div_by_zero_err(result_ty.as_ref()),
-1 => {
spanned_call.node.args = vec![a, divisor];
return None;
}
_ => builtin_ctor(
BuiltinCtor::ResultOk,
vec![euclid_call(BuiltinIntrinsic::IntDivEuclid, a, divisor)],
result_ty.as_ref(),
),
}
} else {
match k {
0 => div_by_zero_err(result_ty.as_ref()),
_ => builtin_ctor(
BuiltinCtor::ResultOk,
vec![euclid_call(BuiltinIntrinsic::IntModEuclid, a, divisor)],
result_ty.as_ref(),
),
}
})
}
fn fold_b_consume_ctor(
expr: &mut Spanned<MirExpr>,
builtins: &[String],
) -> Option<Spanned<MirExpr>> {
match &expr.node {
MirExpr::Call(_) => fold_b_with_default(expr, builtins),
MirExpr::Match(_) => fold_b_match_over_ctor(expr),
MirExpr::Try(_) => fold_b_try_over_ctor(expr),
_ => None,
}
}
fn fold_b_try_over_ctor(expr: &mut Spanned<MirExpr>) -> Option<Spanned<MirExpr>> {
let value_ty = expr.ty().cloned();
let MirExpr::Try(inner) = &expr.node else {
return None;
};
let MirExpr::Construct(ctor_node) = &inner.node else {
return None;
};
let MirCtor::Builtin(ctor) = ctor_node.node.ctor else {
return None;
};
match ctor {
BuiltinCtor::ResultOk => {
let MirExpr::Try(inner) = &mut expr.node else {
unreachable!("guarded above");
};
let MirExpr::Construct(ctor_node) = &mut inner.node else {
unreachable!("guarded above");
};
std::mem::take(&mut ctor_node.node.args).into_iter().next()
}
BuiltinCtor::ResultErr => {
let MirExpr::Try(inner) = &mut expr.node else {
unreachable!("guarded above");
};
let err_ctor = std::mem::replace(
inner.as_mut(),
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
Some(typed(
MirExpr::Return(Box::new(err_ctor)),
value_ty.as_ref(),
))
}
_ => None,
}
}
fn fold_b_with_default(
expr: &mut Spanned<MirExpr>,
builtins: &[String],
) -> Option<Spanned<MirExpr>> {
let MirExpr::Call(spanned_call) = &expr.node else {
return None;
};
if spanned_call.node.args.len() != 2 {
return None;
}
let name = builtin_callee_name(&spanned_call.node.callee, builtins)?;
let is_result = match name {
"Result.withDefault" => true,
"Option.withDefault" => false,
_ => return None,
};
let MirExpr::Construct(inner) = &spanned_call.node.args[0].node else {
return None;
};
let MirCtor::Builtin(ctor) = inner.node.ctor else {
return None;
};
let take_payload = match (is_result, ctor) {
(true, BuiltinCtor::ResultOk) | (false, BuiltinCtor::OptionSome) => true,
(true, BuiltinCtor::ResultErr) | (false, BuiltinCtor::OptionNone) => false,
_ => return None,
};
if !take_payload {
let MirExpr::Construct(inner) = &spanned_call.node.args[0].node else {
return None;
};
if !inner.node.args.iter().all(is_pure) {
return None;
}
}
let MirExpr::Call(spanned_call) = &mut expr.node else {
unreachable!("guarded above");
};
let mut args = std::mem::take(&mut spanned_call.node.args).into_iter();
let ctor_arg = args.next()?; let default = args.next()?;
if take_payload {
let MirExpr::Construct(inner) = ctor_arg.node else {
return None;
};
inner.node.args.into_iter().next()
} else {
Some(default)
}
}
fn fold_b_match_over_ctor(expr: &mut Spanned<MirExpr>) -> Option<Spanned<MirExpr>> {
let match_ty = expr.ty().cloned();
let MirExpr::Match(spanned_match) = &expr.node else {
return None;
};
let MirExpr::Construct(subj) = &spanned_match.node.subject.node else {
return None;
};
let MirCtor::Builtin(subj_ctor) = subj.node.ctor else {
return None;
};
let subj_arity = subj.node.args.len();
let arm_idx = spanned_match
.node
.arms
.iter()
.position(|arm| arm_matches_ctor(&arm.pattern, subj_ctor, subj_arity))?;
let drops_impure = match &spanned_match.node.arms[arm_idx].pattern {
MirPattern::Wildcard => subj.node.args.iter().any(|a| !is_pure(a)),
MirPattern::Ctor { bindings, .. } => bindings
.iter()
.zip(&subj.node.args)
.any(|(slot, arg)| slot.0 == WILDCARD_SLOT && !is_pure(arg)),
_ => false,
};
if drops_impure {
return None;
}
let subj_ty = spanned_match.node.subject.ty().cloned();
let MirExpr::Match(spanned_match) = std::mem::replace(
&mut expr.node,
MirExpr::Literal(Spanned::bare(Literal::Unit)),
) else {
unreachable!("guarded by the let-else above");
};
let MirMatch { subject, mut arms } = spanned_match.node;
let MirExpr::Construct(subj) = subject.node else {
unreachable!("guarded by the Construct check above");
};
let ctor_args = subj.node.args;
let arm = arms.swap_remove(arm_idx);
Some(match arm.pattern {
MirPattern::Ctor {
bindings,
binding_names,
..
} => wrap_in_lets(
&bindings,
&binding_names,
ctor_args,
arm.body,
match_ty.as_ref(),
),
MirPattern::Bind(local, name) => typed(
MirExpr::Let(bare(MirLet {
binding: local,
binding_name: name,
value: Box::new(builtin_ctor(subj_ctor, ctor_args, subj_ty.as_ref())),
body: Box::new(arm.body),
})),
match_ty.as_ref(),
),
MirPattern::Wildcard => arm.body,
_ => arm.body,
})
}
fn arm_matches_ctor(pattern: &MirPattern, ctor: BuiltinCtor, arity: usize) -> bool {
match pattern {
MirPattern::Wildcard | MirPattern::Bind(_, _) => true,
MirPattern::Ctor {
ctor: MirCtor::Builtin(pc),
bindings,
..
} => *pc == ctor && bindings.len() == arity,
_ => false,
}
}
fn wrap_in_lets(
bindings: &[crate::ir::mir::LocalId],
binding_names: &[String],
ctor_args: Vec<Spanned<MirExpr>>,
body: Spanned<MirExpr>,
match_ty: Option<&Type>,
) -> Spanned<MirExpr> {
let mut acc = body;
let n = ctor_args.len();
for (i, (slot, arg)) in bindings.iter().zip(ctor_args).enumerate().rev() {
if slot.0 == WILDCARD_SLOT {
let _ = arg;
continue;
}
let name = binding_names.get(i).cloned().unwrap_or_default();
debug_assert!(i < n);
acc = typed(
MirExpr::Let(bare(MirLet {
binding: *slot,
binding_name: name,
value: Box::new(arg),
body: Box::new(acc),
})),
match_ty,
);
}
acc
}
fn fold_neg(lit: &Literal) -> Option<Literal> {
match lit {
Literal::Int(i) => i.checked_neg().map(Literal::Int),
Literal::Float(f) => Some(Literal::Float(-f)),
_ => None,
}
}
fn fold_binop(op: BinOp, lhs: &Literal, rhs: &Literal) -> Option<Literal> {
match (op, lhs, rhs) {
(BinOp::Add, Literal::Int(a), Literal::Int(b)) => a.checked_add(*b).map(Literal::Int),
(BinOp::Sub, Literal::Int(a), Literal::Int(b)) => a.checked_sub(*b).map(Literal::Int),
(BinOp::Mul, Literal::Int(a), Literal::Int(b)) => a.checked_mul(*b).map(Literal::Int),
(BinOp::Div, Literal::Int(a), Literal::Int(b)) => a.checked_div(*b).map(Literal::Int),
(BinOp::Add, Literal::Float(a), Literal::Float(b)) => Some(Literal::Float(a + b)),
(BinOp::Sub, Literal::Float(a), Literal::Float(b)) => Some(Literal::Float(a - b)),
(BinOp::Mul, Literal::Float(a), Literal::Float(b)) => Some(Literal::Float(a * b)),
(BinOp::Div, Literal::Float(a), Literal::Float(b)) => Some(Literal::Float(a / b)),
(BinOp::Eq, a, b) => literal_eq(a, b).map(Literal::Bool),
(BinOp::Neq, a, b) => literal_eq(a, b).map(|e| Literal::Bool(!e)),
(BinOp::Lt, Literal::Int(a), Literal::Int(b)) => Some(Literal::Bool(a < b)),
(BinOp::Lt, Literal::Float(a), Literal::Float(b)) => Some(Literal::Bool(a < b)),
(BinOp::Gt, Literal::Int(a), Literal::Int(b)) => Some(Literal::Bool(a > b)),
(BinOp::Gt, Literal::Float(a), Literal::Float(b)) => Some(Literal::Bool(a > b)),
(BinOp::Lte, Literal::Int(a), Literal::Int(b)) => Some(Literal::Bool(a <= b)),
(BinOp::Lte, Literal::Float(a), Literal::Float(b)) => Some(Literal::Bool(a <= b)),
(BinOp::Gte, Literal::Int(a), Literal::Int(b)) => Some(Literal::Bool(a >= b)),
(BinOp::Gte, Literal::Float(a), Literal::Float(b)) => Some(Literal::Bool(a >= b)),
_ => None,
}
}
pub(super) fn literal_eq(a: &Literal, b: &Literal) -> Option<bool> {
match (a, b) {
(Literal::Int(x), Literal::Int(y)) => Some(x == y),
(Literal::Float(x), Literal::Float(y)) => Some(x == y),
(Literal::Bool(x), Literal::Bool(y)) => Some(x == y),
(Literal::Str(x), Literal::Str(y)) => Some(x == y),
(Literal::Unit, Literal::Unit) => Some(true),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::super::test_helpers::{body_of, one_fn_program, span};
use super::*;
#[test]
fn folds_int_add() {
let body = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Add,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(1))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(2))))),
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(3)))
);
}
#[test]
fn folds_nested_arithmetic() {
let inner = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Add,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(1))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(2))))),
}));
let outer = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Mul,
lhs: Box::new(span(inner)),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(3))))),
}));
let folded = const_fold(one_fn_program(outer));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(9)))
);
}
#[test]
fn folds_neg_on_int_literal() {
let body = MirExpr::Neg(Box::new(span(MirExpr::Literal(span(Literal::Int(7))))));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(-7)))
);
}
#[test]
fn folds_eq_to_bool() {
let body = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Eq,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(5))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(5))))),
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Bool(true)))
);
}
#[test]
fn folds_lt_on_floats() {
let body = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Lt,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Float(1.5))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Float(2.0))))),
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Bool(true)))
);
}
#[test]
fn leaves_int_overflow_unfolded() {
let body = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Add,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(i64::MAX))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(1))))),
}));
let folded = const_fold(one_fn_program(body));
assert!(matches!(body_of(&folded), MirExpr::BinOp(_)));
}
#[test]
fn leaves_div_by_zero_unfolded() {
let body = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Div,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(5))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(0))))),
}));
let folded = const_fold(one_fn_program(body));
assert!(matches!(body_of(&folded), MirExpr::BinOp(_)));
}
#[test]
fn leaves_non_literal_operands_untouched() {
use super::super::super::expr::MirLocal;
use super::super::super::program::LocalId;
let body = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Add,
lhs: Box::new(span(MirExpr::Local(span(MirLocal::at(LocalId(0)))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(1))))),
}));
let folded = const_fold(one_fn_program(body));
assert!(matches!(body_of(&folded), MirExpr::BinOp(_)));
}
#[test]
fn folds_inside_let_value_and_body() {
use super::super::super::expr::MirLet;
use super::super::super::program::LocalId;
let value = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Add,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(1))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(2))))),
}));
let body_expr = MirExpr::BinOp(span(MirBinOp {
op: BinOp::Add,
lhs: Box::new(span(MirExpr::Literal(span(Literal::Int(3))))),
rhs: Box::new(span(MirExpr::Literal(span(Literal::Int(4))))),
}));
let body = MirExpr::Let(span(MirLet {
binding: LocalId(0),
binding_name: "x".to_string(),
value: Box::new(span(value)),
body: Box::new(span(body_expr)),
}));
let folded = const_fold(one_fn_program(body));
let MirExpr::Let(let_node) = body_of(&folded) else {
panic!("expected Let at root");
};
assert!(matches!(
&let_node.node.value.node,
MirExpr::Literal(s) if matches!(s.node, Literal::Int(3))
));
assert!(matches!(
&let_node.node.body.node,
MirExpr::Literal(s) if matches!(s.node, Literal::Int(7))
));
}
use super::super::super::expr::MirLocal;
use super::super::super::program::LocalId;
use crate::ir::BuiltinId;
fn int_lit(n: i64) -> Spanned<MirExpr> {
span(MirExpr::Literal(span(Literal::Int(n))))
}
fn program_with_builtin(builtin_name: &str, body: MirExpr) -> MirProgram {
let mut p = one_fn_program(body);
let id = p.intern_builtin(builtin_name);
assert_eq!(id, BuiltinId(0), "test assumes the first interned id");
p
}
fn builtin_call(args: Vec<Spanned<MirExpr>>) -> MirExpr {
MirExpr::Call(span(MirCall {
callee: MirCallee::Builtin(BuiltinId(0)),
args,
}))
}
#[test]
fn fold_a_div_const_to_euclid_ok() {
let body = builtin_call(vec![
span(MirExpr::Local(span(MirLocal::at(LocalId(0))))),
int_lit(10),
]);
let folded = const_fold(program_with_builtin("Int.div", body));
let MirExpr::Construct(c) = body_of(&folded) else {
panic!("expected Result.Ok Construct, got {:?}", body_of(&folded));
};
assert!(matches!(
c.node.ctor,
MirCtor::Builtin(BuiltinCtor::ResultOk)
));
let MirExpr::Call(inner) = &c.node.args[0].node else {
panic!("expected IntDivEuclid call");
};
assert!(matches!(
inner.node.callee,
MirCallee::Intrinsic(BuiltinIntrinsic::IntDivEuclid)
));
}
#[test]
fn fold_a_div_by_zero_to_err() {
let body = builtin_call(vec![int_lit(99), int_lit(0)]);
let folded = const_fold(program_with_builtin("Int.div", body));
let MirExpr::Construct(c) = body_of(&folded) else {
panic!("expected Result.Err Construct");
};
assert!(matches!(
c.node.ctor,
MirCtor::Builtin(BuiltinCtor::ResultErr)
));
let MirExpr::Literal(s) = &c.node.args[0].node else {
panic!("expected Err payload literal");
};
assert!(matches!(&s.node, Literal::Str(m) if m == "division by zero"));
}
#[test]
fn fold_a_div_by_minus_one_left_unchanged() {
let body = builtin_call(vec![int_lit(123), int_lit(-1)]);
let folded = const_fold(program_with_builtin("Int.div", body));
let MirExpr::Call(c) = body_of(&folded) else {
panic!("expected the Int.div Builtin call to survive");
};
assert!(matches!(c.node.callee, MirCallee::Builtin(BuiltinId(0))));
assert_eq!(c.node.args.len(), 2, "args must be restored intact");
}
#[test]
fn fold_a_mod_const_to_euclid_ok() {
let body = builtin_call(vec![
span(MirExpr::Local(span(MirLocal::at(LocalId(0))))),
int_lit(7),
]);
let folded = const_fold(program_with_builtin("Int.mod", body));
let MirExpr::Construct(c) = body_of(&folded) else {
panic!("expected Result.Ok Construct");
};
assert!(matches!(
c.node.ctor,
MirCtor::Builtin(BuiltinCtor::ResultOk)
));
let MirExpr::Call(inner) = &c.node.args[0].node else {
panic!("expected IntModEuclid call");
};
assert!(matches!(
inner.node.callee,
MirCallee::Intrinsic(BuiltinIntrinsic::IntModEuclid)
));
}
#[test]
fn fold_a_mod_by_zero_to_err() {
let body = builtin_call(vec![int_lit(5), int_lit(0)]);
let folded = const_fold(program_with_builtin("Int.mod", body));
let MirExpr::Construct(c) = body_of(&folded) else {
panic!("expected Result.Err Construct");
};
assert!(matches!(
c.node.ctor,
MirCtor::Builtin(BuiltinCtor::ResultErr)
));
}
#[test]
fn fold_a_div_non_literal_divisor_unchanged() {
let body = builtin_call(vec![
int_lit(40),
span(MirExpr::Local(span(MirLocal::at(LocalId(1))))),
]);
let folded = const_fold(program_with_builtin("Int.div", body));
assert!(matches!(body_of(&folded), MirExpr::Call(_)));
}
fn ctor(c: BuiltinCtor, args: Vec<Spanned<MirExpr>>) -> Spanned<MirExpr> {
span(MirExpr::Construct(span(MirConstruct {
ctor: MirCtor::Builtin(c),
args,
})))
}
#[test]
fn fold_b_result_withdefault_ok_takes_payload() {
let body = builtin_call(vec![
ctor(BuiltinCtor::ResultOk, vec![int_lit(7)]),
int_lit(0),
]);
let folded = const_fold(program_with_builtin("Result.withDefault", body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(7)))
);
}
#[test]
fn fold_b_result_withdefault_err_takes_default() {
let body = builtin_call(vec![
ctor(
BuiltinCtor::ResultErr,
vec![span(MirExpr::Literal(span(Literal::Str("x".into()))))],
),
int_lit(0),
]);
let folded = const_fold(program_with_builtin("Result.withDefault", body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(0)))
);
}
#[test]
fn fold_b_option_withdefault_some_takes_payload() {
let body = builtin_call(vec![
ctor(BuiltinCtor::OptionSome, vec![int_lit(9)]),
int_lit(0),
]);
let folded = const_fold(program_with_builtin("Option.withDefault", body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(9)))
);
}
#[test]
fn fold_b_option_withdefault_none_takes_default() {
let body = builtin_call(vec![ctor(BuiltinCtor::OptionNone, vec![]), int_lit(42)]);
let folded = const_fold(program_with_builtin("Option.withDefault", body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(42)))
);
}
#[test]
fn fold_b_try_over_ok_unwraps() {
let body = MirExpr::Try(Box::new(ctor(BuiltinCtor::ResultOk, vec![int_lit(7)])));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(7)))
);
}
#[test]
fn fold_b_try_over_err_returns() {
let err = ctor(
BuiltinCtor::ResultErr,
vec![span(MirExpr::Literal(span(Literal::Str("x".into()))))],
);
let folded = const_fold(one_fn_program(MirExpr::Try(Box::new(err))));
let MirExpr::Return(inner) = body_of(&folded) else {
panic!(
"expected `return Result.Err(...)`, got {:?}",
body_of(&folded)
);
};
let MirExpr::Construct(c) = &inner.node else {
panic!("expected Result.Err under the return");
};
assert!(matches!(
c.node.ctor,
MirCtor::Builtin(BuiltinCtor::ResultErr)
));
}
#[test]
fn fold_a_then_b_try_div_const_to_bare_euclid() {
let div = builtin_call(vec![
span(MirExpr::Local(span(MirLocal::at(LocalId(0))))),
int_lit(10),
]);
let folded = const_fold(program_with_builtin(
"Int.div",
MirExpr::Try(Box::new(span(div))),
));
let MirExpr::Call(c) = body_of(&folded) else {
panic!(
"expected bare IntDivEuclid call, got {:?}",
body_of(&folded)
);
};
assert!(matches!(
c.node.callee,
MirCallee::Intrinsic(BuiltinIntrinsic::IntDivEuclid)
));
}
#[test]
fn fold_b_match_ok_binds_payload_in_let() {
let subject = ctor(
BuiltinCtor::ResultOk,
vec![span(MirExpr::Local(span(MirLocal::at(LocalId(0)))))],
);
let ok_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk),
bindings: vec![LocalId(1)],
binding_names: vec!["q".to_string()],
},
body: span(MirExpr::Local(span(MirLocal::at(LocalId(1))))),
};
let err_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultErr),
bindings: vec![LocalId(2)],
binding_names: vec!["_".to_string()],
},
body: int_lit(-1),
};
let body = MirExpr::Match(span(MirMatch {
subject: Box::new(subject),
arms: vec![ok_arm, err_arm],
}));
let folded = const_fold(one_fn_program(body));
let MirExpr::Let(l) = body_of(&folded) else {
panic!("expected `let q = x; q`, got {:?}", body_of(&folded));
};
assert_eq!(l.node.binding, LocalId(1));
assert_eq!(l.node.binding_name, "q");
assert!(matches!(
&l.node.value.node,
MirExpr::Local(s) if s.node.slot == LocalId(0)
));
assert!(matches!(
&l.node.body.node,
MirExpr::Local(s) if s.node.slot == LocalId(1)
));
}
#[test]
fn fold_b_match_none_picks_nullary_arm_no_let() {
let subject = ctor(BuiltinCtor::OptionNone, vec![]);
let some_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionSome),
bindings: vec![LocalId(1)],
binding_names: vec!["v".to_string()],
},
body: span(MirExpr::Local(span(MirLocal::at(LocalId(1))))),
};
let none_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionNone),
bindings: vec![],
binding_names: vec![],
},
body: int_lit(5),
};
let body = MirExpr::Match(span(MirMatch {
subject: Box::new(subject),
arms: vec![some_arm, none_arm],
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(5)))
);
}
#[test]
fn fold_b_match_non_literal_subject_unchanged() {
let body = MirExpr::Match(span(MirMatch {
subject: Box::new(span(MirExpr::Local(span(MirLocal::at(LocalId(0)))))),
arms: vec![MirMatchArm {
pattern: MirPattern::Wildcard,
body: int_lit(1),
}],
}));
let folded = const_fold(one_fn_program(body));
assert!(matches!(body_of(&folded), MirExpr::Match(_)));
}
#[test]
fn fold_a_then_b_compose_to_bare_euclid() {
let mut p = one_fn_program(MirExpr::Literal(span(Literal::Unit)));
let div_id = p.intern_builtin("Int.div");
let wd_id = p.intern_builtin("Result.withDefault");
let inner_div = MirExpr::Call(span(MirCall {
callee: MirCallee::Builtin(div_id),
args: vec![
span(MirExpr::Local(span(MirLocal::at(LocalId(0))))),
int_lit(10),
],
}));
let body = MirExpr::Call(span(MirCall {
callee: MirCallee::Builtin(wd_id),
args: vec![span(inner_div), int_lit(0)],
}));
p.fns.get_mut(&crate::ir::FnId(0)).unwrap().body = span(body);
let folded = const_fold(p);
let MirExpr::Call(c) = body_of(&folded) else {
panic!(
"expected bare IntDivEuclid call, got {:?}",
body_of(&folded)
);
};
assert!(
matches!(
c.node.callee,
MirCallee::Intrinsic(BuiltinIntrinsic::IntDivEuclid)
),
"withDefault(Int.div(a, 10), 0) must fold to a bare Euclidean intrinsic"
);
}
fn impure() -> Spanned<MirExpr> {
span(MirExpr::Call(span(MirCall {
callee: MirCallee::Intrinsic(BuiltinIntrinsic::IntDivEuclid),
args: vec![int_lit(1), int_lit(1)],
})))
}
#[test]
fn fold_a_div_by_zero_keeps_impure_dividend() {
let body = builtin_call(vec![impure(), int_lit(0)]);
let folded = const_fold(program_with_builtin("Int.div", body));
let MirExpr::Call(c) = body_of(&folded) else {
panic!(
"impure dividend must keep the runtime Int.div call, got {:?}",
body_of(&folded)
);
};
assert!(matches!(c.node.callee, MirCallee::Builtin(BuiltinId(0))));
assert_eq!(c.node.args.len(), 2, "both args restored intact");
}
#[test]
fn fold_a_div_by_zero_still_folds_pure_dividend() {
let body = builtin_call(vec![int_lit(99), int_lit(0)]);
let folded = const_fold(program_with_builtin("Int.div", body));
let MirExpr::Construct(c) = body_of(&folded) else {
panic!("pure dividend over /0 must still fold to Err");
};
assert!(matches!(
c.node.ctor,
MirCtor::Builtin(BuiltinCtor::ResultErr)
));
}
#[test]
fn fold_b_withdefault_err_keeps_impure_payload() {
let body = builtin_call(vec![
ctor(BuiltinCtor::ResultErr, vec![impure()]),
int_lit(0),
]);
let folded = const_fold(program_with_builtin("Result.withDefault", body));
assert!(
matches!(body_of(&folded), MirExpr::Call(_)),
"impure Err payload must keep the runtime withDefault call, got {:?}",
body_of(&folded)
);
}
#[test]
fn fold_b_match_wildcard_keeps_impure_subject_arg() {
let body = MirExpr::Match(span(MirMatch {
subject: Box::new(ctor(BuiltinCtor::ResultOk, vec![impure()])),
arms: vec![MirMatchArm {
pattern: MirPattern::Wildcard,
body: int_lit(42),
}],
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Match(_)),
"impure ctor arg under a Wildcard arm must keep the match, got {:?}",
body_of(&folded)
);
}
#[test]
fn fold_b_match_ctor_discard_field_keeps_impure_arg() {
let ok_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk),
bindings: vec![LocalId(WILDCARD_SLOT)],
binding_names: vec![String::new()],
},
body: int_lit(42),
};
let err_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultErr),
bindings: vec![LocalId(WILDCARD_SLOT)],
binding_names: vec![String::new()],
},
body: int_lit(0),
};
let body = MirExpr::Match(span(MirMatch {
subject: Box::new(ctor(BuiltinCtor::ResultOk, vec![impure()])),
arms: vec![ok_arm, err_arm],
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Match(_)),
"impure arg at a `_` field must keep the match, got {:?}",
body_of(&folded)
);
}
#[test]
fn fold_b_match_ctor_discard_field_pure_folds_without_sentinel_let() {
let ok_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk),
bindings: vec![LocalId(WILDCARD_SLOT)],
binding_names: vec![String::new()],
},
body: int_lit(42),
};
let err_arm = MirMatchArm {
pattern: MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultErr),
bindings: vec![LocalId(WILDCARD_SLOT)],
binding_names: vec![String::new()],
},
body: int_lit(0),
};
let body = MirExpr::Match(span(MirMatch {
subject: Box::new(ctor(BuiltinCtor::ResultOk, vec![int_lit(7)])),
arms: vec![ok_arm, err_arm],
}));
let folded = const_fold(one_fn_program(body));
assert!(
matches!(body_of(&folded), MirExpr::Literal(s) if matches!(s.node, Literal::Int(42))),
"pure `_`-field fold must collapse to the bare body with no sentinel Let, got {:?}",
body_of(&folded)
);
}
}