use std::collections::{HashMap, HashSet};
use crate::ast::{BinOp, Literal, Spanned, Type};
use crate::ir::FnId;
use crate::ir::interval::{Interval, OpClass, raw_i64_eligible};
use super::super::expr::{MirCallee, MirExpr, MirPattern};
use super::super::program::{LocalId, MirFn, MirProgram};
pub type CarrierIntervals = HashMap<String, (Interval, bool)>;
pub type FieldCarrierIntervals = HashMap<(String, String), (Interval, bool)>;
const CARRIER_BARE_ELIGIBLE: bool = true;
fn carrier_interval(ty: &str, carrier: &CarrierIntervals) -> Option<Interval> {
if !CARRIER_BARE_ELIGIBLE {
return None;
}
let bare = bare_named_type(ty)?;
let (iv, known) = carrier.get(bare).copied()?;
if known && iv.fits_i64() {
Some(iv)
} else {
None
}
}
fn bare_named_type(ty: &str) -> Option<&str> {
let rest = ty.strip_prefix("Named {")?;
let after = rest.split("name:").nth(1)?;
let start = after.find('"')? + 1;
let end = after[start..].find('"')? + start;
Some(&after[start..end])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Repr {
Bare,
Boxed,
}
#[derive(Debug, Clone)]
pub struct ValueFact {
pub interval: Option<Interval>,
pub op_class: OpClass,
pub escapes: bool,
pub repr: Repr,
}
impl ValueFact {
fn boxed() -> Self {
Self {
interval: None,
op_class: OpClass::Unbounded,
escapes: true,
repr: Repr::Boxed,
}
}
pub fn is_bare(&self) -> bool {
self.repr == Repr::Bare
}
}
#[derive(Debug, Clone, Default)]
pub struct FnBareFacts {
pub values: HashMap<LocalId, ValueFact>,
pub bare_params: Vec<bool>,
pub bare_return: bool,
pub carrier_slots: HashMap<LocalId, Interval>,
pub carrier_types: CarrierIntervals,
pub field_carrier_intervals: FieldCarrierIntervals,
}
impl FnBareFacts {
pub fn is_bare(&self, slot: LocalId) -> bool {
self.values.get(&slot).is_some_and(ValueFact::is_bare)
}
pub fn param_is_bare(&self, i: usize) -> bool {
self.bare_params.get(i).copied().unwrap_or(false)
}
fn bare_slot_interval(&self, slot: LocalId) -> Option<Interval> {
let fact = self.values.get(&slot)?;
if !fact.is_bare() {
return None;
}
fact.interval
}
pub fn carrier_project_interval(&self, e: &MirExpr) -> Option<Interval> {
let MirExpr::Project(p) = e else {
return None;
};
if let MirExpr::Local(local) = &p.node.base.node
&& let Some(iv) = self.carrier_slots.get(&local.node.slot).copied()
{
return Some(iv);
}
if let Some(iv) = self.field_carrier_field_interval(&p.node.base, &p.node.field) {
return Some(iv);
}
self.base_renders_eligible_carrier(&p.node.base)
}
fn field_carrier_field_interval(
&self,
base: &Spanned<MirExpr>,
field: &str,
) -> Option<Interval> {
let name = base.ty().and_then(Type::named_name)?;
let bare = name.rsplit_once('.').map_or(name, |(_, b)| b);
let (iv, known) = self
.field_carrier_intervals
.get(&(bare.to_string(), field.to_string()))
.copied()?;
(known && iv.fits_i64()).then_some(iv)
}
fn base_renders_eligible_carrier(&self, base: &Spanned<MirExpr>) -> Option<Interval> {
if !matches!(base.node, MirExpr::Project(_)) {
return None;
}
let name = base.ty().and_then(Type::named_name)?;
let bare = name.rsplit_once('.').map_or(name, |(_, b)| b);
let (iv, known) = self.carrier_types.get(bare).copied()?;
(known && iv.fits_i64()).then_some(iv)
}
pub fn is_carrier_project(&self, e: &MirExpr) -> bool {
self.carrier_project_interval(e).is_some()
}
pub fn bare_expr_interval(&self, e: &MirExpr) -> Option<Interval> {
match e {
MirExpr::Literal(l) => match l.node {
Literal::Int(k) => Some(Interval::point(k as i128)),
_ => None,
},
MirExpr::Local(local) => self.bare_slot_interval(local.node.slot),
MirExpr::Project(_) => self.carrier_project_interval(e),
MirExpr::Neg(inner) => {
let r = Interval::point(0).sub(self.bare_expr_interval(&inner.node)?);
r.fits_i64().then_some(r)
}
MirExpr::BinOp(b) => {
let l = self.bare_expr_interval(&b.node.lhs.node)?;
let r = self.bare_expr_interval(&b.node.rhs.node)?;
let result = match b.node.op {
BinOp::Add => l.add(r),
BinOp::Sub => l.sub(r),
BinOp::Mul => l.mul(r),
_ => return None,
};
result.fits_i64().then_some(result)
}
_ => None,
}
}
pub fn expr_is_bare_i64(&self, e: &MirExpr) -> bool {
match e {
MirExpr::Literal(l) => matches!(l.node, Literal::Int(_)),
MirExpr::Local(local) => self.is_bare(local.node.slot),
MirExpr::Project(_) => self.is_carrier_project(e),
MirExpr::Neg(_) | MirExpr::BinOp(_) => self.bare_expr_interval(e).is_some_and(|iv| {
raw_i64_eligible(Some(iv), std::iter::once(&OpClass::OverflowFree))
}),
_ => false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BareI64Facts {
fns: HashMap<FnId, FnBareFacts>,
}
impl BareI64Facts {
pub fn for_fn(&self, id: FnId) -> Option<&FnBareFacts> {
self.fns.get(&id)
}
pub fn bare_values(&self) -> usize {
self.fns
.values()
.flat_map(|f| f.values.values())
.filter(|v| v.is_bare())
.count()
}
}
pub fn analyze(program: &MirProgram, carrier: &CarrierIntervals) -> BareI64Facts {
analyze_with_fields(program, carrier, &FieldCarrierIntervals::new())
}
pub fn analyze_with_fields(
program: &MirProgram,
carrier: &CarrierIntervals,
field_carrier: &FieldCarrierIntervals,
) -> BareI64Facts {
if std::env::var("AVER_NO_BARE_I64").is_ok() {
return BareI64Facts::default();
}
if program.external_callers_possible || program.modules.len() > 1 {
return BareI64Facts::default();
}
let mut int_params: HashMap<FnId, Vec<bool>> = HashMap::new();
for (id, f) in program.iter() {
int_params.insert(*id, f.params.iter().map(|p| ty_str_is_int(&p.ty)).collect());
}
let summary = compute_summary(program, &int_params, carrier, field_carrier);
let mut fns: HashMap<FnId, FnBareFacts> = HashMap::new();
for (id, f) in program.iter() {
fns.insert(*id, analyze_fn(f, &summary, carrier, field_carrier));
}
BareI64Facts { fns }
}
struct Summary {
bare_params: HashMap<FnId, Vec<bool>>,
bare_return: HashMap<FnId, bool>,
bare_param_intervals: HashMap<FnId, Vec<Option<Interval>>>,
}
impl Summary {
fn param_bare(&self, id: FnId, i: usize) -> bool {
self.bare_params
.get(&id)
.and_then(|v| v.get(i).copied())
.unwrap_or(false)
}
fn return_bare(&self, id: FnId) -> bool {
self.bare_return.get(&id).copied().unwrap_or(false)
}
fn param_interval(&self, id: FnId, i: usize) -> Option<Interval> {
self.bare_param_intervals
.get(&id)
.and_then(|v| v.get(i).copied())
.flatten()
}
}
fn compute_summary(
program: &MirProgram,
int_params: &HashMap<FnId, Vec<bool>>,
carrier: &CarrierIntervals,
field_carrier: &FieldCarrierIntervals,
) -> Summary {
let mut address_taken: HashSet<String> = HashSet::new();
for (_, f) in program.iter() {
collect_fn_values(&f.body.node, &mut address_taken);
}
let mut bare_params: HashMap<FnId, Vec<bool>> = HashMap::new();
let mut bare_return: HashMap<FnId, bool> = HashMap::new();
for (id, f) in program.iter() {
let externally_reachable = f.name == "main" || address_taken.contains(&f.name);
let ip = &int_params[id];
let seed: Vec<bool> = ip.iter().map(|&i| i && !externally_reachable).collect();
bare_params.insert(*id, seed);
bare_return.insert(*id, ty_str_is_int(&f.return_type) && !externally_reachable);
}
let mut edges: Vec<CallEdge> = Vec::new();
for (caller, f) in program.iter() {
collect_call_edges(*caller, &f.body.node, &mut edges);
}
let mut has_ordinary_caller: HashSet<FnId> = HashSet::new();
for edge in &edges {
if !edge.is_tail_self() {
has_ordinary_caller.insert(edge.target);
}
}
for (id, br) in bare_return.iter_mut() {
if !has_ordinary_caller.contains(id) {
*br = false;
}
}
let bare_param_intervals = compute_bare_param_intervals(program, &edges, &address_taken);
loop {
let mut changed = false;
for edge in &edges {
let Some(callee_params) = bare_params.get(&edge.target).cloned() else {
continue;
};
for (i, arg) in edge.args.iter().enumerate() {
if !callee_params.get(i).copied().unwrap_or(false) {
continue; }
let ok =
arg_supplies_bare(&arg.node, edge.caller, program, &bare_params, &bare_return);
if !ok {
demote_param(&mut bare_params, edge.target, i, &mut changed);
}
}
}
for (id, _f) in program.iter() {
let ip = &int_params[id];
for (i, &is_int) in ip.iter().enumerate() {
if !bare_params[id].get(i).copied().unwrap_or(false) {
continue;
}
if !is_int {
demote_param(&mut bare_params, *id, i, &mut changed);
continue;
}
let bound = bare_param_intervals
.get(id)
.and_then(|v| v.get(i).copied())
.flatten();
let eligible = bound.is_some_and(|iv| {
raw_i64_eligible(Some(iv), std::iter::once(&OpClass::OverflowFree))
});
if !eligible {
demote_param(&mut bare_params, *id, i, &mut changed);
}
}
}
let tmp = Summary {
bare_params: bare_params.clone(),
bare_return: bare_return.clone(),
bare_param_intervals: bare_param_intervals.clone(),
};
for (id, f) in program.iter() {
let mut escaping: HashSet<LocalId> = HashSet::new();
scan_escapes(&f.body.node, &tmp, &mut escaping);
for (i, p) in f.params.iter().enumerate() {
if bare_params[id].get(i).copied().unwrap_or(false) && escaping.contains(&p.local) {
demote_param(&mut bare_params, *id, i, &mut changed);
}
}
}
for (id, f) in program.iter() {
if !bare_return.get(id).copied().unwrap_or(false) {
continue;
}
let tmp = Summary {
bare_params: bare_params.clone(),
bare_return: bare_return.clone(),
bare_param_intervals: bare_param_intervals.clone(),
};
let facts = analyze_fn(f, &tmp, carrier, field_carrier);
if !facts.bare_return && bare_return.insert(*id, false) != Some(false) {
changed = true;
}
}
let unsafe_returns = collect_unsafe_return_consumers(program, &bare_params);
for id in &unsafe_returns {
if bare_return.get(id).copied().unwrap_or(false)
&& bare_return.insert(*id, false) != Some(false)
{
changed = true;
}
}
let tmp = Summary {
bare_params: bare_params.clone(),
bare_return: bare_return.clone(),
bare_param_intervals: bare_param_intervals.clone(),
};
for (_caller, f) in program.iter() {
let facts = analyze_fn(f, &tmp, carrier, field_carrier);
let mut demote: Vec<FnId> = Vec::new();
collect_let_bound_boxed_returns(&f.body.node, &facts, &bare_return, &mut demote);
for target in demote {
if bare_return.get(&target).copied().unwrap_or(false)
&& bare_return.insert(target, false) != Some(false)
{
changed = true;
}
}
}
if !changed {
break;
}
}
Summary {
bare_params,
bare_return,
bare_param_intervals,
}
}
fn collect_let_bound_boxed_returns(
e: &MirExpr,
facts: &FnBareFacts,
bare_return: &HashMap<FnId, bool>,
out: &mut Vec<FnId>,
) {
if let MirExpr::Let(l) = e
&& let MirExpr::Call(c) = &l.node.value.node
&& let MirCallee::Fn(target) = c.node.callee
&& bare_return.get(&target).copied().unwrap_or(false)
&& !facts.is_bare(l.node.binding)
{
out.push(target);
}
visit_children(e, &mut |c| {
collect_let_bound_boxed_returns(c, facts, bare_return, out)
});
}
fn collect_unsafe_return_consumers(
program: &MirProgram,
bare_params: &HashMap<FnId, Vec<bool>>,
) -> HashSet<FnId> {
let mut unsafe_set: HashSet<FnId> = HashSet::new();
for (_, f) in program.iter() {
scan_return_consumers(&f.body.node, program, bare_params, &mut unsafe_set);
}
unsafe_set
}
fn scan_return_consumers(
e: &MirExpr,
program: &MirProgram,
bare_params: &HashMap<FnId, Vec<bool>>,
unsafe_set: &mut HashSet<FnId>,
) {
let flag_if_call = |child: &MirExpr, unsafe_set: &mut HashSet<FnId>| {
if let MirExpr::Call(c) = child
&& let MirCallee::Fn(t) = c.node.callee
{
unsafe_set.insert(t);
}
};
match e {
MirExpr::BinOp(b) => {
flag_if_call(&b.node.lhs.node, unsafe_set);
flag_if_call(&b.node.rhs.node, unsafe_set);
}
MirExpr::Neg(inner) => flag_if_call(&inner.node, unsafe_set),
MirExpr::List(items) | MirExpr::Tuple(items) => {
for it in items {
flag_if_call(&it.node, unsafe_set);
}
}
MirExpr::MapLiteral(pairs) => {
for (k, v) in pairs {
flag_if_call(&k.node, unsafe_set);
flag_if_call(&v.node, unsafe_set);
}
}
MirExpr::Construct(c) => {
for a in &c.node.args {
flag_if_call(&a.node, unsafe_set);
}
}
MirExpr::RecordCreate(r) => {
for fld in &r.node.fields {
flag_if_call(&fld.value.node, unsafe_set);
}
}
MirExpr::RecordUpdate(u) => {
flag_if_call(&u.node.base.node, unsafe_set);
for fld in &u.node.updates {
flag_if_call(&fld.value.node, unsafe_set);
}
}
MirExpr::Call(c) => match c.node.callee {
MirCallee::Fn(target) => {
for (i, a) in c.node.args.iter().enumerate() {
let bare_param = bare_params
.get(&target)
.and_then(|v| v.get(i).copied())
.unwrap_or(false);
if !bare_param {
flag_if_call(&a.node, unsafe_set);
}
}
}
MirCallee::Builtin(bid) => {
let name = program.builtin_name(bid);
if name != "String.fromInt" {
for a in &c.node.args {
flag_if_call(&a.node, unsafe_set);
}
}
}
MirCallee::Intrinsic(_) | MirCallee::LocalSlot { .. } => {
for a in &c.node.args {
flag_if_call(&a.node, unsafe_set);
}
}
},
MirExpr::TailCall(tc) => {
for (i, a) in tc.node.args.iter().enumerate() {
let bare_param = bare_params
.get(&tc.node.target)
.and_then(|v| v.get(i).copied())
.unwrap_or(false);
if !bare_param {
flag_if_call(&a.node, unsafe_set);
}
}
}
_ => {}
}
visit_children(e, &mut |c| {
scan_return_consumers(c, program, bare_params, unsafe_set)
});
}
fn demote_param(
bare_params: &mut HashMap<FnId, Vec<bool>>,
id: FnId,
i: usize,
changed: &mut bool,
) {
if let Some(v) = bare_params.get_mut(&id)
&& let Some(slot) = v.get_mut(i)
&& *slot
{
*slot = false;
*changed = true;
}
}
struct CallEdge {
target: FnId,
caller: FnId,
args: Vec<Spanned<MirExpr>>,
tail_self: bool,
}
impl CallEdge {
fn is_tail_self(&self) -> bool {
self.tail_self
}
}
fn collect_call_edges(caller: FnId, e: &MirExpr, out: &mut Vec<CallEdge>) {
match e {
MirExpr::Call(c) => {
if let MirCallee::Fn(target) = c.node.callee {
out.push(CallEdge {
target,
caller,
args: c.node.args.clone(),
tail_self: false,
});
}
}
MirExpr::TailCall(tc) => {
out.push(CallEdge {
target: tc.node.target,
caller,
args: tc.node.args.clone(),
tail_self: tc.node.target == caller,
});
}
_ => {}
}
visit_children(e, &mut |c| collect_call_edges(caller, c, out));
}
fn arg_supplies_bare(
arg: &MirExpr,
caller: FnId,
program: &MirProgram,
bare_params: &HashMap<FnId, Vec<bool>>,
bare_return: &HashMap<FnId, bool>,
) -> bool {
match arg {
MirExpr::Literal(l) => matches!(l.node, Literal::Int(_)),
MirExpr::Neg(inner) => {
arg_supplies_bare(&inner.node, caller, program, bare_params, bare_return)
}
MirExpr::Local(local) => local_supplies_bare(local.node.slot, caller, program, bare_params),
MirExpr::BinOp(b) if matches!(b.node.op, BinOp::Add | BinOp::Sub | BinOp::Mul) => {
arg_supplies_bare(&b.node.lhs.node, caller, program, bare_params, bare_return)
&& arg_supplies_bare(&b.node.rhs.node, caller, program, bare_params, bare_return)
}
MirExpr::Call(c) => match c.node.callee {
MirCallee::Fn(target) => bare_return.get(&target).copied().unwrap_or(false),
_ => false,
},
_ => false,
}
}
fn local_supplies_bare(
slot: LocalId,
caller: FnId,
program: &MirProgram,
bare_params: &HashMap<FnId, Vec<bool>>,
) -> bool {
let Some(f) = program.fn_by_id(caller) else {
return false;
};
for (i, p) in f.params.iter().enumerate() {
if p.local == slot {
return bare_params
.get(&caller)
.and_then(|v| v.get(i).copied())
.unwrap_or(false);
}
}
false
}
const UNROLL: usize = 2;
fn compute_bare_param_intervals(
program: &MirProgram,
edges: &[CallEdge],
address_taken: &HashSet<String>,
) -> HashMap<FnId, Vec<Option<Interval>>> {
let mut state: HashMap<FnId, Vec<Interval>> = HashMap::new();
let mut pinned: HashMap<FnId, Vec<bool>> = HashMap::new();
for (id, f) in program.iter() {
let externally_reachable = f.name == "main" || address_taken.contains(&f.name);
state.insert(*id, vec![Interval::unbounded(); f.params.len()]);
pinned.insert(*id, vec![externally_reachable; f.params.len()]);
}
let mut floors: HashMap<FnId, Vec<Interval>> = HashMap::new();
let mut seeds: HashMap<FnId, Vec<Interval>> = HashMap::new();
for (id, f) in program.iter() {
let mut fv = Vec::with_capacity(f.params.len());
let mut sv = Vec::with_capacity(f.params.len());
for i in 0..f.params.len() {
fv.push(guard_floor(f, i, edges));
sv.push(seed_interval(f, i, edges));
}
floors.insert(*id, fv);
seeds.insert(*id, sv);
}
for (id, f) in program.iter() {
for i in 0..f.params.len() {
if !pinned[id][i] {
state.get_mut(id).unwrap()[i] = seeds[id][i];
}
}
}
let nodes: Vec<FnId> = program.iter().map(|(id, _)| *id).collect();
let mut graph: HashMap<FnId, Vec<FnId>> = HashMap::new();
for edge in edges {
graph.entry(edge.caller).or_default().push(edge.target);
}
for scc in crate::scc::tarjan_sccs::<FnId>(&nodes, &graph) {
solve_scc(program, edges, &scc, &seeds, &floors, &pinned, &mut state);
}
let top = Interval::unbounded();
let mut out: HashMap<FnId, Vec<Option<Interval>>> = HashMap::new();
for (id, f) in program.iter() {
let cells = &state[id];
let fl = &floors[id];
let ivs: Vec<Option<Interval>> = (0..f.params.len())
.map(|i| {
let iv = cells[i];
if fl[i] != top && iv.fits_i64() {
Some(iv)
} else {
None
}
})
.collect();
out.insert(*id, ivs);
}
out
}
#[cfg(test)]
fn compute_param_intervals_for_test(program: &MirProgram) -> HashMap<FnId, Vec<Option<Interval>>> {
let mut address_taken: HashSet<String> = HashSet::new();
for (_, f) in program.iter() {
collect_fn_values(&f.body.node, &mut address_taken);
}
let mut edges: Vec<CallEdge> = Vec::new();
for (caller, f) in program.iter() {
collect_call_edges(*caller, &f.body.node, &mut edges);
}
compute_bare_param_intervals(program, &edges, &address_taken)
}
fn solve_scc(
program: &MirProgram,
edges: &[CallEdge],
scc: &[FnId],
seeds: &HashMap<FnId, Vec<Interval>>,
floors: &HashMap<FnId, Vec<Interval>>,
pinned: &HashMap<FnId, Vec<bool>>,
state: &mut HashMap<FnId, Vec<Interval>>,
) {
let mut round = 0usize;
loop {
let mut changed = false;
for &fid in scc {
let Some(f) = program.fn_by_id(fid) else {
continue;
};
for i in 0..f.params.len() {
if pinned[&fid][i] {
continue; }
let floor = floors[&fid][i];
let seed = seeds[&fid][i];
let mut back: Option<Interval> = None;
for edge in edges {
if edge.target != fid || !edge.is_tail_self() {
continue;
}
let Some(arg) = edge.args.get(i) else {
continue;
};
let env = caller_env(f, &state[&fid], floors[&fid].as_slice());
let iv = eval_interval_pub(&arg.node, &env);
back = Some(match back {
Some(prev) => prev.hull(iv),
None => iv,
});
}
let pre = match back {
Some(b) => seed.hull(b),
None => seed,
};
let incoming = pre.intersect(floor);
let joined = state[&fid][i].hull(incoming);
let next = if round < UNROLL {
joined
} else {
state[&fid][i].widen(joined)
};
if next != state[&fid][i] {
state.get_mut(&fid).unwrap()[i] = next;
changed = true;
}
}
}
round += 1;
if !changed {
break;
}
}
}
fn caller_env(f: &MirFn, cells: &[Interval], floors: &[Interval]) -> HashMap<LocalId, Interval> {
let top = Interval::unbounded();
let mut env: HashMap<LocalId, Interval> = HashMap::new();
for (i, p) in f.params.iter().enumerate() {
let iv = if floors[i] == top {
cells[i]
} else {
floors[i]
};
env.insert(p.local, iv);
}
env
}
fn eval_interval_pub(e: &MirExpr, env: &HashMap<LocalId, Interval>) -> Interval {
let mut worst = Interval::point(0);
let no_carriers = FnBareFacts::default();
let iv = eval_interval(e, env, &mut worst, &no_carriers);
worst.hull(iv)
}
fn seed_interval(f: &MirFn, i: usize, edges: &[CallEdge]) -> Interval {
let mut entry: Option<Interval> = None;
let mut saw_entry = false;
for edge in edges {
if edge.target != f.fn_id || edge.is_tail_self() {
continue;
}
saw_entry = true;
let Some(arg) = edge.args.get(i) else {
return Interval::unbounded();
};
let Some(iv) = literal_interval(&arg.node) else {
return Interval::unbounded();
};
entry = Some(match entry {
Some(prev) => prev.hull(iv),
None => iv,
});
}
match (saw_entry, entry) {
(true, Some(iv)) => iv,
_ => Interval::unbounded(),
}
}
fn guard_floor(f: &MirFn, i: usize, edges: &[CallEdge]) -> Interval {
let unfloored = Interval::unbounded();
let Some(param_slot) = f.params.get(i).map(|p| p.local) else {
return unfloored;
};
let Some((guard_k, guard_kind)) = guard_literal_for(&f.body.node, param_slot) else {
return unfloored;
};
if guard_kind != GuardKind::Equality {
return unfloored; }
if !guard_dominates_recursion(&f.body.node, f.fn_id, param_slot, guard_k) {
return unfloored;
}
if equality_guard_arm_recurses(&f.body.node, f.fn_id, param_slot, guard_k) {
return unfloored;
}
let mut step: Option<i128> = None;
let mut saw_self_tail = false;
let all_same_decrement = walk_self_tailcall_steps(
&f.body.node,
f.fn_id,
param_slot,
i,
&mut step,
&mut saw_self_tail,
);
if !saw_self_tail || !all_same_decrement {
return unfloored;
}
let Some(step) = step else {
return unfloored;
};
let mut entry: Option<Interval> = None;
let mut saw_entry = false;
for edge in edges {
if edge.target != f.fn_id || edge.is_tail_self() {
continue;
}
saw_entry = true;
let Some(arg) = edge.args.get(i) else {
return unfloored;
};
let Some(iv) = literal_interval(&arg.node) else {
return unfloored;
};
let v = match iv.lo {
crate::ir::interval::Bound::Finite(v) => v,
_ => return unfloored,
};
if v < guard_k || (v - guard_k) % step != 0 {
return unfloored;
}
entry = Some(match entry {
Some(prev) => prev.hull(iv),
None => iv,
});
}
let entry = match (saw_entry, entry) {
(true, Some(iv)) => iv,
_ => return unfloored, };
let lo = entry.lo.min(Interval::point(guard_k - step).lo);
let hi = entry.hi.max(Interval::point(guard_k).hi);
Interval { lo, hi }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GuardKind {
Equality,
Comparison,
}
fn guard_literal_for(e: &MirExpr, counter: LocalId) -> Option<(i128, GuardKind)> {
match e {
MirExpr::Match(m) => {
if subject_is_local(&m.node.subject.node, counter) {
for arm in &m.node.arms {
if let MirPattern::Literal(Literal::Int(k)) = &arm.pattern {
return Some((*k as i128, GuardKind::Equality));
}
}
}
guard_literal_for(&m.node.subject.node, counter).or_else(|| {
m.node
.arms
.iter()
.find_map(|arm| guard_literal_for(&arm.body.node, counter))
})
}
MirExpr::IfThenElse(ite) => {
if let MirExpr::BinOp(b) = &ite.node.cond.node
&& matches!(
b.node.op,
BinOp::Eq | BinOp::Lt | BinOp::Lte | BinOp::Gt | BinOp::Gte
)
{
let kind = if matches!(b.node.op, BinOp::Eq) {
GuardKind::Equality
} else {
GuardKind::Comparison
};
if subject_is_local(&b.node.lhs.node, counter)
&& let MirExpr::Literal(l) = &b.node.rhs.node
&& let Literal::Int(k) = l.node
{
return Some((k as i128, kind));
}
if subject_is_local(&b.node.rhs.node, counter)
&& let MirExpr::Literal(l) = &b.node.lhs.node
&& let Literal::Int(k) = l.node
{
return Some((k as i128, kind));
}
}
guard_literal_for(&ite.node.cond.node, counter)
.or_else(|| guard_literal_for(&ite.node.then_branch.node, counter))
.or_else(|| guard_literal_for(&ite.node.else_branch.node, counter))
}
MirExpr::Let(l) => guard_literal_for(&l.node.value.node, counter)
.or_else(|| guard_literal_for(&l.node.body.node, counter)),
_ => {
let mut found = None;
visit_children(e, &mut |c| {
if found.is_none() {
found = guard_literal_for(c, counter);
}
});
found
}
}
}
fn walk_self_tailcall_steps(
e: &MirExpr,
self_fn: FnId,
counter: LocalId,
i: usize,
step: &mut Option<i128>,
saw_self_tail: &mut bool,
) -> bool {
let mut ok = true;
if let MirExpr::TailCall(tc) = e
&& tc.node.target == self_fn
&& let Some(arg) = tc.node.args.get(i)
{
*saw_self_tail = true;
match decrement_step(&arg.node, counter) {
Some(s) => match *step {
None => *step = Some(s),
Some(prev) if prev != s => ok = false,
Some(_) => {}
},
None => ok = false,
}
}
visit_children(e, &mut |c| {
if !walk_self_tailcall_steps(c, self_fn, counter, i, step, saw_self_tail) {
ok = false;
}
});
ok
}
fn equality_guard_arm_recurses(
e: &MirExpr,
self_fn: FnId,
counter: LocalId,
guard_k: i128,
) -> bool {
if let MirExpr::Match(m) = e
&& subject_is_local(&m.node.subject.node, counter)
{
for arm in &m.node.arms {
if let MirPattern::Literal(Literal::Int(k)) = &arm.pattern
&& *k as i128 == guard_k
&& contains_self_tailcall(&arm.body.node, self_fn)
{
return true;
}
}
}
let mut found = false;
visit_children(e, &mut |c| {
if !found {
found = equality_guard_arm_recurses(c, self_fn, counter, guard_k);
}
});
found
}
fn contains_self_tailcall(e: &MirExpr, self_fn: FnId) -> bool {
if let MirExpr::TailCall(tc) = e
&& tc.node.target == self_fn
{
return true;
}
let mut found = false;
visit_children(e, &mut |c| {
if !found {
found = contains_self_tailcall(c, self_fn);
}
});
found
}
fn guard_dominates_recursion(e: &MirExpr, self_fn: FnId, counter: LocalId, guard_k: i128) -> bool {
if let MirExpr::Match(m) = e
&& subject_is_local(&m.node.subject.node, counter)
{
let mut has_k_arm = false;
let mut has_sibling_recursion = false;
for arm in &m.node.arms {
let is_k = matches!(
&arm.pattern,
MirPattern::Literal(Literal::Int(k)) if *k as i128 == guard_k
);
if is_k {
has_k_arm = true;
} else if contains_self_tailcall(&arm.body.node, self_fn) {
has_sibling_recursion = true;
}
}
if has_k_arm && has_sibling_recursion {
return true;
}
}
if let MirExpr::IfThenElse(ite) = e
&& let MirExpr::BinOp(b) = &ite.node.cond.node
&& matches!(b.node.op, BinOp::Eq)
&& cond_compares_counter_to_k(&b.node.lhs.node, &b.node.rhs.node, counter, guard_k)
&& contains_self_tailcall(&ite.node.else_branch.node, self_fn)
&& !contains_self_tailcall(&ite.node.then_branch.node, self_fn)
{
return true;
}
let mut found = false;
visit_children(e, &mut |c| {
if !found {
found = guard_dominates_recursion(c, self_fn, counter, guard_k);
}
});
found
}
fn cond_compares_counter_to_k(
lhs: &MirExpr,
rhs: &MirExpr,
counter: LocalId,
guard_k: i128,
) -> bool {
fn is_k_lit(e: &MirExpr, guard_k: i128) -> bool {
if let MirExpr::Literal(l) = e
&& let Literal::Int(k) = l.node
{
return k as i128 == guard_k;
}
false
}
(subject_is_local(lhs, counter) && is_k_lit(rhs, guard_k))
|| (subject_is_local(rhs, counter) && is_k_lit(lhs, guard_k))
}
fn decrement_step(e: &MirExpr, counter: LocalId) -> Option<i128> {
if let MirExpr::BinOp(b) = e
&& matches!(b.node.op, BinOp::Sub)
&& subject_is_local(&b.node.lhs.node, counter)
&& let MirExpr::Literal(l) = &b.node.rhs.node
&& let Literal::Int(k) = l.node
&& k > 0
{
return Some(k as i128);
}
None
}
fn subject_is_local(e: &MirExpr, slot: LocalId) -> bool {
matches!(e, MirExpr::Local(l) if l.node.slot == slot)
}
fn literal_interval(arg: &MirExpr) -> Option<Interval> {
match arg {
MirExpr::Literal(l) => match l.node {
Literal::Int(k) => Some(Interval::point(k as i128)),
_ => None,
},
MirExpr::Neg(inner) => match &inner.node {
MirExpr::Literal(l) => match l.node {
Literal::Int(k) => Some(Interval::point(-(k as i128))),
_ => None,
},
_ => None,
},
_ => None,
}
}
fn analyze_fn(
f: &MirFn,
summary: &Summary,
carrier: &CarrierIntervals,
field_carrier: &FieldCarrierIntervals,
) -> FnBareFacts {
let mut facts = FnBareFacts::default();
let mut intervals: HashMap<LocalId, Interval> = HashMap::new();
let mut bare_params = vec![false; f.params.len()];
let mut carrier_slots: HashMap<LocalId, Interval> = HashMap::new();
for (i, p) in f.params.iter().enumerate() {
let recurrence_bare = summary.param_bare(f.fn_id, i);
bare_params[i] = recurrence_bare;
if let Some(cv) = carrier_interval(&p.ty, carrier) {
carrier_slots.insert(p.local, cv);
intervals.insert(p.local, Interval::unbounded());
continue;
}
let iv = if recurrence_bare {
summary
.param_interval(f.fn_id, i)
.filter(|iv| iv.fits_i64())
.unwrap_or_else(|| Interval::between(i64::MIN as i128, i64::MAX as i128))
} else {
Interval::unbounded()
};
intervals.insert(p.local, iv);
}
facts.carrier_slots = carrier_slots;
facts.carrier_types = carrier.clone();
facts.field_carrier_intervals = field_carrier.clone();
let mut op_classes: HashMap<LocalId, OpClass> = HashMap::new();
walk_let_chain(&f.body.node, &mut intervals, &mut op_classes, &facts);
let mut escaping: HashSet<LocalId> = HashSet::new();
scan_escapes(&f.body.node, summary, &mut escaping);
for (slot, iv) in &intervals {
let op_class = op_classes
.get(slot)
.copied()
.unwrap_or(OpClass::OverflowFree);
let escapes = escaping.contains(slot);
let eligible = raw_i64_eligible(Some(*iv), std::iter::once(&op_class));
let repr = if eligible && !escapes {
Repr::Bare
} else {
Repr::Boxed
};
facts.values.insert(
*slot,
ValueFact {
interval: Some(*iv),
op_class,
escapes,
repr,
},
);
}
for (i, p) in f.params.iter().enumerate() {
let seeded_bare = summary.param_bare(f.fn_id, i);
facts.values.entry(p.local).or_insert_with(|| {
if seeded_bare {
ValueFact {
interval: intervals.get(&p.local).copied(),
op_class: OpClass::OverflowFree,
escapes: false,
repr: Repr::Bare,
}
} else {
ValueFact::boxed()
}
});
}
facts.bare_params = bare_params;
facts.bare_return =
summary.return_bare(f.fn_id) && tail_value_is_bare(&f.body.node, &facts, &escaping);
facts
}
fn walk_let_chain(
e: &MirExpr,
env: &mut HashMap<LocalId, Interval>,
op_classes: &mut HashMap<LocalId, OpClass>,
facts: &FnBareFacts,
) {
match e {
MirExpr::Let(l) => {
let mut worst = Interval::point(0);
let iv = eval_interval(&l.node.value.node, env, &mut worst, facts);
env.insert(l.node.binding, iv);
op_classes.insert(l.node.binding, OpClass::of_interval(worst.hull(iv)));
walk_let_chain(&l.node.value.node, env, op_classes, facts);
walk_let_chain(&l.node.body.node, env, op_classes, facts);
}
MirExpr::Match(m) => {
let mut worst = Interval::point(0);
let subj_iv = eval_interval(&m.node.subject.node, env, &mut worst, facts);
walk_let_chain(&m.node.subject.node, env, op_classes, facts);
for arm in &m.node.arms {
if let MirPattern::Bind(slot, _) = &arm.pattern {
env.entry(*slot).or_insert(subj_iv);
op_classes
.entry(*slot)
.or_insert_with(|| OpClass::of_interval(subj_iv));
}
walk_let_chain(&arm.body.node, env, op_classes, facts);
}
}
_ => {
visit_children(e, &mut |c| walk_let_chain(c, env, op_classes, facts));
}
}
}
fn eval_interval(
e: &MirExpr,
env: &HashMap<LocalId, Interval>,
worst: &mut Interval,
facts: &FnBareFacts,
) -> Interval {
match e {
MirExpr::Literal(l) => match l.node {
Literal::Int(k) => Interval::point(k as i128),
_ => Interval::unbounded(),
},
MirExpr::Local(local) => env
.get(&local.node.slot)
.copied()
.unwrap_or_else(Interval::unbounded),
MirExpr::Project(_) => facts
.carrier_project_interval(e)
.unwrap_or_else(Interval::unbounded),
MirExpr::Neg(inner) => {
let r = Interval::point(0).sub(eval_interval(&inner.node, env, worst, facts));
*worst = worst.hull(r);
r
}
MirExpr::BinOp(b) => {
let l = eval_interval(&b.node.lhs.node, env, worst, facts);
let r = eval_interval(&b.node.rhs.node, env, worst, facts);
let result = match b.node.op {
BinOp::Add => l.add(r),
BinOp::Sub => l.sub(r),
BinOp::Mul => l.mul(r),
_ => Interval::unbounded(),
};
*worst = worst.hull(result);
result
}
_ => Interval::unbounded(),
}
}
fn scan_escapes(e: &MirExpr, summary: &Summary, out: &mut HashSet<LocalId>) {
match e {
MirExpr::List(items) | MirExpr::Tuple(items) => {
for it in items {
mark_operand_escapes_deep(&it.node, out);
scan_escapes(&it.node, summary, out);
}
}
MirExpr::MapLiteral(pairs) => {
for (k, v) in pairs {
mark_operand_escapes_deep(&k.node, out);
mark_operand_escapes_deep(&v.node, out);
scan_escapes(&k.node, summary, out);
scan_escapes(&v.node, summary, out);
}
}
MirExpr::Construct(c) => {
for a in &c.node.args {
mark_operand_escapes_deep(&a.node, out);
scan_escapes(&a.node, summary, out);
}
}
MirExpr::RecordCreate(r) => {
for fld in &r.node.fields {
mark_operand_escapes_deep(&fld.value.node, out);
scan_escapes(&fld.value.node, summary, out);
}
}
MirExpr::RecordUpdate(u) => {
mark_operand_escapes_deep(&u.node.base.node, out);
scan_escapes(&u.node.base.node, summary, out);
for fld in &u.node.updates {
mark_operand_escapes_deep(&fld.value.node, out);
scan_escapes(&fld.value.node, summary, out);
}
}
MirExpr::IndependentProduct(ip) => {
for it in &ip.node.items {
mark_operand_escapes_deep(&it.node, out);
scan_escapes(&it.node, summary, out);
}
}
MirExpr::InterpolatedStr(parts) => {
for p in parts {
if let super::super::expr::MirStrPart::Expr(ex) = p {
mark_operand_escapes_deep(&ex.node, out);
scan_escapes(&ex.node, summary, out);
}
}
}
MirExpr::Call(c) => match c.node.callee {
MirCallee::Fn(target) => {
for (i, a) in c.node.args.iter().enumerate() {
if !summary.param_bare(target, i) {
mark_operand_escapes(&a.node, out);
}
scan_escapes(&a.node, summary, out);
}
}
_ => {
for a in &c.node.args {
mark_operand_escapes_deep(&a.node, out);
scan_escapes(&a.node, summary, out);
}
}
},
MirExpr::TailCall(tc) => {
for (i, a) in tc.node.args.iter().enumerate() {
if !summary.param_bare(tc.node.target, i) {
mark_operand_escapes(&a.node, out);
}
scan_escapes(&a.node, summary, out);
}
}
_ => {
visit_children(e, &mut |c| scan_escapes(c, summary, out));
}
}
}
fn mark_operand_escapes(e: &MirExpr, out: &mut HashSet<LocalId>) {
if let MirExpr::Local(l) = e {
out.insert(l.node.slot);
}
}
fn mark_operand_escapes_deep(e: &MirExpr, out: &mut HashSet<LocalId>) {
match e {
MirExpr::Local(l) => {
out.insert(l.node.slot);
}
MirExpr::Neg(inner) => mark_operand_escapes_deep(&inner.node, out),
MirExpr::BinOp(b) => {
mark_operand_escapes_deep(&b.node.lhs.node, out);
mark_operand_escapes_deep(&b.node.rhs.node, out);
}
_ => {}
}
}
fn tail_value_is_bare(e: &MirExpr, facts: &FnBareFacts, escaping: &HashSet<LocalId>) -> bool {
match e {
MirExpr::Local(l) => facts.is_bare(l.node.slot) && !escaping.contains(&l.node.slot),
MirExpr::Literal(l) => matches!(l.node, Literal::Int(_)),
MirExpr::Let(let_node) => tail_value_is_bare(&let_node.node.body.node, facts, escaping),
MirExpr::Match(m) => m
.node
.arms
.iter()
.all(|arm| tail_value_is_bare(&arm.body.node, facts, escaping)),
MirExpr::IfThenElse(ite) => {
tail_value_is_bare(&ite.node.then_branch.node, facts, escaping)
&& tail_value_is_bare(&ite.node.else_branch.node, facts, escaping)
}
MirExpr::TailCall(_) => true,
MirExpr::Return(inner) => tail_value_is_bare(&inner.node, facts, escaping),
MirExpr::BinOp(b) if matches!(b.node.op, BinOp::Add | BinOp::Sub | BinOp::Mul) => {
facts.expr_is_bare_i64(e)
}
MirExpr::Project(_) => facts.is_carrier_project(e),
_ => false,
}
}
fn collect_fn_values(e: &MirExpr, out: &mut HashSet<String>) {
if let MirExpr::FnValue(name) = e {
out.insert(name.clone());
}
visit_children(e, &mut |c| collect_fn_values(c, out));
}
fn ty_str_is_int(ty: &str) -> bool {
ty == "Int"
}
pub fn type_is_int(ty: Option<&Type>) -> bool {
matches!(ty, Some(Type::Int))
}
#[cfg(test)]
pub(crate) fn tests_visit_children(e: &MirExpr, f: &mut dyn FnMut(&MirExpr)) {
visit_children(e, f)
}
pub(crate) fn visit_children(e: &MirExpr, f: &mut dyn FnMut(&MirExpr)) {
match e {
MirExpr::Literal(_) | MirExpr::Local(_) | MirExpr::FnValue(_) => {}
MirExpr::Let(l) => {
f(&l.node.value.node);
f(&l.node.body.node);
}
MirExpr::Call(c) => {
for a in &c.node.args {
f(&a.node);
}
}
MirExpr::TailCall(tc) => {
for a in &tc.node.args {
f(&a.node);
}
}
MirExpr::BinOp(b) => {
f(&b.node.lhs.node);
f(&b.node.rhs.node);
}
MirExpr::Neg(inner)
| MirExpr::Try(inner)
| MirExpr::Return(inner)
| MirExpr::Box(inner)
| MirExpr::Unbox(inner) => f(&inner.node),
MirExpr::Match(m) => {
f(&m.node.subject.node);
for arm in &m.node.arms {
f(&arm.body.node);
}
}
MirExpr::Construct(c) => {
for a in &c.node.args {
f(&a.node);
}
}
MirExpr::RecordCreate(r) => {
for field in &r.node.fields {
f(&field.value.node);
}
}
MirExpr::RecordUpdate(u) => {
f(&u.node.base.node);
for field in &u.node.updates {
f(&field.value.node);
}
}
MirExpr::Project(p) => f(&p.node.base.node),
MirExpr::IfThenElse(ite) => {
f(&ite.node.cond.node);
f(&ite.node.then_branch.node);
f(&ite.node.else_branch.node);
}
MirExpr::List(items) | MirExpr::Tuple(items) => {
for i in items {
f(&i.node);
}
}
MirExpr::MapLiteral(pairs) => {
for (k, v) in pairs {
f(&k.node);
f(&v.node);
}
}
MirExpr::InterpolatedStr(parts) => {
for p in parts {
if let super::super::expr::MirStrPart::Expr(e) = p {
f(&e.node);
}
}
}
MirExpr::IndependentProduct(ip) => {
for i in &ip.node.items {
f(&i.node);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::mir::{lower_program, optimize};
use crate::source::parse_source;
fn facts_for(src: &str) -> (MirProgram, BareI64Facts) {
facts_for_with_carrier(src, &CarrierIntervals::new())
}
fn facts_for_with_carrier(src: &str, carrier: &CarrierIntervals) -> (MirProgram, BareI64Facts) {
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: Vec<crate::ir::hir::ResolvedTopLevel> = result.resolved_items.clone();
let program = optimize(lower_program(&mir_items));
let facts = analyze(&program, carrier);
(program, facts)
}
fn carrier_table_for(
items: &[crate::ast::TopLevel],
symbols: &crate::ir::SymbolTable,
) -> CarrierIntervals {
let empty_prefixes: HashSet<String> = HashSet::new();
let empty_recursive: HashSet<crate::ir::FnId> = HashSet::new();
let inputs = crate::codegen::proof_lower::ProofLowerInputs {
entry_items: items,
dep_modules: &[],
module_prefixes: &empty_prefixes,
recursive_fns: &empty_recursive,
symbol_table: symbols,
program_shape: None,
};
crate::codegen::proof_lower::carrier_interval_table(&inputs)
}
fn fn_id_by_name<'a>(program: &'a MirProgram, name: &str) -> crate::ir::FnId {
program
.iter()
.find(|(_, f)| f.name == name)
.map(|(id, _)| *id)
.unwrap_or_else(|| panic!("fn `{name}` not in program"))
}
const CARRIER_SRC: &str = r#"
module Toy
intent = "t"
depends []
record IntRange
value: Int
fn fromInt(n: Int) -> Result<IntRange, String>
match Bool.and(n >= 0, n <= 100)
true -> Result.Ok(IntRange(value = n))
false -> Result.Err("err")
fn toInt(c: IntRange) -> Int
c.value
fn doubled(c: IntRange) -> Int
c.value + c.value
fn main() -> Int
match fromInt(50)
Result.Ok(c) -> toInt(c) + doubled(c)
Result.Err(_) -> 0
"#;
fn carrier_facts(carrier_on: bool) -> (MirProgram, BareI64Facts) {
let mut items = parse_source(CARRIER_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);
let carrier = if carrier_on {
carrier_table_for(&items, &result.symbol_table)
} else {
CarrierIntervals::new()
};
let mir_items = result.resolved_items.clone();
let program = optimize(lower_program(&mir_items));
let facts = analyze(&program, &carrier);
(program, facts)
}
#[test]
fn bare_named_type_extracts_name_from_debug() {
assert_eq!(
bare_named_type("Named { id: Some(TypeId(0)), name: \"IntRange\" }"),
Some("IntRange")
);
assert_eq!(
bare_named_type("Named { id: None, name: \"Natural\" }"),
Some("Natural")
);
assert_eq!(bare_named_type("Int"), None);
assert_eq!(
bare_named_type("Result(Named { id: None, name: \"X\" }, Str)"),
None
);
}
#[test]
fn carrier_interval_table_derives_proven_bound() {
let mut items = parse_source(CARRIER_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);
let table = carrier_table_for(&items, &result.symbol_table);
let (iv, known) = table.get("IntRange").copied().expect("IntRange in table");
assert!(known, "the [0,100] invariant is recognized");
assert!(iv.fits_i64(), "the carrier bound fits i64");
assert_eq!(iv, Interval::between(0, 100), "exact proven carrier bound");
}
#[test]
fn carrier_seam_enabled_records_carrier_projection_slots() {
assert!(
CARRIER_BARE_ELIGIBLE,
"this test pins the enabled carrier-arithmetic seam"
);
let (program, facts) = carrier_facts(true);
for name in ["toInt", "doubled"] {
let id = fn_id_by_name(&program, name);
let f = facts.for_fn(id).expect("carrier facts");
assert!(
!f.param_is_bare(0),
"`{name}`'s carrier param is NOT an Int-bare param (no spurious \
Unbox at the call site)"
);
let pf = program.fn_by_id(id).expect("mir fn");
let carrier_slot = pf.params[0].local;
assert_eq!(
f.carrier_slots.get(&carrier_slot).copied(),
Some(Interval::between(0, 100)),
"`{name}`'s carrier param is a carrier-projection source with the \
proven [0,100] bound"
);
}
}
#[test]
fn carrier_off_table_records_no_carrier_slots() {
let (program, facts) = carrier_facts(false);
for name in ["toInt", "doubled"] {
let id = fn_id_by_name(&program, name);
let f = facts.for_fn(id).expect("carrier facts");
assert!(
f.carrier_slots.is_empty(),
"with the carrier table empty, `{name}` records no carrier slot"
);
}
}
const NESTED_CARRIER_SRC: &str = r#"
module Toy
intent = "t"
depends []
record IntRange
value: Int
record Holder
c: IntRange
record Wide
value: Int
record WideHolder
c: Wide
fn fromInt(n: Int) -> Result<IntRange, String>
match Bool.and(n >= 0, n <= 100)
true -> Result.Ok(IntRange(value = n))
false -> Result.Err("err")
fn fromWide(n: Int) -> Result<Wide, String>
match Bool.and(n >= 0, n <= 1099511627776)
true -> Result.Ok(Wide(value = n))
false -> Result.Err("err")
fn nestedAdd(h: Holder) -> Int
h.c.value + h.c.value
fn nestedWideMul(h: WideHolder) -> Int
h.c.value * h.c.value
fn main() -> Int
0
"#;
fn nested_carrier_facts(carrier_on: bool) -> (MirProgram, BareI64Facts) {
let mut items = parse_source(NESTED_CARRIER_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 carrier = if carrier_on {
carrier_table_for(&items, &result.symbol_table)
} else {
CarrierIntervals::new()
};
let mir_items = result.resolved_items.clone();
let program = optimize(lower_program(&mir_items));
let facts = analyze(&program, &carrier);
(program, facts)
}
fn find_nested_project_interval(e: &MirExpr, facts: &FnBareFacts) -> Option<Interval> {
if let MirExpr::Project(p) = e
&& matches!(p.node.base.node, MirExpr::Project(_))
&& let Some(iv) = facts.carrier_project_interval(e)
{
return Some(iv);
}
let mut found = None;
visit_children(e, &mut |c| {
if found.is_none() {
found = find_nested_project_interval(c, facts);
}
});
found
}
#[test]
fn nested_carrier_field_in_range_is_bare_leaf() {
let (program, facts) = nested_carrier_facts(true);
let id = fn_id_by_name(&program, "nestedAdd");
let f = facts.for_fn(id).expect("nestedAdd facts");
let pf = program.fn_by_id(id).expect("mir fn");
let iv = find_nested_project_interval(&pf.body.node, f)
.expect("the nested h.c.value read is recognized as a carrier projection");
assert_eq!(
iv,
Interval::between(0, 100),
"the nested-field read carries the carrier's proven [0,100] bound"
);
let sum =
find_carrier_sum(&pf.body.node).expect("the body contains the `c.value + c.value` add");
assert!(
f.expr_is_bare_i64(sum),
"in-range nested-field sum is bare-i64 eligible (OverflowFree)"
);
}
#[test]
fn nested_carrier_field_off_table_declines() {
let (program, facts) = nested_carrier_facts(false);
let id = fn_id_by_name(&program, "nestedAdd");
let f = facts.for_fn(id).expect("nestedAdd facts");
let pf = program.fn_by_id(id).expect("mir fn");
assert!(
find_nested_project_interval(&pf.body.node, f).is_none(),
"with the carrier table empty, a nested-field read is NOT a carrier projection"
);
}
#[test]
fn nested_carrier_field_wide_mul_declines() {
let (program, facts) = nested_carrier_facts(true);
let id = fn_id_by_name(&program, "nestedWideMul");
let f = facts.for_fn(id).expect("nestedWideMul facts");
let pf = program.fn_by_id(id).expect("mir fn");
let iv = find_nested_project_interval(&pf.body.node, f)
.expect("the nested wide read is still a recognized carrier projection");
assert!(iv.fits_i64(), "the [0,2^40] carrier bound itself fits i64");
let mul = find_carrier_sum(&pf.body.node)
.expect("the body contains the `c.value * c.value` multiply");
assert!(
!f.expr_is_bare_i64(mul),
"the wide-bound nested-field multiply overflows i64 and stays boxed"
);
}
fn find_carrier_sum(e: &MirExpr) -> Option<&MirExpr> {
match e {
MirExpr::BinOp(b)
if matches!(b.node.op, BinOp::Add | BinOp::Mul)
&& matches!(b.node.lhs.node, MirExpr::Project(_))
&& matches!(b.node.rhs.node, MirExpr::Project(_)) =>
{
Some(e)
}
MirExpr::Return(inner) | MirExpr::Box(inner) | MirExpr::Unbox(inner) => {
find_carrier_sum(&inner.node)
}
MirExpr::Let(l) => {
find_carrier_sum(&l.node.value.node).or_else(|| find_carrier_sum(&l.node.body.node))
}
MirExpr::Match(m) => m
.node
.arms
.iter()
.find_map(|a| find_carrier_sum(&a.body.node)),
_ => None,
}
}
#[test]
fn countdown_counter_is_bare() {
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, facts) = facts_for(src);
let id = fn_id_by_name(&program, "countdown");
let f = facts.for_fn(id).expect("countdown facts");
assert!(
f.param_is_bare(0),
"the countdown counter must be proven bare i64"
);
}
#[test]
fn factorial_counter_is_bare() {
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, facts) = facts_for(src);
let id = fn_id_by_name(&program, "factorial");
let f = facts.for_fn(id).expect("factorial facts");
assert!(
f.param_is_bare(0),
"the factorial counter `n` must be proven bare i64"
);
}
#[test]
fn unbounded_param_stays_boxed() {
let src = r#"
module M
intent = "t"
depends []
fn down(n: Int) -> Int
match n
0 -> 0
_ -> down(n - 1)
fn caller(x: Int) -> Int
down(x)
fn main() -> Int
caller(5)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "down");
let f = facts.for_fn(id).expect("down facts");
assert!(
!f.param_is_bare(0),
"a counter reached via a non-literal arg must stay boxed (fail-closed)"
);
}
#[test]
fn non_recursive_fn_param_stays_boxed() {
let src = r#"
module M
intent = "t"
depends []
fn twice(n: Int) -> Int
n + n
fn main() -> Int
twice(10)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "twice");
let f = facts.for_fn(id).expect("twice facts");
assert!(
!f.param_is_bare(0),
"a param with no bounding recurrence must stay boxed"
);
}
#[test]
fn counter_stored_in_aggregate_demotes_via_escape() {
let src = r#"
module M
intent = "t"
depends []
fn collect(n: Int) -> List<Int>
match n
0 -> [0]
_ -> [n]
fn main() -> List<Int>
collect(10)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "collect");
let f = facts.for_fn(id).expect("collect facts");
assert!(
!f.param_is_bare(0),
"a counter stored in a List aggregate escapes → must stay boxed"
);
assert!(!f.bare_return, "a List<Int> return is never bare i64");
}
#[test]
fn worst_join_demotes_transient_out_of_i64_intermediate() {
let mut env = HashMap::new();
let n = LocalId(0);
env.insert(n, Interval::between(0, 10));
let big = i64::MAX as i128;
let lit = |k: i128| Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Int(k as i64))));
let local_n = Spanned::bare(MirExpr::Local(Spanned::bare(
super::super::super::expr::MirLocal::at(n),
)));
let add = Spanned::bare(MirExpr::BinOp(Spanned::bare(
super::super::super::expr::MirBinOp {
op: BinOp::Add,
lhs: Box::new(local_n),
rhs: Box::new(lit(big)),
},
)));
let sub = MirExpr::BinOp(Spanned::bare(super::super::super::expr::MirBinOp {
op: BinOp::Sub,
lhs: Box::new(add),
rhs: Box::new(lit(big)),
}));
let mut worst = Interval::point(0);
let no_carriers = FnBareFacts::default();
let result = eval_interval(&sub, &env, &mut worst, &no_carriers);
assert!(result.fits_i64(), "final value cancels back into range");
assert_ne!(
OpClass::of_interval(worst.hull(result)),
OpClass::OverflowFree,
"the transient out-of-i64 intermediate must demote below OverflowFree"
);
}
#[test]
fn congruence_skip_declines() {
let src = r#"
module M
intent = "t"
depends []
fn loopit(n: Int, acc: Int) -> Int
match n
0 -> acc
_ -> loopit(n - 4611686018427387905, acc)
fn main() -> Int
loopit(4, 0)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "loopit");
let f = facts.for_fn(id).expect("loopit facts");
assert!(
!f.param_is_bare(0),
"a counter that steps OVER its equality guard (4 % (2^62+1) != 0) must box"
);
}
#[test]
fn entry_below_guard_declines() {
let src = r#"
module M
intent = "t"
depends []
fn down(n: Int, acc: Int) -> Int
match n
100 -> acc
_ -> down(n - 1, acc)
fn main() -> Int
down(5, 0)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "down");
let f = facts.for_fn(id).expect("down facts");
assert!(
!f.param_is_bare(0),
"entry 5 < guard 100 decrements away from K → diverges → must box"
);
}
#[test]
fn parity_skip_declines() {
let src = r#"
module M
intent = "t"
depends []
fn skip(n: Int, acc: Int) -> Int
match n
0 -> acc
_ -> skip(n - 2, acc)
fn main() -> Int
skip(25, 0)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "skip");
let f = facts.for_fn(id).expect("skip facts");
assert!(
!f.param_is_bare(0),
"odd entry 25 with step 2 toward guard 0 steps over 0 → must box"
);
}
#[test]
fn divisible_reachable_guard_stays_bare() {
let src = r#"
module M
intent = "t"
depends []
fn skip(n: Int, acc: Int) -> Int
match n
0 -> acc
_ -> skip(n - 2, acc)
fn main() -> Int
skip(24, 0)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "skip");
let f = facts.for_fn(id).expect("skip facts");
assert!(
f.param_is_bare(0),
"even entry 24 with step 2 reaches guard 0 → must stay bare (win survives)"
);
}
#[test]
fn overflowing_compound_is_not_bare() {
let mut facts = FnBareFacts::default();
let n = LocalId(0);
facts.values.insert(
n,
ValueFact {
interval: Some(Interval::point(1)),
op_class: OpClass::OverflowFree,
escapes: false,
repr: Repr::Bare,
},
);
let big = i64::MAX as i128;
let local_n = Spanned::bare(MirExpr::Local(Spanned::bare(
super::super::super::expr::MirLocal::at(n),
)));
let lit = Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Int(big as i64))));
let add = MirExpr::BinOp(Spanned::bare(super::super::super::expr::MirBinOp {
op: BinOp::Add,
lhs: Box::new(local_n),
rhs: Box::new(lit),
}));
assert!(
!facts.expr_is_bare_i64(&add),
"`n + i64::MAX` (result [MAX+1, MAX+1]) leaves i64 → must NOT be bare"
);
}
#[test]
fn in_range_compound_stays_bare() {
let mut facts = FnBareFacts::default();
let n = LocalId(0);
facts.values.insert(
n,
ValueFact {
interval: Some(Interval::between(-1, 20000)),
op_class: OpClass::OverflowFree,
escapes: false,
repr: Repr::Bare,
},
);
let local_n = Spanned::bare(MirExpr::Local(Spanned::bare(
super::super::super::expr::MirLocal::at(n),
)));
let lit = Spanned::bare(MirExpr::Literal(Spanned::bare(Literal::Int(1))));
let sub = MirExpr::BinOp(Spanned::bare(super::super::super::expr::MirBinOp {
op: BinOp::Sub,
lhs: Box::new(local_n),
rhs: Box::new(lit),
}));
assert!(
facts.expr_is_bare_i64(&sub),
"`n - 1` over a tight `[-1, 20000]` counter stays in i64 → bare"
);
}
#[test]
fn binop_in_aggregate_escapes() {
let src = r#"
module M
intent = "t"
depends []
fn collect(n: Int) -> List<Int>
match n
1 -> [n + 1]
_ -> collect(n - 1)
fn main() -> List<Int>
collect(2)
"#;
let (program, facts) = facts_for(src);
let id = fn_id_by_name(&program, "collect");
let f = facts.for_fn(id).expect("collect facts");
assert!(
!f.param_is_bare(0),
"a counter reaching `[n + 1]` through a BinOp escapes the aggregate → must box"
);
}
#[test]
fn call_arg_to_boxed_param_keeps_counter_bare() {
let src = r#"
module M
intent = "t"
depends []
fn keep(x: Int) -> Int
x
fn down(n: Int) -> Int
match n
0 -> keep(n + 1)
_ -> down(n - 1)
fn main() -> Int
down(2)
"#;
let (program, facts) = facts_for(src);
let down = facts
.for_fn(fn_id_by_name(&program, "down"))
.expect("down facts");
assert!(
down.param_is_bare(0),
"the down counter stays bare; the boxed-param arg `n + 1` converts at the boundary"
);
let keep = facts
.for_fn(fn_id_by_name(&program, "keep"))
.expect("keep facts");
assert!(
!keep.param_is_bare(0),
"keep's general-Int param stays boxed (no bounding recurrence)"
);
}
#[test]
fn bare_return_consumed_by_boxed_return_fn_stays_bare() {
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, facts) = facts_for(src);
let g = facts.for_fn(fn_id_by_name(&program, "g")).expect("g facts");
assert!(g.param_is_bare(0), "g's bounded counter stays bare");
assert!(
g.bare_return,
"g's return stays bare; the boxed consumer `h` converts at its return boundary"
);
}
#[test]
fn match_binding_alias_is_tracked_bare() {
let src = r#"
module M
intent = "t"
depends []
fn loopit(n: Int) -> Int
match n
0 -> match n
y -> y
_ -> loopit(n - 1)
fn main() -> Int
loopit(3)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "loopit"))
.expect("loopit facts");
assert!(
f.param_is_bare(0),
"the counter stays bare; the aliased binding `y` inherits its bare interval"
);
let bare_y = f.values.values().any(|v| v.is_bare());
assert!(
bare_y,
"at least the counter / its bare alias is proven bare"
);
}
#[test]
fn match_let_alias_into_aggregate_demotes() {
let src = r#"
module M
intent = "t"
depends []
fn loopit(n: Int) -> List<Int>
match n
0 -> match n - 1
x -> [x, x]
_ -> loopit(n - 1)
fn main() -> List<Int>
loopit(4)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "loopit"))
.expect("loopit facts");
assert!(f.param_is_bare(0), "the counter `n` stays bare");
assert!(!f.bare_return, "a List<Int> return is never bare i64");
}
#[test]
fn multi_literal_arm_counter_stays_bare() {
let src = r#"
module M
intent = "t"
depends []
fn loopit(n: Int, acc: Int) -> Int
match n
2 -> acc
0 -> acc
_ -> loopit(n - 1, acc + 1)
fn main() -> Int
loopit(5, 0)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "loopit"))
.expect("loopit facts");
assert!(
f.param_is_bare(0),
"a ≥2-literal-arm bounded counter stays bare (dispatch path emits `== Ki64`)"
);
}
#[test]
fn multi_tailcall_growing_path_demotes_to_boxed() {
let src = r#"
module M
intent = "t"
depends []
fn loopit(n: Int, phase: Int) -> Int
match n
0 -> n + n
_ -> match phase
0 -> n + n
5000 -> loopit(n - 1, phase)
_ -> loopit(n + 1000000000000000000, phase - 1)
fn main() -> Int
loopit(8, 5)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "loopit"))
.expect("loopit facts");
assert!(
!f.param_is_bare(0),
"a counter with a growing second self-tail-call path is unbounded → must box"
);
}
#[test]
fn recursive_base_arm_declines() {
let src = r#"
module M
intent = "t"
depends []
fn loopit(n: Int) -> Int
match n
0 -> loopit(n - 1)
9223372036854775807 -> n + 1
_ -> loopit(n - 1)
fn main() -> Int
loopit(5)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "loopit"))
.expect("loopit facts");
assert!(
!f.param_is_bare(0),
"a counter whose equality-guard base arm self-recurses is unbounded → must box"
);
}
#[test]
fn non_dominating_guard_declines() {
let src = r#"
module M
intent = "t"
depends []
fn bad(n: Int) -> Int
witness = match n
0 -> 0
_ -> 0
match n
9223372036854775807 -> n
_ -> bad(n - 1)
fn main() -> Int
bad(5)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "bad"))
.expect("bad facts");
assert!(
!f.param_is_bare(0),
"a counter whose equality guard does not dominate the recursion is unbounded → must box"
);
}
#[test]
fn comparison_equality_countdown_stays_bare() {
let src = r#"
module M
intent = "t"
depends []
fn down(n: Int) -> Int
match n == 0
true -> 0
false -> down(n - 1)
fn main() -> Int
down(20000)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "down"))
.expect("down facts");
assert!(
f.param_is_bare(0),
"a `match n == 0` countdown's counter dominates and must stay bare (no over-box)"
);
}
fn produced_interval(program: &MirProgram, fn_name: &str, i: usize) -> Option<Interval> {
let id = fn_id_by_name(program, fn_name);
let ivs = compute_param_intervals_for_test(program);
ivs.get(&id).and_then(|v| v.get(i).copied()).flatten()
}
#[test]
fn countdown_interval_is_K_minus_step_to_entry() {
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, _facts) = facts_for(src);
assert_eq!(
produced_interval(&program, "countdown", 0),
Some(Interval::between(-1, 20000)),
"countdown's counter interval must be byte-identical to the old [K-step, entry]"
);
}
#[test]
fn factorial_n_interval_is_K_minus_step_to_entry_and_acc_is_none() {
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, _facts) = facts_for(src);
assert_eq!(
produced_interval(&program, "factorial", 0),
Some(Interval::between(-1, 10)),
"factorial `n` interval must be byte-identical to [K-step, entry] = [-1, 10]"
);
assert_eq!(
produced_interval(&program, "factorial", 1),
None,
"factorial `acc` grows unbounded → boxed (None)"
);
}
#[test]
fn step_two_modular_nonlanding_interval_is_none() {
let src = r#"
module M
intent = "t"
depends []
fn down(n: Int, acc: Int) -> Int
match n
0 -> acc
_ -> down(n - 2, acc)
fn main() -> Int
down(5, 0)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "down"))
.expect("down facts");
assert!(
!f.param_is_bare(0),
"odd entry 5, step 2 toward guard 0 steps over 0 → must box"
);
assert_eq!(
produced_interval(&program, "down", 0),
None,
"the modular-hole counter must be None (the gate withholds the floor)"
);
}
#[test]
fn widen_terminates_unbounded_decrement() {
let src = r#"
module M
intent = "t"
depends []
fn spin(n: Int, k: Int) -> Int
match k
0 -> n
_ -> spin(n - 1, k - 1)
fn caller(k: Int) -> Int
spin(7, k)
fn main() -> Int
caller(3)
"#;
let (program, facts) = facts_for(src);
let f = facts
.for_fn(fn_id_by_name(&program, "spin"))
.expect("spin facts");
assert!(
!f.param_is_bare(0),
"an unguarded decrement counter (guard is on another param) must box"
);
assert_eq!(
produced_interval(&program, "spin", 0),
None,
"the unguarded decrement counter must widen to None, not hang"
);
}
fn field_table_for(
src: &str,
) -> HashMap<(String, String), (crate::ir::interval::Interval, bool)> {
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);
let empty_prefixes: HashSet<String> = HashSet::new();
let empty_recursive: HashSet<crate::ir::FnId> = HashSet::new();
let inputs = crate::codegen::proof_lower::ProofLowerInputs {
entry_items: &items,
dep_modules: &[],
module_prefixes: &empty_prefixes,
recursive_fns: &empty_recursive,
symbol_table: &result.symbol_table,
program_shape: None,
};
crate::codegen::proof_lower::field_carrier_interval_table(&inputs)
}
#[test]
fn field_carrier_per_field_intervals() {
let src = r#"
module Toy
intent = "t"
depends []
record Coord
x: Int
y: Int
fn coord(x: Int, y: Int) -> Result<Coord, String>
match Bool.and(Bool.and(x >= 0, x <= 100), Bool.and(y >= 0, y <= 200))
true -> Result.Ok(Coord(x = x, y = y))
false -> Result.Err("err")
fn main() -> Int
match coord(1, 2)
Result.Ok(c) -> c.x
Result.Err(_) -> 0
"#;
let table = field_table_for(src);
let (ix, kx) = table
.get(&("Coord".to_string(), "x".to_string()))
.copied()
.expect("x field bound");
let (iy, ky) = table
.get(&("Coord".to_string(), "y".to_string()))
.copied()
.expect("y field bound");
assert!(kx && ky, "both fields recognized");
use crate::ir::interval::Bound;
assert_eq!(ix.lo, Bound::Finite(0));
assert_eq!(ix.hi, Bound::Finite(100));
assert_eq!(iy.lo, Bound::Finite(0));
assert_eq!(iy.hi, Bound::Finite(200));
}
#[test]
fn field_carrier_cross_field_condition_dropped() {
let src = r#"
module Toy
intent = "t"
depends []
record Coord
x: Int
y: Int
fn coord(x: Int, y: Int) -> Result<Coord, String>
match Bool.and(Bool.and(x >= 0, x <= 100), Bool.and(y >= 0, x + y <= 50))
true -> Result.Ok(Coord(x = x, y = y))
false -> Result.Err("err")
fn main() -> Int
0
"#;
let table = field_table_for(src);
let (ix, kx) = table
.get(&("Coord".to_string(), "x".to_string()))
.copied()
.expect("x field bound");
assert!(kx);
use crate::ir::interval::Bound;
assert_eq!(ix.lo, Bound::Finite(0));
assert_eq!(ix.hi, Bound::Finite(100));
let y = table.get(&("Coord".to_string(), "y".to_string())).copied();
if let Some((iy, ky)) = y {
assert!(
!(ky && iy.fits_i64()),
"y with only a lower bound must not be a fits_i64 eligible field"
);
}
}
#[test]
fn field_carrier_only_one_field_bounded() {
let src = r#"
module Toy
intent = "t"
depends []
record Coord
x: Int
y: Int
fn coord(x: Int, y: Int) -> Result<Coord, String>
match Bool.and(x >= 0, x <= 100)
true -> Result.Ok(Coord(x = x, y = y))
false -> Result.Err("err")
fn main() -> Int
0
"#;
let table = field_table_for(src);
assert!(
table.contains_key(&("Coord".to_string(), "x".to_string())),
"the gated field x is bounded"
);
let y = table.get(&("Coord".to_string(), "y".to_string())).copied();
assert!(
y.is_none_or(|(iv, k)| !(k && iv.fits_i64())),
"the ungated field y must not be an eligible bounded field"
);
}
#[test]
fn field_carrier_mis_fire_no_smart_ctor() {
let src = r#"
module Toy
intent = "t"
depends []
record Coord
x: Int
y: Int
fn main() -> Int
Coord(x = 1, y = 2).x
"#;
let table = field_table_for(src);
assert!(
!table.contains_key(&("Coord".to_string(), "x".to_string())),
"a record with no smart ctor attributes no field bound"
);
}
}