use crate::ast::{BinOp, Literal, Spanned};
use crate::ir::mir::program::LocalId;
use crate::ir::mir::{
BareI64Facts, FnBareFacts, MirCallee, MirExpr, MirFn, MirFnRepr, MirProgram, MirStrPart,
};
pub fn rewrite_for_rust(
program: MirProgram,
boxed_signature_fns: &std::collections::HashSet<crate::ir::FnId>,
_carrier: &super::bare_i64::CarrierIntervals,
) -> MirProgram {
let empty = super::bare_i64::CarrierIntervals::new();
let empty_fields = super::bare_i64::FieldCarrierIntervals::new();
rewrite(program, boxed_signature_fns, &empty, &empty_fields)
}
pub fn rewrite_for_wasm_gc(
program: MirProgram,
boxed_signature_fns: &std::collections::HashSet<crate::ir::FnId>,
carrier: &super::bare_i64::CarrierIntervals,
field_carrier: &super::bare_i64::FieldCarrierIntervals,
) -> MirProgram {
rewrite(program, boxed_signature_fns, carrier, field_carrier)
}
fn rewrite(
program: MirProgram,
boxed_signature_fns: &std::collections::HashSet<crate::ir::FnId>,
carrier: &super::bare_i64::CarrierIntervals,
field_carrier: &super::bare_i64::FieldCarrierIntervals,
) -> MirProgram {
let mut program = program;
let facts = super::bare_i64::analyze_with_fields(&program, carrier, field_carrier);
let ids: Vec<crate::ir::FnId> = program.fns.keys().copied().collect();
for id in ids {
let fn_facts = if boxed_signature_fns.contains(&id) {
FnBareFacts {
carrier_types: carrier.clone(),
field_carrier_intervals: field_carrier.clone(),
..FnBareFacts::default()
}
} else {
let Some(f) = facts.for_fn(id).cloned() else {
continue;
};
f
};
if let Some(f) = program.fns.get_mut(&id) {
rewrite_fn(f, &fn_facts, &facts, boxed_signature_fns);
}
}
program
}
pub fn mutual_recursion_box_set(
program: &MirProgram,
) -> std::collections::HashSet<crate::ir::FnId> {
use std::collections::HashMap;
let nodes: Vec<crate::ir::FnId> = program.fns.keys().copied().collect();
let mut graph: HashMap<crate::ir::FnId, Vec<crate::ir::FnId>> = HashMap::new();
for (caller, f) in program.iter() {
let mut callees: Vec<crate::ir::FnId> = Vec::new();
collect_callees(&f.body.node, *caller, &mut callees);
graph.insert(*caller, callees);
}
crate::scc::mutually_recursive(&nodes, &graph)
}
fn collect_callees(e: &MirExpr, caller: crate::ir::FnId, out: &mut Vec<crate::ir::FnId>) {
match e {
MirExpr::Call(c) => {
if let MirCallee::Fn(t) = c.node.callee
&& t != caller
{
out.push(t);
}
for a in &c.node.args {
collect_callees(&a.node, caller, out);
}
}
MirExpr::TailCall(tc) => {
if tc.node.target != caller {
out.push(tc.node.target);
}
for a in &tc.node.args {
collect_callees(&a.node, caller, out);
}
}
_ => super::bare_i64::visit_children(e, &mut |c| collect_callees(c, caller, out)),
}
}
fn rewrite_fn(
f: &mut MirFn,
facts: &FnBareFacts,
all: &BareI64Facts,
boxed_signature_fns: &std::collections::HashSet<crate::ir::FnId>,
) {
let mut bare_slots = std::collections::HashSet::new();
for (slot, fact) in &facts.values {
if fact.is_bare() {
bare_slots.insert(*slot);
}
}
let carrier_slots: std::collections::HashSet<LocalId> =
facts.carrier_slots.keys().copied().collect();
f.repr = MirFnRepr {
bare_slots,
bare_params: facts.bare_params.clone(),
bare_return: facts.bare_return,
carrier_slots,
};
let ctx = RewriteCtx {
facts,
all,
boxed_signature_fns,
};
let body = std::mem::replace(
&mut f.body,
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
f.body = rewrite_tail(body, facts.bare_return, &ctx);
}
struct RewriteCtx<'a> {
facts: &'a FnBareFacts,
all: &'a BareI64Facts,
boxed_signature_fns: &'a std::collections::HashSet<crate::ir::FnId>,
}
impl RewriteCtx<'_> {
fn callee_facts(&self, target: crate::ir::FnId) -> Option<&FnBareFacts> {
if self.boxed_signature_fns.contains(&target) {
return None;
}
self.all.for_fn(target)
}
}
fn box_node(e: Spanned<MirExpr>) -> Spanned<MirExpr> {
let line = e.line;
let ty = e.ty().cloned();
let out = Spanned::new(MirExpr::Box(std::boxed::Box::new(e)), line);
if let Some(t) = ty {
out.set_ty(t);
}
out
}
fn unbox_node(e: Spanned<MirExpr>) -> Spanned<MirExpr> {
let line = e.line;
let ty = e.ty().cloned();
let out = Spanned::new(MirExpr::Unbox(std::boxed::Box::new(e)), line);
if let Some(t) = ty {
out.set_ty(t);
}
out
}
fn renders_raw(e: &MirExpr, facts: &FnBareFacts) -> bool {
facts.expr_is_bare_i64(e)
}
fn rewrite_boxed(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
if matches!(&e.node, MirExpr::Literal(l) if matches!(l.node, Literal::Int(_))) {
return rewrite_children(e, ctx);
}
if call_returns_raw(&e.node, ctx) {
let inner = rewrite_children(e, ctx);
return box_node(inner);
}
if renders_raw(&e.node, ctx.facts) {
return box_node(e);
}
if matches!(&e.node, MirExpr::Match(_) | MirExpr::IfThenElse(_)) {
return rewrite_boxed_branches(e, ctx);
}
rewrite_children(e, ctx)
}
fn rewrite_boxed_tail(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
if matches!(&e.node, MirExpr::Literal(l) if matches!(l.node, Literal::Int(_))) {
return rewrite_children(e, ctx);
}
if call_returns_raw(&e.node, ctx) {
let inner = rewrite_children(e, ctx);
return box_node(inner);
}
if renders_raw(&e.node, ctx.facts) {
return box_node(e);
}
if matches!(&e.node, MirExpr::Match(_) | MirExpr::IfThenElse(_)) {
return rewrite_boxed_branches(e, ctx);
}
rewrite_children(e, ctx)
}
fn rewrite_boxed_branches(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
let line = e.line;
let ty = e.ty().cloned();
let rebuilt = match e.node {
MirExpr::Match(mut m) => {
let subj = take_box(&mut m.node.subject);
m.node.subject = std::boxed::Box::new(rewrite_match_subject(subj, &m.node.arms, ctx));
for arm in &mut m.node.arms {
let body = std::mem::replace(
&mut arm.body,
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
arm.body = rewrite_boxed_tail(body, ctx);
}
Spanned::new(MirExpr::Match(m), line)
}
MirExpr::IfThenElse(mut ite) => {
let cond = take_box(&mut ite.node.cond);
ite.node.cond = std::boxed::Box::new(rewrite_value(cond, ctx));
let then_b = take_box(&mut ite.node.then_branch);
ite.node.then_branch = std::boxed::Box::new(rewrite_boxed_tail(then_b, ctx));
let else_b = take_box(&mut ite.node.else_branch);
ite.node.else_branch = std::boxed::Box::new(rewrite_boxed_tail(else_b, ctx));
Spanned::new(MirExpr::IfThenElse(ite), line)
}
other => rewrite_children(Spanned::new(other, line), ctx),
};
if let Some(t) = ty {
rebuilt.set_ty(t);
}
rebuilt
}
fn rewrite_interp_embed(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
rewrite_boxed_tail(e, ctx)
}
fn rewrite_raw(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
if renders_raw(&e.node, ctx.facts) {
return rewrite_children(e, ctx);
}
let inner = rewrite_children(e, ctx);
unbox_node(inner)
}
fn call_returns_raw(e: &MirExpr, ctx: &RewriteCtx<'_>) -> bool {
let target = match e {
MirExpr::Call(c) => match c.node.callee {
MirCallee::Fn(t) => t,
_ => return false,
},
MirExpr::TailCall(tc) => tc.node.target,
_ => return false,
};
ctx.callee_facts(target).is_some_and(|f| f.bare_return)
}
fn rewrite_match_subject(
subj: Spanned<MirExpr>,
arms: &[crate::ir::mir::MirMatchArm],
ctx: &RewriteCtx<'_>,
) -> Spanned<MirExpr> {
use crate::ir::mir::MirPattern;
let bind_slots: Vec<LocalId> = arms
.iter()
.filter_map(|arm| match &arm.pattern {
MirPattern::Bind(slot, _) => Some(*slot),
_ => None,
})
.collect();
if bind_slots.is_empty() {
return rewrite_no_bind_subject(subj, ctx);
}
let all_bare = bind_slots.iter().all(|s| ctx.facts.is_bare(*s));
if all_bare {
rewrite_raw(subj, ctx)
} else {
rewrite_boxed(subj, ctx)
}
}
fn rewrite_no_bind_subject(subj: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
if renders_raw(&subj.node, ctx.facts) {
rewrite_raw(subj, ctx)
} else {
rewrite_boxed(subj, ctx)
}
}
fn rewrite_tail(e: Spanned<MirExpr>, bare_return: bool, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
let line = e.line;
let ty = e.ty().cloned();
let out = rewrite_tail_inner(e.node, line, ty.clone(), bare_return, ctx);
if let Some(t) = ty {
out.set_ty(t);
}
out
}
fn rewrite_tail_inner(
node: MirExpr,
line: usize,
ty: Option<crate::ast::Type>,
bare_return: bool,
ctx: &RewriteCtx<'_>,
) -> Spanned<MirExpr> {
match node {
MirExpr::Match(mut m) => {
let subj = std::mem::replace(
&mut m.node.subject,
std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Unit,
)))),
);
m.node.subject = std::boxed::Box::new(rewrite_match_subject(*subj, &m.node.arms, ctx));
for arm in &mut m.node.arms {
let body = std::mem::replace(
&mut arm.body,
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
arm.body = rewrite_tail(body, bare_return, ctx);
}
Spanned::new(MirExpr::Match(m), line)
}
MirExpr::IfThenElse(mut ite) => {
let cond = std::mem::replace(
&mut ite.node.cond,
std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Bool(false),
)))),
);
ite.node.cond = std::boxed::Box::new(rewrite_value(*cond, ctx));
let then_b = std::mem::replace(
&mut ite.node.then_branch,
std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Unit,
)))),
);
ite.node.then_branch = std::boxed::Box::new(rewrite_tail(*then_b, bare_return, ctx));
let else_b = std::mem::replace(
&mut ite.node.else_branch,
std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Unit,
)))),
);
ite.node.else_branch = std::boxed::Box::new(rewrite_tail(*else_b, bare_return, ctx));
Spanned::new(MirExpr::IfThenElse(ite), line)
}
MirExpr::Let(mut l) => {
let value = std::mem::replace(
&mut l.node.value,
std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Unit,
)))),
);
l.node.value = std::boxed::Box::new(rewrite_let_value(*value, l.node.binding, ctx));
let body = std::mem::replace(
&mut l.node.body,
std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Unit,
)))),
);
l.node.body = std::boxed::Box::new(rewrite_tail(*body, bare_return, ctx));
Spanned::new(MirExpr::Let(l), line)
}
MirExpr::Return(inner) => {
let r = rewrite_tail(*inner, bare_return, ctx);
Spanned::new(MirExpr::Return(std::boxed::Box::new(r)), line)
}
MirExpr::TailCall(mut tc) => {
let target = tc.node.target;
let callee = MirCallee::Fn(target);
let args = std::mem::take(&mut tc.node.args);
tc.node.args = rewrite_call_args(args, &callee, ctx);
Spanned::new(MirExpr::TailCall(tc), line)
}
other => {
let spanned = Spanned::new(other, line);
if let Some(t) = ty {
spanned.set_ty(t);
}
if bare_return {
rewrite_raw(spanned, ctx)
} else {
rewrite_boxed_tail(spanned, ctx)
}
}
}
}
fn rewrite_let_value(
e: Spanned<MirExpr>,
binding: LocalId,
ctx: &RewriteCtx<'_>,
) -> Spanned<MirExpr> {
if ctx.facts.is_bare(binding) {
rewrite_raw(e, ctx)
} else {
rewrite_boxed(e, ctx)
}
}
fn rewrite_value(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
rewrite_children(e, ctx)
}
fn rewrite_children(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
let ty = e.ty().cloned();
let out = rewrite_children_inner(e, ctx);
if let Some(t) = ty {
out.set_ty(t);
}
out
}
fn rewrite_children_inner(e: Spanned<MirExpr>, ctx: &RewriteCtx<'_>) -> Spanned<MirExpr> {
let line = e.line;
match e.node {
MirExpr::BinOp(mut b) => {
let op = b.node.op;
let int_arith = matches!(op, BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div);
let whole_raw =
int_arith && renders_raw(&MirExpr::BinOp(clone_binop_shell(&b)), ctx.facts);
let lhs = take_box(&mut b.node.lhs);
let rhs = take_box(&mut b.node.rhs);
if whole_raw {
b.node.lhs = std::boxed::Box::new(rewrite_raw(lhs, ctx));
b.node.rhs = std::boxed::Box::new(rewrite_raw(rhs, ctx));
return Spanned::new(MirExpr::BinOp(b), line);
}
if int_arith
&& (super::bare_i64::type_is_int(operand_ty(&lhs))
|| super::bare_i64::type_is_int(operand_ty(&rhs)))
{
b.node.lhs = std::boxed::Box::new(rewrite_boxed(lhs, ctx));
b.node.rhs = std::boxed::Box::new(rewrite_boxed(rhs, ctx));
return Spanned::new(MirExpr::BinOp(b), line);
}
if matches!(
op,
BinOp::Eq | BinOp::Neq | BinOp::Lt | BinOp::Gt | BinOp::Lte | BinOp::Gte
) {
let both_raw =
renders_raw(&lhs.node, ctx.facts) && renders_raw(&rhs.node, ctx.facts);
if both_raw {
b.node.lhs = std::boxed::Box::new(rewrite_raw(lhs, ctx));
b.node.rhs = std::boxed::Box::new(rewrite_raw(rhs, ctx));
} else {
b.node.lhs = std::boxed::Box::new(rewrite_boxed(lhs, ctx));
b.node.rhs = std::boxed::Box::new(rewrite_boxed(rhs, ctx));
}
return Spanned::new(MirExpr::BinOp(b), line);
}
b.node.lhs = std::boxed::Box::new(rewrite_value(lhs, ctx));
b.node.rhs = std::boxed::Box::new(rewrite_value(rhs, ctx));
Spanned::new(MirExpr::BinOp(b), line)
}
MirExpr::Neg(inner) => {
let r = rewrite_boxed(*inner, ctx);
Spanned::new(MirExpr::Neg(std::boxed::Box::new(r)), line)
}
MirExpr::Call(mut c) => {
let callee = c.node.callee.clone();
let args = std::mem::take(&mut c.node.args);
c.node.args = rewrite_call_args(args, &callee, ctx);
Spanned::new(MirExpr::Call(c), line)
}
MirExpr::TailCall(mut tc) => {
let target = tc.node.target;
let callee = MirCallee::Fn(target);
let args = std::mem::take(&mut tc.node.args);
tc.node.args = rewrite_call_args(args, &callee, ctx);
Spanned::new(MirExpr::TailCall(tc), line)
}
MirExpr::List(items) => Spanned::new(MirExpr::List(rewrite_boxed_each(items, ctx)), line),
MirExpr::Tuple(items) => Spanned::new(MirExpr::Tuple(rewrite_boxed_each(items, ctx)), line),
MirExpr::MapLiteral(pairs) => {
let pairs = pairs
.into_iter()
.map(|(k, v)| (rewrite_boxed(k, ctx), rewrite_boxed(v, ctx)))
.collect();
Spanned::new(MirExpr::MapLiteral(pairs), line)
}
MirExpr::Construct(mut c) => {
let args = std::mem::take(&mut c.node.args);
c.node.args = rewrite_boxed_each(args, ctx);
Spanned::new(MirExpr::Construct(c), line)
}
MirExpr::RecordCreate(mut r) => {
for fld in &mut r.node.fields {
let v = std::mem::replace(
&mut fld.value,
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
fld.value = rewrite_boxed(v, ctx);
}
Spanned::new(MirExpr::RecordCreate(r), line)
}
MirExpr::RecordUpdate(mut u) => {
let base = take_box(&mut u.node.base);
u.node.base = std::boxed::Box::new(rewrite_value(base, ctx));
for fld in &mut u.node.updates {
let v = std::mem::replace(
&mut fld.value,
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
fld.value = rewrite_boxed(v, ctx);
}
Spanned::new(MirExpr::RecordUpdate(u), line)
}
MirExpr::InterpolatedStr(parts) => {
let parts = parts
.into_iter()
.map(|p| match p {
MirStrPart::Expr(ex) => MirStrPart::Expr(rewrite_interp_embed(ex, ctx)),
MirStrPart::Literal(s) => MirStrPart::Literal(s),
})
.collect();
Spanned::new(MirExpr::InterpolatedStr(parts), line)
}
MirExpr::IndependentProduct(mut ip) => {
let items = std::mem::take(&mut ip.node.items);
ip.node.items = rewrite_boxed_each(items, ctx);
Spanned::new(MirExpr::IndependentProduct(ip), line)
}
MirExpr::Project(mut p) => {
let base = take_box(&mut p.node.base);
p.node.base = std::boxed::Box::new(rewrite_value(base, ctx));
Spanned::new(MirExpr::Project(p), line)
}
MirExpr::Try(inner) => {
let r = rewrite_value(*inner, ctx);
Spanned::new(MirExpr::Try(std::boxed::Box::new(r)), line)
}
MirExpr::Return(inner) => {
let r = rewrite_value(*inner, ctx);
Spanned::new(MirExpr::Return(std::boxed::Box::new(r)), line)
}
MirExpr::Let(mut l) => {
let value = take_box(&mut l.node.value);
l.node.value = std::boxed::Box::new(rewrite_let_value(value, l.node.binding, ctx));
let body = take_box(&mut l.node.body);
l.node.body = std::boxed::Box::new(rewrite_value(body, ctx));
Spanned::new(MirExpr::Let(l), line)
}
MirExpr::Match(mut m) => {
let subj = take_box(&mut m.node.subject);
m.node.subject = std::boxed::Box::new(rewrite_match_subject(subj, &m.node.arms, ctx));
for arm in &mut m.node.arms {
let body = std::mem::replace(
&mut arm.body,
Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Unit))),
);
arm.body = rewrite_value(body, ctx);
}
Spanned::new(MirExpr::Match(m), line)
}
MirExpr::IfThenElse(mut ite) => {
let cond = take_box(&mut ite.node.cond);
ite.node.cond = std::boxed::Box::new(rewrite_value(cond, ctx));
let then_b = take_box(&mut ite.node.then_branch);
ite.node.then_branch = std::boxed::Box::new(rewrite_value(then_b, ctx));
let else_b = take_box(&mut ite.node.else_branch);
ite.node.else_branch = std::boxed::Box::new(rewrite_value(else_b, ctx));
Spanned::new(MirExpr::IfThenElse(ite), line)
}
node @ (MirExpr::Literal(_)
| MirExpr::Local(_)
| MirExpr::FnValue(_)
| MirExpr::Box(_)
| MirExpr::Unbox(_)) => Spanned::new(node, line),
}
}
fn rewrite_call_args(
args: Vec<Spanned<MirExpr>>,
callee: &MirCallee,
ctx: &RewriteCtx<'_>,
) -> Vec<Spanned<MirExpr>> {
let callee_facts = match callee {
MirCallee::Fn(t) => ctx.callee_facts(*t),
_ => None,
};
args.into_iter()
.enumerate()
.map(|(i, a)| {
if callee_facts.is_some_and(|f| f.param_is_bare(i)) {
rewrite_raw(a, ctx)
} else {
rewrite_boxed(a, ctx)
}
})
.collect()
}
fn rewrite_boxed_each(items: Vec<Spanned<MirExpr>>, ctx: &RewriteCtx<'_>) -> Vec<Spanned<MirExpr>> {
items.into_iter().map(|it| rewrite_boxed(it, ctx)).collect()
}
fn take_box(slot: &mut std::boxed::Box<Spanned<MirExpr>>) -> Spanned<MirExpr> {
let placeholder = std::boxed::Box::new(Spanned::bare(MirExpr::Literal(Spanned::bare(
Literal::Unit,
))));
*std::mem::replace(slot, placeholder)
}
fn operand_ty(e: &Spanned<MirExpr>) -> Option<&crate::ast::Type> {
e.ty()
}
fn clone_binop_shell(b: &Spanned<crate::ir::mir::MirBinOp>) -> Spanned<crate::ir::mir::MirBinOp> {
Spanned::new(
crate::ir::mir::MirBinOp {
op: b.node.op,
lhs: b.node.lhs.clone(),
rhs: b.node.rhs.clone(),
},
b.line,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::mir::{lower_program, optimize};
use crate::source::parse_source;
fn rewritten(src: &str) -> MirProgram {
let mut items = parse_source(src).expect("parse");
let cfg = crate::ir::pipeline::PipelineConfig {
typecheck: Some(crate::ir::pipeline::TypecheckMode::Full { base_dir: None }),
..Default::default()
};
let result = crate::ir::pipeline::run(&mut items, cfg);
assert!(
result
.typecheck
.as_ref()
.is_none_or(|t| t.errors.is_empty()),
"typecheck errors: {:?}",
result.typecheck.map(|t| t.errors)
);
let mir_items = result.resolved_items.clone();
let program = optimize(lower_program(&mir_items));
rewrite_for_rust(
program,
&std::collections::HashSet::new(),
&super::super::bare_i64::CarrierIntervals::new(),
)
}
fn fn_named<'a>(program: &'a MirProgram, name: &str) -> &'a MirFn {
program
.iter()
.find(|(_, f)| f.name == name)
.map(|(_, f)| f)
.unwrap_or_else(|| panic!("fn `{name}` not in program"))
}
fn count_boundaries(e: &MirExpr) -> (usize, usize) {
let mut boxes = 0;
let mut unboxes = 0;
count_rec(e, &mut boxes, &mut unboxes);
(boxes, unboxes)
}
fn count_rec(e: &MirExpr, boxes: &mut usize, unboxes: &mut usize) {
match e {
MirExpr::Box(_) => *boxes += 1,
MirExpr::Unbox(_) => *unboxes += 1,
_ => {}
}
super::super::bare_i64::tests_visit_children(e, &mut |c| count_rec(c, boxes, unboxes));
}
#[test]
fn countdown_repr_is_explicit_and_no_spurious_boundary() {
let src = r#"
module Countdown
intent = "t"
depends []
fn countdown(n: Int) -> Int
match n
0 -> 0
_ -> countdown(n - 1)
fn main() -> Int
countdown(20000)
"#;
let program = rewritten(src);
let f = fn_named(&program, "countdown");
assert!(f.repr.param_is_bare(0), "counter param tagged bare on MIR");
assert!(f.repr.bare_return, "bare return tagged on MIR");
let (boxes, unboxes) = count_boundaries(&f.body.node);
assert_eq!(
(boxes, unboxes),
(0, 0),
"an all-bare countdown needs no boundary node"
);
}
#[test]
fn factorial_boxes_bare_operand_at_mul() {
let src = r#"
module Factorial
intent = "t"
depends []
fn factorial(n: Int, acc: Int) -> Int
match n
0 -> acc
_ -> factorial(n - 1, acc * n)
fn main() -> Int
factorial(10, 1)
"#;
let program = rewritten(src);
let f = fn_named(&program, "factorial");
assert!(f.repr.param_is_bare(0), "n is bare on MIR");
assert!(!f.repr.param_is_bare(1), "acc stays boxed on MIR");
let (boxes, unboxes) = count_boundaries(&f.body.node);
assert_eq!(boxes, 1, "exactly the `Box(n)` at `acc * n`");
assert_eq!(unboxes, 0, "no Unbox needed");
}
#[test]
fn q5_bare_return_into_boxed_return_boxes_at_crossing() {
let src = r#"
module M
intent = "t"
depends []
fn g(n: Int) -> Int
match n
0 -> 0
_ -> g(n - 1)
fn h() -> Int
g(2)
fn main() -> Int
h()
"#;
let program = rewritten(src);
let g = fn_named(&program, "g");
assert!(g.repr.bare_return, "g keeps its bare return");
let h = fn_named(&program, "h");
assert!(!h.repr.bare_return, "h's return stays boxed");
let (boxes, _unboxes) = count_boundaries(&h.body.node);
assert!(boxes >= 1, "h boxes the bare-returning call result");
}
#[test]
fn bare_return_call_into_string_from_int_arg_boxes_at_sink() {
let src = r#"
module M
intent = "t"
depends []
fn g(n: Int) -> Int
match n
0 -> 0
_ -> g(n - 1)
fn s() -> String
String.fromInt(g(2))
fn main() -> String
s()
"#;
let program = rewritten(src);
let g = fn_named(&program, "g");
assert!(
g.repr.bare_return,
"g's recurrence stays native (bare return preserved)"
);
let s = fn_named(&program, "s");
let (boxes, _unboxes) = count_boundaries(&s.body.node);
assert!(
boxes >= 1,
"the bare-returning g(2) feeding String.fromInt's $AverInt arg is boxed at the sink"
);
}
#[test]
fn bare_return_call_as_no_bind_match_subject_boxes_at_subject() {
let src = r#"
module M
intent = "t"
depends []
fn g(n: Int) -> Int
match n
0 -> 0
_ -> g(n - 1)
fn r() -> Int
match g(2)
0 -> 1
_ -> 2
fn main() -> Int
r()
"#;
let program = rewritten(src);
let g = fn_named(&program, "g");
assert!(
g.repr.bare_return,
"g's recurrence stays native (bare return preserved)"
);
let r = fn_named(&program, "r");
let (boxes, _unboxes) = count_boundaries(&r.body.node);
assert!(
boxes >= 1,
"the bare-returning g(2) used as a no-bind $AverInt match subject is boxed"
);
}
#[test]
fn bare_local_no_bind_match_subject_stays_raw() {
let src = r#"
module M
intent = "t"
depends []
fn countdown(n: Int) -> Int
match n
0 -> 0
_ -> countdown(n - 1)
fn main() -> Int
countdown(20000)
"#;
let program = rewritten(src);
let f = fn_named(&program, "countdown");
assert!(f.repr.param_is_bare(0), "counter param is bare");
let (boxes, unboxes) = count_boundaries(&f.body.node);
assert_eq!(
(boxes, unboxes),
(0, 0),
"a bare-counter no-bind match subject stays raw — no boundary node"
);
}
}