use std::collections::{HashMap, HashSet};
use crate::ast::{BinOp, Spanned, Type};
use crate::codegen::CodegenContext;
use crate::codegen::common::module_prefix_to_rust_path;
use crate::ir::hir::{
BuiltinCtor, BuiltinIntrinsic, ResolvedCtor, ResolvedMatchArm, ResolvedPattern,
classify_match_dispatch_plan_resolved,
};
use crate::ir::mir::{MirCallee, MirCtor, MirExpr, MirLocal, MirMatch, MirPattern, MirProgram};
use crate::ir::{MatchDispatchPlan, SymbolTable};
use super::emit_ctx::{is_copy_type, should_borrow_param};
use super::expr::{
callee_borrow_mask, constructor_boxed_positions, emit_dispatch_table_match, emit_list_match,
emit_literal, emit_parallel_result_tuple_unwrap, emit_pattern_rebindings,
emit_ref_match_rebindings, emit_result_tuple_unwrap, emit_tuple_from_vars, has_list_patterns,
has_string_literal_patterns,
};
use super::pattern::emit_pattern;
use super::syntax::aver_name_to_rust;
#[derive(Clone, Copy)]
pub struct MirEmitCtx<'a> {
pub symbol_table: &'a SymbolTable,
pub module_prefixes: &'a HashSet<String>,
pub codegen: Option<&'a CodegenContext>,
pub local_types: &'a HashMap<String, Type>,
pub rc_wrapped: &'a HashSet<String>,
pub borrowed_params: &'a HashSet<String>,
pub owned_params: &'a HashSet<String>,
pub current_module_scope: Option<&'a str>,
pub mir_builtins: &'a [String],
}
impl<'a> MirEmitCtx<'a> {
pub fn for_test(symbol_table: &'a SymbolTable, module_prefixes: &'a HashSet<String>) -> Self {
static EMPTY_TYPES: std::sync::OnceLock<HashMap<String, Type>> = std::sync::OnceLock::new();
static EMPTY_SET: std::sync::OnceLock<HashSet<String>> = std::sync::OnceLock::new();
Self {
symbol_table,
module_prefixes,
codegen: None,
local_types: EMPTY_TYPES.get_or_init(HashMap::new),
rc_wrapped: EMPTY_SET.get_or_init(HashSet::new),
borrowed_params: EMPTY_SET.get_or_init(HashSet::new),
owned_params: EMPTY_SET.get_or_init(HashSet::new),
current_module_scope: None,
mir_builtins: &[],
}
}
pub(super) fn program_level(
ctx: &'a CodegenContext,
policy: &'a MirFnEmitPolicy,
mir_builtins: &'a [String],
) -> Self {
Self {
symbol_table: &ctx.symbol_table,
module_prefixes: &ctx.module_prefixes,
codegen: Some(ctx),
local_types: &policy.local_types,
rc_wrapped: &policy.rc_wrapped,
borrowed_params: &policy.borrowed_params,
owned_params: &policy.owned_params,
current_module_scope: policy.current_module_scope.as_deref(),
mir_builtins,
}
}
pub(super) fn for_fn(ctx: &'a CodegenContext, policy: &'a MirFnEmitPolicy) -> Self {
Self {
symbol_table: &ctx.symbol_table,
module_prefixes: &ctx.module_prefixes,
codegen: Some(ctx),
local_types: &policy.local_types,
rc_wrapped: &policy.rc_wrapped,
borrowed_params: &policy.borrowed_params,
owned_params: &policy.owned_params,
current_module_scope: policy.current_module_scope.as_deref(),
mir_builtins: ctx
.mir_program
.as_ref()
.map(|p| p.builtins.as_slice())
.unwrap_or(&[]),
}
}
fn is_copy(&self, name: &str) -> bool {
self.local_types.get(name).is_some_and(is_copy_type)
}
fn is_rc_wrapped(&self, name: &str) -> bool {
self.rc_wrapped.contains(name)
}
fn is_borrowed_param(&self, name: &str) -> bool {
self.borrowed_params.contains(name)
}
}
pub(super) struct MirFnEmitPolicy {
pub local_types: HashMap<String, Type>,
pub rc_wrapped: HashSet<String>,
pub borrowed_params: HashSet<String>,
pub owned_params: HashSet<String>,
pub current_module_scope: Option<String>,
}
impl MirFnEmitPolicy {
pub(super) fn empty() -> Self {
Self {
local_types: HashMap::new(),
rc_wrapped: HashSet::new(),
borrowed_params: HashSet::new(),
owned_params: HashSet::new(),
current_module_scope: None,
}
}
pub(super) fn from_resolved(
resolved: &crate::ir::hir::ResolvedFnDef,
scope: Option<&str>,
borrow_by_default: bool,
) -> Self {
let local_types: HashMap<String, Type> = resolved
.params
.iter()
.map(|(name, ty)| (name.clone(), ty.clone()))
.collect();
let borrowed_params = if borrow_by_default {
local_types
.iter()
.filter(|(_, ty)| should_borrow_param(ty))
.map(|(name, _)| name.clone())
.collect()
} else {
HashSet::new()
};
Self {
local_types,
rc_wrapped: HashSet::new(),
borrowed_params,
owned_params: HashSet::new(),
current_module_scope: scope.map(String::from),
}
}
pub(super) fn apply_own_param(&mut self, mir_fn: &crate::ir::mir::MirFn) {
for (i, param) in mir_fn.params.iter().enumerate() {
let rust_name = aver_name_to_rust(¶m.name);
let Some(ty) = self.local_types.get(&rust_name) else {
continue;
};
if !is_owned_collection_candidate(ty) {
continue;
}
let flagged = mir_fn.aliased_slots.get(i).copied().unwrap_or(true);
if flagged {
continue;
}
self.borrowed_params.remove(&rust_name);
self.owned_params.insert(rust_name);
}
}
}
fn is_owned_collection_candidate(ty: &Type) -> bool {
matches!(ty, Type::Vector(_) | Type::Map(_, _))
}
pub(super) fn owned_collection_param_names(
mir_fn: &crate::ir::mir::MirFn,
param_types: &[(String, Type)],
) -> HashSet<String> {
let mut out = HashSet::new();
for (i, (name, ty)) in param_types.iter().enumerate() {
if !is_owned_collection_candidate(ty) {
continue;
}
let flagged = mir_fn.aliased_slots.get(i).copied().unwrap_or(true);
if flagged {
continue;
}
out.insert(aver_name_to_rust(name));
}
out
}
fn builtin_dotted_record_rename(type_name: &str) -> Option<&'static str> {
match type_name {
"Tcp.Connection" => Some("Tcp_Connection"),
"Terminal.Size" => Some("Terminal_Size"),
_ => None,
}
}
fn resolve_module_call<'a>(
dotted: &'a str,
module_prefixes: &HashSet<String>,
) -> Option<(&'a str, &'a str)> {
let mut best: Option<(&str, &str)> = None;
for (dot_idx, _) in dotted.match_indices('.') {
let prefix = &dotted[..dot_idx];
let suffix = &dotted[dot_idx + 1..];
if module_prefixes.contains(prefix)
&& best.is_none_or(|existing| prefix.len() > existing.0.len())
{
best = Some((prefix, suffix));
}
}
best
}
fn qualify_record_type(
type_id: Option<crate::ir::TypeId>,
type_name: &str,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let entry = match type_id {
Some(id) => ctx.symbol_table.type_entry(id),
None => ctx
.symbol_table
.types
.iter()
.find(|e| e.key.name == type_name)?,
};
let canonical = entry.key.canonical();
let (prefix, suffix) = resolve_module_call(&canonical, ctx.module_prefixes)?;
Some(format!(
"{}::{}",
module_prefix_to_rust_path(prefix),
suffix
))
}
fn mir_record_rust_type(
type_id: Option<crate::ir::TypeId>,
type_name: &str,
ctx: &MirEmitCtx<'_>,
) -> String {
if let Some(renamed) = builtin_dotted_record_rename(type_name) {
return renamed.to_string();
}
if let Some(qualified) = qualify_record_type(type_id, type_name, ctx) {
return qualified;
}
type_name.to_string()
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CoverageReport {
pub total: usize,
pub mir_covered: usize,
pub hir_fallback: usize,
}
impl CoverageReport {
pub fn ratio(&self) -> f64 {
if self.total == 0 {
0.0
} else {
self.mir_covered as f64 / self.total as f64
}
}
}
pub fn coverage_report(program: &MirProgram, emit_ctx: &MirEmitCtx<'_>) -> CoverageReport {
coverage_report_with_blockers(program, emit_ctx).0
}
pub fn coverage_report_with_blockers(
program: &MirProgram,
emit_ctx: &MirEmitCtx<'_>,
) -> (
CoverageReport,
std::collections::BTreeMap<&'static str, usize>,
) {
let mut report = CoverageReport::default();
let mut blockers: std::collections::BTreeMap<&'static str, usize> =
std::collections::BTreeMap::new();
for (_, mir_fn) in program.iter() {
report.total += 1;
if emit_mir_expr(&mir_fn.body, emit_ctx).is_some() {
report.mir_covered += 1;
} else {
report.hir_fallback += 1;
let label = first_blocker(&mir_fn.body, emit_ctx).unwrap_or("Unknown");
*blockers.entry(label).or_insert(0) += 1;
}
}
(report, blockers)
}
fn first_blocker(expr: &Spanned<MirExpr>, emit_ctx: &MirEmitCtx<'_>) -> Option<&'static str> {
if emit_mir_expr(expr, emit_ctx).is_some() {
return None;
}
match &expr.node {
MirExpr::Neg(inner) | MirExpr::Return(inner) | MirExpr::Try(inner) => {
first_blocker(inner, emit_ctx).or(Some(label_for(&expr.node)))
}
MirExpr::BinOp(b) => first_blocker(&b.node.lhs, emit_ctx)
.or_else(|| first_blocker(&b.node.rhs, emit_ctx))
.or(Some("BinOp")),
MirExpr::Call(c) => {
for a in &c.node.args {
if let Some(b) = first_blocker(a, emit_ctx) {
return Some(b);
}
}
match &c.node.callee {
MirCallee::Builtin(_) => Some("Call(Builtin)"),
MirCallee::Intrinsic(_) => Some("Call(Intrinsic)"),
MirCallee::Fn(_) => Some("Call(Fn)"),
MirCallee::LocalSlot { .. } => Some("Call(LocalSlot)"),
}
}
MirExpr::TailCall(tc) => {
for a in &tc.node.args {
if let Some(b) = first_blocker(a, emit_ctx) {
return Some(b);
}
}
Some("TailCall")
}
MirExpr::Tuple(items) | MirExpr::List(items) => {
for item in items {
if let Some(b) = first_blocker(item, emit_ctx) {
return Some(b);
}
}
Some(label_for(&expr.node))
}
MirExpr::MapLiteral(entries) => {
for (k, v) in entries {
if let Some(b) = first_blocker(k, emit_ctx) {
return Some(b);
}
if let Some(b) = first_blocker(v, emit_ctx) {
return Some(b);
}
}
Some("MapLiteral")
}
MirExpr::Let(l) => first_blocker(&l.node.value, emit_ctx)
.or_else(|| first_blocker(&l.node.body, emit_ctx))
.or(Some("Let(synthetic)")),
MirExpr::Project(p) => first_blocker(&p.node.base, emit_ctx).or(Some("Project")),
MirExpr::RecordCreate(r) => {
for f in &r.node.fields {
if let Some(b) = first_blocker(&f.value, emit_ctx) {
return Some(b);
}
}
Some("RecordCreate(builtin/Tcp)")
}
MirExpr::RecordUpdate(u) => {
if let Some(b) = first_blocker(&u.node.base, emit_ctx) {
return Some(b);
}
for f in &u.node.updates {
if let Some(b) = first_blocker(&f.value, emit_ctx) {
return Some(b);
}
}
Some("RecordUpdate(builtin/Tcp)")
}
MirExpr::Construct(c) => {
for a in &c.node.args {
if let Some(b) = first_blocker(a, emit_ctx) {
return Some(b);
}
}
Some("Construct")
}
MirExpr::IfThenElse(ite) => first_blocker(&ite.node.cond, emit_ctx)
.or_else(|| first_blocker(&ite.node.then_branch, emit_ctx))
.or_else(|| first_blocker(&ite.node.else_branch, emit_ctx))
.or(Some("IfThenElse")),
MirExpr::Match(m) => {
if let Some(b) = first_blocker(&m.node.subject, emit_ctx) {
return Some(b);
}
for arm in &m.node.arms {
if let Some(b) = first_blocker(&arm.body, emit_ctx) {
return Some(b);
}
}
Some("Match")
}
other => Some(label_for(other)),
}
}
fn label_for(expr: &MirExpr) -> &'static str {
match expr {
MirExpr::Literal(_) => "Literal",
MirExpr::Local(_) => "Local(synthetic)",
MirExpr::Let(_) => "Let(synthetic)",
MirExpr::Call(_) => "Call",
MirExpr::TailCall(_) => "TailCall",
MirExpr::BinOp(_) => "BinOp",
MirExpr::Neg(_) => "Neg",
MirExpr::Match(_) => "Match",
MirExpr::Construct(_) => "Construct",
MirExpr::RecordCreate(_) => "RecordCreate",
MirExpr::RecordUpdate(_) => "RecordUpdate",
MirExpr::Project(_) => "Project",
MirExpr::IfThenElse(_) => "IfThenElse",
MirExpr::Try(_) => "Try",
MirExpr::List(_) => "List",
MirExpr::Tuple(_) => "Tuple",
MirExpr::MapLiteral(_) => "MapLiteral",
MirExpr::InterpolatedStr(_) => "InterpolatedStr",
MirExpr::IndependentProduct(_) => "IndependentProduct",
MirExpr::Return(_) => "Return",
MirExpr::FnValue(_) => "FnValue",
}
}
pub(super) fn emit_mir_expr(expr: &Spanned<MirExpr>, emit_ctx: &MirEmitCtx<'_>) -> Option<String> {
match &expr.node {
MirExpr::Literal(lit) => {
match &lit.node {
crate::ast::Literal::Int(i) if *i < 0 => match i.checked_neg() {
Some(pos) => Some(format!(
"(-{})",
emit_literal(&crate::ast::Literal::Int(pos))
)),
None => Some(emit_literal(&lit.node)),
},
crate::ast::Literal::Float(f) if f.is_sign_negative() => Some(format!(
"(-{})",
emit_literal(&crate::ast::Literal::Float(-f))
)),
_ => Some(emit_literal(&lit.node)),
}
}
MirExpr::Local(spanned_local) => {
let name = &spanned_local.node.name;
if name.is_empty() {
return None;
}
Some(aver_name_to_rust(name))
}
MirExpr::Neg(inner) => Some(format!("(-{})", emit_mir_expr(inner, emit_ctx)?)),
MirExpr::BinOp(spanned_binop) => {
let bop = &spanned_binop.node;
let l = emit_mir_expr(&bop.lhs, emit_ctx)?;
let r = emit_mir_expr(&bop.rhs, emit_ctx)?;
let op_str = match bop.op {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Eq => "==",
BinOp::Neq => "!=",
BinOp::Lt => "<",
BinOp::Gt => ">",
BinOp::Lte => "<=",
BinOp::Gte => ">=",
};
if matches!(bop.op, BinOp::Add)
&& !ty_is_numeric(bop.lhs.ty())
&& !ty_is_numeric(bop.rhs.ty())
{
let l = mir_maybe_clone(l, &bop.lhs.node, emit_ctx);
Some(format!("({} + &{})", l, r))
} else if matches!(bop.op, BinOp::Eq | BinOp::Neq) {
if let MirExpr::Literal(lit) = &bop.rhs.node
&& let crate::ast::Literal::Str(s) = &lit.node
{
return Some(format!("(&*{} {} {:?})", l, op_str, s));
}
if let MirExpr::Literal(lit) = &bop.lhs.node
&& let crate::ast::Literal::Str(s) = &lit.node
{
return Some(format!("({:?} {} &*{})", s, op_str, r));
}
Some(format!("({} {} {})", l, op_str, r))
} else {
Some(format!("({} {} {})", l, op_str, r))
}
}
MirExpr::Call(spanned_call) => {
let call = &spanned_call.node;
match &call.callee {
MirCallee::Fn(fn_id) => {
let name = emit_ctx.symbol_table.fn_entry(*fn_id).key.canonical();
emit_named_call(&name, &call.args, emit_ctx)
}
MirCallee::Builtin(id) => {
let name = emit_ctx.mir_builtins.get(id.0 as usize)?.as_str();
if super::builtins::builtin_is_effectful(name) {
emit_mir_effectful_builtin_call(name, &call.args, emit_ctx)
} else {
emit_mir_builtin_call(name, &call.args, emit_ctx)
}
}
MirCallee::Intrinsic(intrinsic) => {
emit_mir_intrinsic_call(*intrinsic, &call.args, emit_ctx)
}
MirCallee::LocalSlot { name, .. } => {
let func = aver_name_to_rust(name);
let mut arg_strs = Vec::with_capacity(call.args.len());
for a in &call.args {
arg_strs.push(mir_clone_arg(
emit_mir_expr(a, emit_ctx)?,
&a.node,
emit_ctx,
));
}
Some(format!("{}({})", func, arg_strs.join(", ")))
}
}
}
MirExpr::Return(inner) => Some(format!("return {}", emit_mir_expr(inner, emit_ctx)?)),
MirExpr::TailCall(spanned_tc) => {
let tc = &spanned_tc.node;
let name = emit_ctx.symbol_table.fn_entry(tc.target).key.canonical();
emit_named_call(&name, &tc.args, emit_ctx)
}
MirExpr::Try(inner) => {
let inner_code = emit_mir_expr(inner, emit_ctx)?;
let owned = mir_clone_arg(inner_code, &inner.node, emit_ctx);
Some(format!("{}?", owned))
}
MirExpr::Tuple(items) => {
let mut parts = Vec::with_capacity(items.len());
for item in items {
parts.push(mir_clone_arg(
emit_mir_expr(item, emit_ctx)?,
&item.node,
emit_ctx,
));
}
Some(format!("({})", parts.join(", ")))
}
MirExpr::List(items) => {
if items.is_empty() {
return Some("aver_rt::AverList::empty()".to_string());
}
let mut parts = Vec::with_capacity(items.len());
for item in items {
parts.push(mir_clone_arg(
emit_mir_expr(item, emit_ctx)?,
&item.node,
emit_ctx,
));
}
Some(format!(
"aver_rt::AverList::from_vec(vec![{}])",
parts.join(", ")
))
}
MirExpr::MapLiteral(entries) => {
if entries.is_empty() {
return Some("HashMap::new()".to_string());
}
let mut parts = Vec::with_capacity(entries.len());
for (k, v) in entries {
let key_str = mir_clone_arg(emit_mir_expr(k, emit_ctx)?, &k.node, emit_ctx);
let val_str = mir_clone_arg(emit_mir_expr(v, emit_ctx)?, &v.node, emit_ctx);
parts.push(format!("({}, {})", key_str, val_str));
}
Some(format!(
"vec![{}].into_iter().collect::<HashMap<_, _>>()",
parts.join(", ")
))
}
MirExpr::Let(spanned_let) => {
let let_node = &spanned_let.node;
let value = emit_mir_expr(&let_node.value, emit_ctx)?;
let body = emit_mir_expr(&let_node.body, emit_ctx)?;
if let_node.binding_name.is_empty() {
Some(format!("{{ {}; {} }}", value, body))
} else {
let name = aver_name_to_rust(&let_node.binding_name);
Some(format!("{{ let {} = {}; {} }}", name, value, body))
}
}
MirExpr::Project(spanned_proj) => {
let proj = &spanned_proj.node;
if let Some(dotted) = collapse_fnvalue_projection(&expr.node)
&& resolve_module_call(&dotted, emit_ctx.module_prefixes).is_some()
{
return Some(emit_mir_static_ref(&dotted, emit_ctx));
}
let base = emit_mir_expr(&proj.base, emit_ctx)?;
Some(format!("{}.{}", base, aver_name_to_rust(&proj.field)))
}
MirExpr::RecordCreate(spanned_rec) => {
let rec = &spanned_rec.node;
let rust_type = mir_record_rust_type(rec.type_id, &rec.type_name, emit_ctx);
let mut parts = Vec::with_capacity(rec.fields.len());
for f in &rec.fields {
let val =
mir_clone_arg(emit_mir_expr(&f.value, emit_ctx)?, &f.value.node, emit_ctx);
parts.push(format!("{}: {}", aver_name_to_rust(&f.name), val));
}
Some(format!("{} {{ {} }}", rust_type, parts.join(", ")))
}
MirExpr::RecordUpdate(spanned_upd) => {
let upd = &spanned_upd.node;
let rust_type = mir_record_rust_type(upd.type_id, &upd.type_name, emit_ctx);
let base = mir_clone_arg(
emit_mir_expr(&upd.base, emit_ctx)?,
&upd.base.node,
emit_ctx,
);
let mut parts = Vec::with_capacity(upd.updates.len());
for f in &upd.updates {
let val =
mir_clone_arg(emit_mir_expr(&f.value, emit_ctx)?, &f.value.node, emit_ctx);
parts.push(format!("{}: {}", aver_name_to_rust(&f.name), val));
}
Some(format!(
"{} {{ {}, ..{} }}",
rust_type,
parts.join(", "),
base
))
}
MirExpr::Construct(spanned_ctor) => {
let con = &spanned_ctor.node;
match con.ctor {
MirCtor::Builtin(builtin) => {
let (name, takes_arg) = match builtin {
BuiltinCtor::ResultOk => ("Ok", true),
BuiltinCtor::ResultErr => ("Err", true),
BuiltinCtor::OptionSome => ("Some", true),
BuiltinCtor::OptionNone => ("None", false),
};
if !takes_arg {
return Some(name.to_string());
}
let mut args = Vec::with_capacity(con.args.len());
for a in &con.args {
args.push(mir_clone_arg(
emit_mir_expr(a, emit_ctx)?,
&a.node,
emit_ctx,
));
}
Some(format!("{}({})", name, args.join(", ")))
}
MirCtor::User(ctor_id) => {
let ctor_entry = emit_ctx.symbol_table.ctor_entry(ctor_id);
let variant_name = ctor_entry.name.clone();
let type_entry = emit_ctx.symbol_table.type_entry(ctor_entry.owning_type);
let qualified = type_entry.key.canonical();
let boxed_positions = match emit_ctx.codegen {
Some(cg) => {
let ctor_name = format!("{}.{}", qualified, variant_name);
constructor_boxed_positions(&ctor_name, cg)
}
None => HashSet::new(),
};
let mut args = Vec::with_capacity(con.args.len());
for (idx, a) in con.args.iter().enumerate() {
let arg = mir_clone_arg(emit_mir_expr(a, emit_ctx)?, &a.node, emit_ctx);
if boxed_positions.contains(&idx) {
args.push(format!("std::sync::Arc::new({})", arg));
} else {
args.push(arg);
}
}
let args_str = args.join(", ");
let head = if let Some((prefix, suffix)) =
resolve_module_call(&qualified, emit_ctx.module_prefixes)
{
format!("{}::{}", module_prefix_to_rust_path(prefix), suffix)
} else {
qualified
};
if con.args.is_empty() {
Some(format!("{}::{}", head, variant_name))
} else {
Some(format!("{}::{}({})", head, variant_name, args_str))
}
}
}
}
MirExpr::IfThenElse(spanned_ite) => emit_mir_if_then_else(&spanned_ite.node, emit_ctx),
MirExpr::Match(spanned_match) => emit_mir_match(&spanned_match.node, emit_ctx),
MirExpr::IndependentProduct(spanned_ip) => {
emit_mir_independent_product(&spanned_ip.node, emit_ctx)
}
MirExpr::FnValue(name) => Some(emit_mir_static_ref(name, emit_ctx)),
_ => None,
}
}
fn collapse_fnvalue_projection(expr: &MirExpr) -> Option<String> {
match expr {
MirExpr::FnValue(name) => Some(name.clone()),
MirExpr::Project(p) => {
let base = collapse_fnvalue_projection(&p.node.base.node)?;
Some(format!("{}.{}", base, p.node.field))
}
_ => None,
}
}
fn emit_mir_static_ref(name: &str, ctx: &MirEmitCtx<'_>) -> String {
if name == "Option.None" || name == "None" {
return "None".to_string();
}
if name == "BranchPath.Root" {
return "aver_rt::BranchPath::root()".to_string();
}
if let Some((type_name, variant_name)) = name.rsplit_once('.')
&& let Some(cg) = ctx.codegen
{
let is_user = |n: &str| crate::codegen::common::is_user_type(n, cg);
if is_user(type_name) {
return if let Some((prefix, _)) = resolve_module_call(name, ctx.module_prefixes) {
let module_path = module_prefix_to_rust_path(prefix);
let bare_type = type_name
.rsplit_once('.')
.map(|(_, t)| t)
.unwrap_or(type_name);
format!("{}::{}::{}", module_path, bare_type, variant_name)
} else {
format!("{}::{}", type_name, variant_name)
};
}
if let Some((_, bare_type)) = type_name.rsplit_once('.')
&& is_user(bare_type)
{
return if let Some((prefix, _)) = resolve_module_call(name, ctx.module_prefixes) {
let module_path = module_prefix_to_rust_path(prefix);
format!("{}::{}::{}", module_path, bare_type, variant_name)
} else {
format!("{}::{}", bare_type, variant_name)
};
}
}
if let Some((prefix, bare)) = resolve_module_call(name, ctx.module_prefixes) {
let module_path = module_prefix_to_rust_path(prefix);
format!("{}::{}", module_path, aver_name_to_rust(bare))
} else {
aver_name_to_rust(name)
}
}
pub(super) fn emit_mir_verify_expr(
resolved: &Spanned<crate::ir::hir::ResolvedExpr>,
ctx: &CodegenContext,
) -> Option<String> {
let base = ctx.mir_program.as_ref()?;
let mut prog = base.clone();
let lowered = crate::ir::mir::lower_top_level_value(resolved, &mut prog).ok()?;
let policy = MirFnEmitPolicy::empty();
let emit_ctx = MirEmitCtx::program_level(ctx, &policy, &prog.builtins);
emit_mir_expr(&lowered, &emit_ctx)
}
pub(super) fn emit_mir_main_body(fn_id: crate::ir::FnId, ctx: &CodegenContext) -> Option<String> {
let mir_fn = ctx.mir_program.as_ref()?.fn_by_id(fn_id)?;
let resolved = ctx.resolved_program.fn_by_id(fn_id)?;
let policy = MirFnEmitPolicy::from_resolved(resolved, None, true);
let emit_ctx = MirEmitCtx::for_fn(ctx, &policy);
emit_mir_fn_body(&mir_fn.body, &emit_ctx)
}
pub(super) fn emit_mir_guest_entry_body(
resolved_fd: &crate::ir::hir::ResolvedFnDef,
scope: Option<&str>,
ctx: &CodegenContext,
) -> Option<String> {
let mir_fn = ctx.mir_program.as_ref()?.fn_by_id(resolved_fd.fn_id)?;
let policy =
MirFnEmitPolicy::from_resolved(resolved_fd, scope, true);
let emit_ctx = MirEmitCtx::for_fn(ctx, &policy);
emit_mir_fn_body(&mir_fn.body, &emit_ctx)
}
pub(super) fn emit_mir_top_stmt_values(
resolved_values: &[&Spanned<crate::ir::hir::ResolvedExpr>],
ctx: &CodegenContext,
) -> Option<Vec<String>> {
let base = ctx.mir_program.as_ref()?;
let mut prog = base.clone();
let lowered: Vec<Spanned<MirExpr>> = resolved_values
.iter()
.map(|value| crate::ir::mir::lower_top_level_value(value, &mut prog).ok())
.collect::<Option<_>>()?;
let policy = MirFnEmitPolicy::empty();
let emit_ctx = MirEmitCtx::program_level(ctx, &policy, &prog.builtins);
lowered
.iter()
.map(|low| emit_mir_expr(low, &emit_ctx))
.collect::<Option<Vec<_>>>()
}
fn emit_mir_independent_product(
ip: &crate::ir::mir::MirIndependentProduct,
emit_ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let mut parts: Vec<String> = Vec::with_capacity(ip.items.len());
for it in &ip.items {
parts.push(mir_clone_arg(
emit_mir_expr(it, emit_ctx)?,
&it.node,
emit_ctx,
));
}
let n = parts.len();
let has_replay = emit_ctx.codegen.is_some_and(|c| c.emit_replay_runtime);
let unwrap = ip.unwrap_results;
let mut code = String::new();
if has_replay {
code.push_str("if crate::aver_replay::is_effect_tracking_active() { ");
code.push_str("crate::aver_replay::enter_effect_group(); ");
for (i, part) in parts.iter().enumerate() {
code.push_str(&format!(
"crate::aver_replay::set_effect_branch({i}); let _r{i} = {part}; "
));
}
code.push_str("crate::aver_replay::exit_effect_group(); ");
if unwrap {
code.push_str(&emit_result_tuple_unwrap("_r", "__v", n));
code.push('?');
} else {
code.push_str(&emit_tuple_from_vars("_r", n));
}
code.push_str(" } else { ");
}
if unwrap {
code.push_str("{ ");
if has_replay {
code.push_str(
"let __parallel_scope = crate::aver_replay::capture_parallel_scope_context(); ",
);
}
code.push_str(
"let __cancel_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); ",
);
code.push_str("std::thread::scope(|_s| { ");
for (i, part) in parts.iter().enumerate() {
if has_replay {
code.push_str(&format!(
"let __parallel_scope{i} = __parallel_scope.clone(); "
));
}
code.push_str(&format!("let __cancel_flag{i} = __cancel_flag.clone(); "));
code.push_str(&format!("let _h{i} = _s.spawn(move || "));
if has_replay {
code.push_str(&format!(
"crate::aver_replay::with_parallel_scope_context(__parallel_scope{i}.clone(), move || "
));
}
code.push_str("{ crate::run_cancelable_branch(__cancel_flag");
code.push_str(&i.to_string());
code.push_str(".clone(), move || { let __result = ");
code.push_str(part);
code.push_str("; if let Err(_) = &__result { __cancel_flag");
code.push_str(&i.to_string());
code.push_str(".store(true, std::sync::atomic::Ordering::Relaxed); } __result }) }");
if has_replay {
code.push(')');
}
code.push_str("); ");
}
for i in 0..n {
code.push_str(&format!("let _b{i} = _h{i}.join().unwrap(); "));
}
code.push_str(&emit_parallel_result_tuple_unwrap("_b", "_r", "__v", n));
code.push_str(" })? }");
} else {
if has_replay {
code.push_str(
"let __parallel_scope = crate::aver_replay::capture_parallel_scope_context(); ",
);
}
code.push_str("std::thread::scope(|_s| { ");
for (i, part) in parts.iter().enumerate() {
if has_replay {
code.push_str(&format!(
"let __parallel_scope{i} = __parallel_scope.clone(); "
));
code.push_str(&format!(
"let _h{i} = _s.spawn(move || crate::aver_replay::with_parallel_scope_context(__parallel_scope{i}.clone(), move || {part})); "
));
} else {
code.push_str(&format!("let _h{i} = _s.spawn(move || {part}); "));
}
}
for i in 0..n {
code.push_str(&format!("let _r{i} = _h{i}.join().unwrap(); "));
}
code.push_str(&emit_tuple_from_vars("_r", n));
code.push_str(" }) ");
}
if has_replay {
code.push('}');
}
Some(code)
}
fn emit_mir_if_then_else(
ite: &crate::ir::mir::MirIfThenElse,
emit_ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let (cond, then_src, else_src) = mir_if_cond_and_branches(ite, emit_ctx)?;
let then_branch = mir_maybe_clone(emit_mir_expr(then_src, emit_ctx)?, &then_src.node, emit_ctx);
let else_branch = mir_maybe_clone(emit_mir_expr(else_src, emit_ctx)?, &else_src.node, emit_ctx);
Some(format!(
"if {} {{ {} }} else {{ {} }}",
cond, then_branch, else_branch
))
}
fn resolved_pattern_is_irrefutable(pat: &ResolvedPattern) -> bool {
match pat {
ResolvedPattern::Wildcard | ResolvedPattern::Ident(_) => true,
ResolvedPattern::Tuple(pats) => pats.iter().all(resolved_pattern_is_irrefutable),
_ => false,
}
}
fn mir_pattern_to_resolved(pat: &MirPattern, ctx: &MirEmitCtx<'_>) -> Option<ResolvedPattern> {
Some(match pat {
MirPattern::Wildcard => ResolvedPattern::Wildcard,
MirPattern::Literal(lit) => ResolvedPattern::Literal(lit.clone()),
MirPattern::Bind(_, name) => ResolvedPattern::Ident(name.clone()),
MirPattern::EmptyList => ResolvedPattern::EmptyList,
MirPattern::Cons {
head_name,
tail_name,
..
} => ResolvedPattern::Cons(head_name.clone(), tail_name.clone()),
MirPattern::Tuple(sub) => {
let mut parts = Vec::with_capacity(sub.len());
for p in sub {
parts.push(mir_pattern_to_resolved(p, ctx)?);
}
ResolvedPattern::Tuple(parts)
}
MirPattern::Ctor {
ctor,
binding_names,
..
} => {
let resolved_ctor = match ctor {
MirCtor::Builtin(b) => ResolvedCtor::Builtin(*b),
MirCtor::User(ctor_id) => {
let entry = ctx.symbol_table.ctor_entry(*ctor_id);
ResolvedCtor::User {
ctor_id: *ctor_id,
type_id: entry.owning_type,
name: entry.name.clone(),
}
}
};
ResolvedPattern::Ctor(resolved_ctor, binding_names.clone())
}
})
}
fn synthetic_arm(pattern: ResolvedPattern) -> ResolvedMatchArm {
ResolvedMatchArm {
pattern,
body: Box::new(Spanned {
node: crate::ir::hir::ResolvedExpr::Literal(crate::ast::Literal::Unit),
line: 0,
ty: std::sync::OnceLock::new(),
}),
binding_slots: std::sync::OnceLock::new(),
}
}
fn emit_mir_match(m: &MirMatch, emit_ctx: &MirEmitCtx<'_>) -> Option<String> {
emit_mir_match_with(m, emit_ctx, &|arm_body, ctx| {
let body = emit_mir_expr(arm_body, ctx)?;
Some(mir_maybe_clone(body, &arm_body.node, ctx))
})
}
fn emit_mir_match_with(
m: &MirMatch,
emit_ctx: &MirEmitCtx<'_>,
render_arm: &dyn Fn(&Spanned<MirExpr>, &MirEmitCtx<'_>) -> Option<String>,
) -> Option<String> {
let mut arms: Vec<ResolvedMatchArm> = Vec::with_capacity(m.arms.len());
for arm in &m.arms {
arms.push(synthetic_arm(mir_pattern_to_resolved(
&arm.pattern,
emit_ctx,
)?));
}
let mut arm_bodies: Vec<String> = Vec::with_capacity(m.arms.len());
for arm in &m.arms {
arm_bodies.push(render_arm(&arm.body, emit_ctx)?);
}
let body_for_arm = |arm: &ResolvedMatchArm| -> String {
arms.iter()
.position(|candidate| std::ptr::eq(candidate, arm))
.map(|idx| arm_bodies[idx].clone())
.unwrap_or_default()
};
if arms.len() == 1 && resolved_pattern_is_irrefutable(&arms[0].pattern) {
let subj = mir_clone_arg(
emit_mir_expr(&m.subject, emit_ctx)?,
&m.subject.node,
emit_ctx,
);
let codegen = emit_ctx.codegen?;
let pat = emit_pattern(&arms[0].pattern, false, codegen);
let body = arm_bodies[0].clone();
return Some(match &arms[0].pattern {
ResolvedPattern::Wildcard => body,
ResolvedPattern::Ident(name) => {
let name = aver_name_to_rust(name);
format!("{{ let {} = {}; {} }}", name, subj, body)
}
_ => format!("{{ let {} = {}; {} }}", pat, subj, body),
});
}
let codegen = emit_ctx.codegen?;
let no_bindings = arms
.iter()
.all(|arm| crate::ir::vars::resolved_pattern_bindings(&arm.pattern).is_empty());
let match_on_ref = no_bindings && mir_subject_is_borrowed_param(&m.subject.node, emit_ctx);
let subj = if match_on_ref {
emit_mir_expr(&m.subject, emit_ctx)?
} else {
mir_clone_arg(
emit_mir_expr(&m.subject, emit_ctx)?,
&m.subject.node,
emit_ctx,
)
};
let dispatch_plan = classify_match_dispatch_plan_resolved(&arms);
if matches!(dispatch_plan.as_ref(), Some(MatchDispatchPlan::Bool(_))) {
return None;
}
if has_list_patterns(&arms) {
let list_shape = match dispatch_plan.as_ref() {
Some(MatchDispatchPlan::List(shape)) => Some(*shape),
_ => None,
};
return Some(emit_list_match(
subj,
&arms,
list_shape,
true,
codegen,
body_for_arm,
));
}
if let Some(MatchDispatchPlan::Table(shape)) = dispatch_plan.as_ref() {
return Some(emit_dispatch_table_match(subj, &arms, shape, body_for_arm));
}
let needs_as_str = true;
let match_expr = if needs_as_str && has_string_literal_patterns(&arms) {
format!("&*{}", subj)
} else {
subj
};
let mut arm_strs = Vec::with_capacity(arms.len());
for (idx, arm) in arms.iter().enumerate() {
let pat = emit_pattern(&arm.pattern, needs_as_str, codegen);
let body = arm_bodies[idx].clone();
let mut rebindings = emit_pattern_rebindings(&arm.pattern, codegen);
if match_on_ref {
let ref_rebinds = emit_ref_match_rebindings(&arm.pattern);
if !ref_rebinds.is_empty() {
rebindings = format!("{}{}", ref_rebinds, rebindings);
}
}
arm_strs.push(format!(
" {} => {{\n {}{}\n }}",
pat, rebindings, body
));
}
Some(format!(
"match {} {{\n{}\n }}",
match_expr,
arm_strs.join(",\n")
))
}
fn mir_subject_is_borrowed_param(subject: &MirExpr, emit_ctx: &MirEmitCtx<'_>) -> bool {
local_of(subject).is_some_and(|local| emit_ctx.is_borrowed_param(&local.name))
}
pub(super) fn emit_mir_fn_body(
body: &Spanned<MirExpr>,
emit_ctx: &MirEmitCtx<'_>,
) -> Option<String> {
if let MirExpr::Let(spanned_let) = &body.node
&& let Some(lines) = emit_mir_let_chain_flat(&spanned_let.node, emit_ctx)
{
return Some(format!(" crate::cancel_checkpoint();\n {}", lines));
}
let mut code = emit_mir_expr(body, emit_ctx)?;
if let MirExpr::Project(p) = &body.node
&& let Some(local) = local_of(&p.node.base.node)
&& emit_ctx.is_borrowed_param(&local.name)
{
code = format!("{}.clone()", code);
}
Some(format!(" crate::cancel_checkpoint();\n {}", code))
}
fn emit_mir_let_chain_flat(
let_node: &crate::ir::mir::MirLet,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let mut lines: Vec<String> = Vec::new();
let mut current = let_node;
loop {
let value = emit_mir_expr(¤t.value, ctx)?;
if current.binding_name.is_empty() {
lines.push(format!("{};", value));
} else {
let name = aver_name_to_rust(¤t.binding_name);
lines.push(format!("let {} = {};", name, value));
}
match ¤t.body.node {
MirExpr::Let(next) => {
current = &next.node;
}
_ => {
let final_expr = emit_mir_expr(¤t.body, ctx)?;
lines.push(final_expr);
break;
}
}
}
Some(lines.join("\n "))
}
pub(super) fn emit_mir_fn_body_routed(
mir_fn: &crate::ir::mir::MirFn,
resolved: &crate::ir::hir::ResolvedFnDef,
scope: Option<&str>,
borrow_by_default: bool,
ctx: &CodegenContext,
) -> Option<String> {
let mut policy = MirFnEmitPolicy::from_resolved(resolved, scope, borrow_by_default);
policy.apply_own_param(mir_fn);
let emit_ctx = MirEmitCtx::for_fn(ctx, &policy);
emit_mir_fn_body(&mir_fn.body, &emit_ctx)
}
fn ty_is_numeric(ty: Option<&Type>) -> bool {
matches!(ty, Some(Type::Int | Type::Float))
}
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_mir_tco_fn(
fd: &crate::ast::FnDef,
resolved_fd: &crate::ir::hir::ResolvedFnDef,
mir_fn: &crate::ir::mir::MirFn,
fn_name: &str,
ret_type: &str,
visibility: &str,
scope: Option<&str>,
ctx: &CodegenContext,
) -> Option<String> {
use super::toplevel::{compute_rc_params, compute_self_passthrough_params, rc_param_names};
let passthrough_indices = compute_self_passthrough_params(fd);
let rc_indices = compute_rc_params(std::slice::from_ref(&fd), ctx);
let rc_names = rc_param_names(&fd.params, &rc_indices);
let mut policy = MirFnEmitPolicy::from_resolved(resolved_fd, scope, false);
policy.rc_wrapped = rc_names.clone();
policy.apply_own_param(mir_fn);
for n in &rc_names {
policy.owned_params.remove(n);
}
let emit_ctx = MirEmitCtx::for_fn(ctx, &policy);
let body_code = emit_mir_tco_body(
&mir_fn.body,
mir_fn.fn_id,
&fd.params,
&passthrough_indices,
&emit_ctx,
)?;
let params = emit_tco_params_mir(&fd.params, &rc_indices);
let mut lines = Vec::new();
lines.push(format!(
"{}fn {}({}) -> {} {{",
visibility, fn_name, params, ret_type
));
for &i in &rc_indices {
let rust_name = aver_name_to_rust(&fd.params[i].0);
lines.push(format!(
" let {} = std::sync::Arc::new({});",
rust_name, rust_name
));
}
lines.push(" loop {".to_string());
lines.push(body_code);
lines.push(" }".to_string());
lines.push("}".to_string());
Some(lines.join("\n"))
}
fn emit_tco_params_mir(
params: &[(String, String)],
rc_indices: &std::collections::HashSet<usize>,
) -> String {
params
.iter()
.enumerate()
.map(|(i, (name, type_ann))| {
let rust_type = super::types::type_annotation_to_rust(type_ann);
let rust_name = aver_name_to_rust(name);
if rc_indices.contains(&i) {
format!("{}: {}", rust_name, rust_type)
} else {
format!("mut {}: {}", rust_name, rust_type)
}
})
.collect::<Vec<_>>()
.join(", ")
}
fn emit_mir_tco_body(
body: &Spanned<MirExpr>,
self_fn: crate::ir::FnId,
params: &[(String, String)],
passthrough: &std::collections::HashSet<usize>,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let mut lines = Vec::new();
lines.push(" crate::cancel_checkpoint();".to_string());
let mut current = body;
while let MirExpr::Let(spanned_let) = ¤t.node {
let let_node = &spanned_let.node;
let value = emit_mir_expr(&let_node.value, ctx)?;
if let_node.binding_name.is_empty() {
lines.push(format!(" {};", value));
} else {
let name = aver_name_to_rust(&let_node.binding_name);
lines.push(format!(" let {} = {};", name, value));
}
current = &let_node.body;
}
let tail = emit_mir_tco_tail_expr(current, self_fn, params, passthrough, ctx)?;
lines.push(format!(" {}", tail));
Some(lines.join("\n"))
}
fn emit_mir_tco_tail_expr(
expr: &Spanned<MirExpr>,
self_fn: crate::ir::FnId,
params: &[(String, String)],
passthrough: &std::collections::HashSet<usize>,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
match &expr.node {
MirExpr::TailCall(spanned_tc) => {
let tc = &spanned_tc.node;
if tc.target == self_fn && tc.args.len() == params.len() {
emit_mir_self_tco_continue(&tc.args, params, passthrough, ctx)
} else {
let name = ctx.symbol_table.fn_entry(tc.target).key.canonical();
Some(format!(
"return {};",
emit_named_call(&name, &tc.args, ctx)?
))
}
}
MirExpr::Match(spanned_match) => {
emit_mir_match_with(&spanned_match.node, ctx, &|arm_body, ctx| {
emit_mir_tco_tail_expr(arm_body, self_fn, params, passthrough, ctx)
})
}
MirExpr::IfThenElse(spanned_ite) => {
emit_mir_tco_if_then_else(&spanned_ite.node, self_fn, params, passthrough, ctx)
}
_ => Some(format!("return {};", emit_mir_value_return(expr, ctx)?)),
}
}
fn emit_mir_tco_if_then_else(
ite: &crate::ir::mir::MirIfThenElse,
self_fn: crate::ir::FnId,
params: &[(String, String)],
passthrough: &std::collections::HashSet<usize>,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let (cond, then_src, else_src) = mir_if_cond_and_branches(ite, ctx)?;
let then_branch = emit_mir_tco_tail_expr(then_src, self_fn, params, passthrough, ctx)?;
let else_branch = emit_mir_tco_tail_expr(else_src, self_fn, params, passthrough, ctx)?;
Some(format!(
"if {} {{ {} }} else {{ {} }}",
cond, then_branch, else_branch
))
}
fn emit_mir_value_return(expr: &Spanned<MirExpr>, ctx: &MirEmitCtx<'_>) -> Option<String> {
let code = emit_mir_expr(expr, ctx)?;
Some(mir_maybe_clone(code, &expr.node, ctx))
}
fn emit_mir_self_tco_continue(
args: &[Spanned<MirExpr>],
params: &[(String, String)],
passthrough: &std::collections::HashSet<usize>,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let mut arg_strs = Vec::with_capacity(args.len());
for a in args {
arg_strs.push(mir_clone_arg(emit_mir_expr(a, ctx)?, &a.node, ctx));
}
let mut rebind: Vec<bool> = vec![false; params.len()];
for (i, (name, _)) in params.iter().enumerate() {
if passthrough.contains(&i) {
continue;
}
if arg_strs[i] == aver_name_to_rust(name) {
continue; }
rebind[i] = true;
}
let mut lines = Vec::new();
lines.push("{".to_string());
for (i, arg_str) in arg_strs.iter().enumerate() {
if rebind[i] {
lines.push(format!(" let __tco{} = {};", i, arg_str));
}
}
for (i, (name, _)) in params.iter().enumerate() {
if rebind[i] {
lines.push(format!(
" {} = __tco{};",
aver_name_to_rust(name),
i
));
}
}
lines.push(" continue;".to_string());
lines.push(" }".to_string());
Some(lines.join("\n"))
}
fn mir_if_cond_and_branches<'a>(
ite: &'a crate::ir::mir::MirIfThenElse,
ctx: &MirEmitCtx<'_>,
) -> Option<(String, &'a Spanned<MirExpr>, &'a Spanned<MirExpr>)> {
let canonical_compare = |op: BinOp| -> Option<(&'static str, bool)> {
match op {
BinOp::Eq => Some(("==", false)),
BinOp::Neq => Some(("==", true)),
BinOp::Lt => Some(("<", false)),
BinOp::Gte => Some(("<", true)),
BinOp::Gt => Some((">", false)),
BinOp::Lte => Some((">", true)),
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => None,
}
};
match &ite.cond.node {
MirExpr::BinOp(spanned_binop) if canonical_compare(spanned_binop.node.op).is_some() => {
let bop = &spanned_binop.node;
let (op_str, invert) = canonical_compare(bop.op).expect("checked by guard");
let l = emit_mir_expr(&bop.lhs, ctx)?;
let r = emit_mir_expr(&bop.rhs, ctx)?;
let cond = format!("({} {} {})", l, op_str, r);
if invert {
Some((cond, &ite.else_branch, &ite.then_branch))
} else {
Some((cond, &ite.then_branch, &ite.else_branch))
}
}
_ => {
let cond = emit_mir_expr(&ite.cond, ctx)?;
Some((cond, &ite.then_branch, &ite.else_branch))
}
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_mir_mutual_tco_block(
group_id: usize,
group_fns: &[&crate::ast::FnDef],
mir_fns: &[&crate::ir::mir::MirFn],
resolved_fns: &[&crate::ir::hir::ResolvedFnDef],
ctx: &CodegenContext,
scope: Option<&str>,
visibility: &str,
) -> Option<String> {
use super::toplevel::{compute_rc_params, fn_name_to_variant, rc_param_names};
if group_fns.is_empty() {
return None;
}
let enum_name = format!("__MutualTco{}", group_id);
let trampoline_name = format!("__mutual_tco_trampoline_{}", group_id);
let ret_type = if group_fns[0].return_type.is_empty() {
"()".to_string()
} else {
super::types::type_annotation_to_rust(&group_fns[0].return_type)
};
let member_fn_ids: HashSet<crate::ir::FnId> = mir_fns.iter().map(|m| m.fn_id).collect();
let rc_indices = compute_rc_params(group_fns, ctx);
let rc_names = rc_param_names(&group_fns[0].params, &rc_indices);
let mut arm_bodies: Vec<String> = Vec::with_capacity(group_fns.len());
for (i, mir_fn) in mir_fns.iter().enumerate() {
let mut policy = MirFnEmitPolicy::from_resolved(resolved_fns[i], scope, false);
policy.rc_wrapped = rc_names.clone();
let arm_ctx = MirEmitCtx::for_fn(ctx, &policy);
let body = emit_mir_trampoline_body(
&mir_fn.body,
&member_fn_ids,
&enum_name,
&rc_names,
&arm_ctx,
)?;
arm_bodies.push(body);
}
let mut sections = Vec::new();
let mut enum_lines = Vec::new();
enum_lines.push("#[allow(non_camel_case_types)]".to_string());
enum_lines.push(format!("enum {} {{", enum_name));
for fd in group_fns {
let variant = fn_name_to_variant(&fd.name);
let param_types: Vec<String> = fd
.params
.iter()
.filter(|(name, _)| !rc_names.contains(name))
.map(|(_, ty)| super::types::type_annotation_to_rust(ty))
.collect();
if param_types.is_empty() {
enum_lines.push(format!(" {},", variant));
} else {
enum_lines.push(format!(" {}({}),", variant, param_types.join(", ")));
}
}
enum_lines.push("}".to_string());
sections.push(enum_lines.join("\n"));
let rc_extra_params: String = mutual_rc_param_sig(group_fns[0], &rc_names);
let mut tramp_lines = Vec::new();
tramp_lines.push(format!(
"fn {}(mut __state: {}{}) -> {} {{",
trampoline_name, enum_name, rc_extra_params, ret_type
));
tramp_lines.push(" loop {".to_string());
tramp_lines.push(" __state = match __state {".to_string());
for (fd, arm_body) in group_fns.iter().zip(&arm_bodies) {
let variant = fn_name_to_variant(&fd.name);
let param_bindings: Vec<String> = fd
.params
.iter()
.filter(|(name, _)| !rc_names.contains(name))
.map(|(name, _)| format!("mut {}", aver_name_to_rust(name)))
.collect();
let binding = if param_bindings.is_empty() {
format!("{}::{}", enum_name, variant)
} else {
format!("{}::{}({})", enum_name, variant, param_bindings.join(", "))
};
tramp_lines.push(format!(" {} => {{", binding));
tramp_lines.push(arm_body.clone());
tramp_lines.push(" }".to_string());
}
tramp_lines.push(" };".to_string());
tramp_lines.push(" }".to_string());
tramp_lines.push("}".to_string());
sections.push(tramp_lines.join("\n"));
for fd in group_fns {
let fn_name = aver_name_to_rust(&fd.name);
let variant = fn_name_to_variant(&fd.name);
let params = super::toplevel::emit_fn_params_pub(&fd.params, false);
let variant_arg_names: Vec<String> = fd
.params
.iter()
.filter(|(name, _)| !rc_names.contains(name))
.map(|(name, type_ann)| {
let rust_name = aver_name_to_rust(name);
let ty = crate::types::parse_type_str(type_ann);
if should_borrow_param(&ty) {
format!("{}.clone()", rust_name)
} else {
rust_name
}
})
.collect();
let variant_call = if variant_arg_names.is_empty() {
format!("{}::{}", enum_name, variant)
} else {
format!(
"{}::{}({})",
enum_name,
variant,
variant_arg_names.join(", ")
)
};
let rc_extra_args: String = {
let parts: Vec<String> = fd
.params
.iter()
.filter(|(name, _)| rc_names.contains(name))
.map(|(name, _)| format!("&{}", aver_name_to_rust(name)))
.collect();
if parts.is_empty() {
String::new()
} else {
format!(", {}", parts.join(", "))
}
};
let mut wrapper = Vec::new();
if let Some(desc) = &fd.desc {
wrapper.push(format!("/// {}", desc));
}
wrapper.push(format!(
"{}fn {}({}) -> {} {{",
visibility, fn_name, params, ret_type
));
wrapper.push(format!(
" {}({}{})",
trampoline_name, variant_call, rc_extra_args
));
wrapper.push("}".to_string());
sections.push(wrapper.join("\n"));
}
Some(sections.join("\n\n"))
}
fn mutual_rc_param_sig(fd: &crate::ast::FnDef, rc_names: &HashSet<String>) -> String {
if rc_names.is_empty() {
return String::new();
}
let parts: Vec<String> = fd
.params
.iter()
.filter(|(name, _)| rc_names.contains(name))
.map(|(name, ty)| {
format!(
"{}: &{}",
aver_name_to_rust(name),
super::types::type_annotation_to_rust(ty)
)
})
.collect();
if parts.is_empty() {
String::new()
} else {
format!(", {}", parts.join(", "))
}
}
fn emit_mir_trampoline_body(
body: &Spanned<MirExpr>,
members: &HashSet<crate::ir::FnId>,
enum_name: &str,
rc_names: &HashSet<String>,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let mut lines = Vec::new();
lines.push(" crate::cancel_checkpoint();".to_string());
let mut current = body;
while let MirExpr::Let(spanned_let) = ¤t.node {
let let_node = &spanned_let.node;
let value = emit_mir_expr(&let_node.value, ctx)?;
if let_node.binding_name.is_empty() {
lines.push(format!(" {};", value));
} else {
let name = aver_name_to_rust(&let_node.binding_name);
lines.push(format!(" let {} = {};", name, value));
}
current = &let_node.body;
}
let tail = emit_mir_trampoline_tail_expr(current, members, enum_name, rc_names, ctx)?;
lines.push(format!(" {}", tail));
Some(lines.join("\n"))
}
fn emit_mir_trampoline_tail_expr(
expr: &Spanned<MirExpr>,
members: &HashSet<crate::ir::FnId>,
enum_name: &str,
rc_names: &HashSet<String>,
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
match &expr.node {
MirExpr::TailCall(spanned_tc) => {
let tc = &spanned_tc.node;
if members.contains(&tc.target) {
let target_name = ctx.symbol_table.fn_entry(tc.target).key.name.clone();
let variant = super::toplevel::fn_name_to_variant(&target_name);
let mut arg_strs = Vec::new();
for a in &tc.args {
if let Some(local) = local_of(&a.node)
&& rc_names.contains(&local.name)
{
continue;
}
arg_strs.push(mir_clone_arg(emit_mir_expr(a, ctx)?, &a.node, ctx));
}
if arg_strs.is_empty() {
Some(format!("{}::{}", enum_name, variant))
} else {
Some(format!(
"{}::{}({})",
enum_name,
variant,
arg_strs.join(", ")
))
}
} else {
let name = ctx.symbol_table.fn_entry(tc.target).key.canonical();
Some(format!("return {}", emit_named_call(&name, &tc.args, ctx)?))
}
}
MirExpr::Match(spanned_match) => {
emit_mir_match_with(&spanned_match.node, ctx, &|arm_body, ctx| {
emit_mir_trampoline_tail_expr(arm_body, members, enum_name, rc_names, ctx)
})
}
MirExpr::IfThenElse(spanned_ite) => {
let (cond, then_src, else_src) = mir_if_cond_and_branches(&spanned_ite.node, ctx)?;
let t = emit_mir_trampoline_tail_expr(then_src, members, enum_name, rc_names, ctx)?;
let e = emit_mir_trampoline_tail_expr(else_src, members, enum_name, rc_names, ctx)?;
Some(format!("if {} {{ {} }} else {{ {} }}", cond, t, e))
}
_ => Some(format!("return {}", emit_mir_value_return(expr, ctx)?)),
}
}
fn local_of(expr: &MirExpr) -> Option<&MirLocal> {
match expr {
MirExpr::Local(l) if !l.node.name.is_empty() => Some(&l.node),
_ => None,
}
}
fn mir_expr_skip_clone(expr: &MirExpr, ctx: &MirEmitCtx<'_>) -> bool {
match local_of(expr) {
Some(local) => {
let name = local.name.as_str();
if ctx.is_rc_wrapped(name) || ctx.is_borrowed_param(name) {
return false;
}
local.last_use || ctx.is_copy(name)
}
None => true,
}
}
fn mir_maybe_clone(code: String, expr: &MirExpr, ctx: &MirEmitCtx<'_>) -> String {
if let Some(local) = local_of(expr) {
let name = local.name.as_str();
return if mir_expr_skip_clone(expr, ctx) {
code
} else if ctx.is_rc_wrapped(name) {
format!("(*{}).clone()", code)
} else {
format!("{}.clone()", code)
};
}
if matches!(expr, MirExpr::Project(_)) {
return format!("{}.clone()", code);
}
code
}
fn mir_clone_arg(code: String, expr: &MirExpr, ctx: &MirEmitCtx<'_>) -> String {
if let MirExpr::Project(p) = expr
&& mir_attr_result_is_copy(&p.node, ctx)
{
return code;
}
mir_maybe_clone(code, expr, ctx)
}
fn mir_attr_result_is_copy(proj: &crate::ir::mir::MirProject, ctx: &MirEmitCtx<'_>) -> bool {
let Some(cg) = ctx.codegen else {
return false;
};
let Some(local) = local_of(&proj.base.node) else {
return false;
};
let Some(named_ty) = ctx
.local_types
.get(&local.name)
.filter(|t| matches!(t, Type::Named { .. }))
else {
return false;
};
super::expr::record_field_is_copy(named_ty, &proj.field, cg)
}
fn emit_named_call(name: &str, args: &[Spanned<MirExpr>], ctx: &MirEmitCtx<'_>) -> Option<String> {
let borrow_mask = match ctx.codegen {
Some(cg) => callee_borrow_mask(name, args.len(), cg),
None => vec![false; args.len()],
};
let mut arg_strs = Vec::with_capacity(args.len());
for (i, a) in args.iter().enumerate() {
let code = emit_mir_expr(a, ctx)?;
let s = if borrow_mask.get(i).copied().unwrap_or(false) {
mir_borrow_arg(code, &a.node, ctx)
} else {
mir_clone_arg(code, &a.node, ctx)
};
arg_strs.push(s);
}
if let Some((prefix, suffix)) = resolve_module_call(name, ctx.module_prefixes) {
Some(format!(
"{}::{}({})",
module_prefix_to_rust_path(prefix),
aver_name_to_rust(suffix),
arg_strs.join(", ")
))
} else {
Some(format!(
"{}({})",
aver_name_to_rust(name),
arg_strs.join(", ")
))
}
}
fn mir_borrow_arg(code: String, expr: &MirExpr, ctx: &MirEmitCtx<'_>) -> String {
let Some(local) = local_of(expr) else {
return format!("&{}", code);
};
let name = local.name.as_str();
if ctx.is_copy(name) {
code
} else if matches!(ctx.local_types.get(name), Some(Type::Str)) {
if local.last_use {
code
} else if ctx.is_rc_wrapped(name) {
format!("(*{}).clone()", code)
} else {
format!("{}.clone()", code)
}
} else if ctx.is_borrowed_param(name) {
code
} else if ctx.is_rc_wrapped(name) {
format!("&*{}", code)
} else {
format!("&{}", code)
}
}
fn mir_str_arg_or_deref(expr: &Spanned<MirExpr>, ctx: &MirEmitCtx<'_>) -> Option<String> {
if let MirExpr::Literal(lit) = &expr.node
&& let crate::ast::Literal::Str(s) = &lit.node
{
return Some(format!("{:?}", s));
}
let code = emit_mir_expr(expr, ctx)?;
Some(format!("&*{}", code))
}
fn mir_builtin_call_parts<'a, 'c>(
expr: &'a MirExpr,
ctx: &MirEmitCtx<'c>,
) -> Option<(&'c str, &'a [Spanned<MirExpr>])> {
let MirExpr::Call(spanned_call) = expr else {
return None;
};
let call = &spanned_call.node;
let MirCallee::Builtin(id) = &call.callee else {
return None;
};
let name = ctx.mir_builtins.get(id.0 as usize)?.as_str();
Some((name, &call.args))
}
fn mir_same_local(a: &MirExpr, b: &MirExpr) -> bool {
match (local_of(a), local_of(b)) {
(Some(la), Some(lb)) => la.slot == lb.slot,
_ => false,
}
}
fn try_emit_mir_fusion(
name: &str,
args: &[Spanned<MirExpr>],
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
match name {
"Option.withDefault" if args.len() == 2 => {
let (inner_name, inner_args) = mir_builtin_call_parts(&args[0].node, ctx)?;
if inner_name != "Vector.set" || inner_args.len() != 3 {
return None;
}
if !mir_same_local(&args[1].node, &inner_args[0].node) {
return None;
}
let set_local = local_of(&inner_args[0].node);
let default_local = local_of(&args[1].node);
let move_vec = match (set_local, default_local) {
(Some(sv), Some(dv)) => {
ctx.owned_params.contains(sv.name.as_str())
&& !ctx.is_borrowed_param(&sv.name)
&& !ctx.is_rc_wrapped(&sv.name)
&& (sv.last_use || dv.last_use)
}
_ => false,
};
let vector = if move_vec {
emit_mir_expr(&inner_args[0], ctx)?
} else {
mir_clone_arg(
emit_mir_expr(&inner_args[0], ctx)?,
&inner_args[0].node,
ctx,
)
};
let index = emit_mir_expr(&inner_args[1], ctx)?;
let value = mir_clone_arg(
emit_mir_expr(&inner_args[2], ctx)?,
&inner_args[2].node,
ctx,
);
Some(format!(
"{{ let __vec = {}; let __idx = {} as usize; if __idx < __vec.len() {{ __vec.set_unchecked(__idx, {}) }} else {{ __vec }} }}",
vector, index, value
))
}
"Result.withDefault" if args.len() == 2 => {
let (inner_name, inner_args) = mir_builtin_call_parts(&args[0].node, ctx)?;
let op = match inner_name {
"Int.mod" => "rem_euclid",
"Int.div" => "div",
_ => return None,
};
if inner_args.len() != 2 {
return None;
}
let MirExpr::Literal(default_lit) = &args[1].node else {
return None;
};
let a = &inner_args[0];
let b = &inner_args[1];
let a_str = emit_mir_expr(a, ctx)?;
let default = emit_literal(&default_lit.node);
match op {
"div" => {
let b_str = emit_mir_expr(b, ctx)?;
Some(format!(
"({}).checked_div_euclid({}).unwrap_or({})",
a_str, b_str, default
))
}
_ => {
if let MirExpr::Literal(b_lit) = &b.node
&& let crate::ast::Literal::Int(n) = &b_lit.node
&& *n != 0
{
let b_str = emit_literal(&crate::ast::Literal::Int(*n));
Some(format!("({}).rem_euclid({})", a_str, b_str))
} else {
let b_str = emit_mir_expr(b, ctx)?;
Some(format!(
"{{ let __b = {}; if __b == 0i64 {{ {} }} else {{ ({}).rem_euclid(__b) }} }}",
b_str, default, a_str
))
}
}
}
}
"Vector.get" if args.len() == 2 => {
let (inner_name, inner_args) = mir_builtin_call_parts(&args[0].node, ctx)?;
if inner_name != "Vector.fromList" || inner_args.len() != 1 {
return None;
}
let list = emit_mir_expr(&inner_args[0], ctx)?;
let index = emit_mir_expr(&args[1], ctx)?;
Some(format!(
"{}.to_vec().get({} as usize).cloned()",
list, index
))
}
_ => None,
}
}
fn emit_mir_builtin_call(
name: &str,
args: &[Spanned<MirExpr>],
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
if let Some(fused) = try_emit_mir_fusion(name, args, ctx) {
return Some(fused);
}
macro_rules! arg {
($i:expr) => {
emit_mir_expr(&args[$i], ctx)?
};
}
macro_rules! clone {
($i:expr) => {
mir_clone_arg(emit_mir_expr(&args[$i], ctx)?, &args[$i].node, ctx)
};
}
let result = match name {
"Result.Ok" => format!("Ok({})", clone!(0)),
"Result.Err" => format!("Err({})", clone!(0)),
"Result.withDefault" => format!("{}.unwrap_or({})", clone!(0), clone!(1)),
"Option.Some" => format!("Some({})", clone!(0)),
"Option.withDefault" => format!("{}.unwrap_or({})", clone!(0), clone!(1)),
"Option.toResult" => format!("{}.ok_or({})", clone!(0), clone!(1)),
"Int.abs" => format!("{}.abs()", arg!(0)),
"Int.fromFloat" => format!("({} as i64)", arg!(0)),
"Int.fromString" => {
let s = arg!(0);
format!(
"{{ let __s = &({s}); __s.parse::<i64>().map_err(|_| format!(\"Cannot parse '{{}}' as Int\", __s)) }}"
)
}
"Int.min" => format!("{}.min({})", arg!(0), arg!(1)),
"Int.max" => format!("{}.max({})", arg!(0), arg!(1)),
"Int.mod" => {
let a = arg!(0);
let b = arg!(1);
format!(
"if ({b}) == 0i64 {{ Err(\"division by zero\".to_string()) }} else {{ Ok(({a}).rem_euclid({b})) }}"
)
}
"Int.div" => {
let a = arg!(0);
let b = arg!(1);
format!(
"if ({b}) == 0i64 {{ Err(\"division by zero\".to_string()) }} else {{ match ({a}).checked_div_euclid({b}) {{ Some(__q) => Ok(__q), None => Err(\"division overflow\".to_string()) }} }}"
)
}
"Float.abs" => format!("{}.abs()", arg!(0)),
"Float.round" => format!("{}.round() as i64", arg!(0)),
"Float.floor" => format!("{}.floor() as i64", arg!(0)),
"Float.ceil" => format!("{}.ceil() as i64", arg!(0)),
"Float.fromString" => {
let s = arg!(0);
format!(
"{{ let __s = &({s}); __s.parse::<f64>().map_err(|_| format!(\"Cannot parse '{{}}' as Float\", __s)) }}"
)
}
"Float.sqrt" => format!("{}.sqrt()", arg!(0)),
"Float.pow" => format!("{}.powf({})", arg!(0), arg!(1)),
"Float.min" => format!("{}.min({})", arg!(0), arg!(1)),
"Float.max" => format!("{}.max({})", arg!(0), arg!(1)),
"Float.sin" => format!("{}.sin()", arg!(0)),
"Float.cos" => format!("{}.cos()", arg!(0)),
"Float.atan2" => format!("{}.atan2({})", arg!(0), arg!(1)),
"Float.pi" => "std::f64::consts::PI".to_string(),
"Float.fromInt" => format!("{} as f64", arg!(0)),
"String.fromInt" => format!("{}.to_string()", arg!(0)),
"String.fromFloat" => format!("{}.to_string()", arg!(0)),
"String.fromBool" => format!("{}.to_string()", arg!(0)),
"String.charAt" => {
let s = arg!(0);
let idx = arg!(1);
format!("{}.chars().nth({} as usize).map(|c| c.to_string())", s, idx)
}
"String.len" => format!("({}.chars().count() as i64)", arg!(0)),
"String.slice" => {
let s = arg!(0);
let from = arg!(1);
let to = arg!(2);
format!("aver_rt::string_slice(&{}, {}, {})", s, from, to)
}
"String.contains" => {
let s = arg!(0);
let sub = mir_str_arg_or_deref(&args[1], ctx)?;
format!("{}.contains({})", s, sub)
}
"String.startsWith" => {
let s = arg!(0);
let prefix = mir_str_arg_or_deref(&args[1], ctx)?;
format!("{}.starts_with({})", s, prefix)
}
"String.endsWith" => {
let s = arg!(0);
let suffix = mir_str_arg_or_deref(&args[1], ctx)?;
format!("{}.ends_with({})", s, suffix)
}
"String.trim" => format!("{}.trim().to_string()", arg!(0)),
"String.toUpper" => format!("{}.to_uppercase()", arg!(0)),
"String.toLower" => format!("{}.to_lowercase()", arg!(0)),
"String.split" => {
let s = arg!(0);
let delim = arg!(1);
format!(
"aver_rt::AverList::from_vec({}.split(&*{}).map(|s| s.to_string()).collect::<Vec<_>>())",
s, delim
)
}
"String.join" => {
let parts = arg!(0);
let delim = arg!(1);
format!("aver_rt::string_join(&{}, &{})", parts, delim)
}
"String.replace" => {
let s = arg!(0);
let from = arg!(1);
let to = arg!(2);
format!("{}.replace(&*{}, &*{})", s, from, to)
}
"String.chars" => format!(
"aver_rt::AverList::from_vec({}.chars().map(|c| c.to_string()).collect::<Vec<_>>())",
arg!(0)
),
"String.repeat" => {
let s = arg!(0);
let n = arg!(1);
format!("{}.repeat({} as usize)", s, n)
}
"String.indexOf" => {
let s = arg!(0);
let sub = arg!(1);
format!("{}.find(&*{}).map(|i| i as i64).unwrap_or(-1i64)", s, sub)
}
"String.byteLength" => format!("({}.len() as i64)", arg!(0)),
"List.len" => {
if let MirExpr::List(items) = &args[0].node
&& items.is_empty()
{
"0i64".to_string()
} else {
format!("({}.len() as i64)", arg!(0))
}
}
"List.prepend" => format!("aver_rt::AverList::prepend({}, &{})", clone!(0), clone!(1)),
"List.take" => {
let list = arg!(0);
let count = arg!(1);
format!(
"{{ let __n = if ({count}) <= 0 {{ 0usize }} else {{ usize::try_from({count}).unwrap_or(usize::MAX) }}; aver_rt::AverList::from_vec(({list}).iter().take(__n).cloned().collect::<Vec<_>>()) }}"
)
}
"List.drop" => {
let list = arg!(0);
let count = arg!(1);
format!(
"{{ let __n = if ({count}) <= 0 {{ 0usize }} else {{ usize::try_from({count}).unwrap_or(usize::MAX) }}; aver_rt::AverList::from_vec(({list}).iter().skip(__n).cloned().collect::<Vec<_>>()) }}"
)
}
"List.concat" => format!("aver_rt::AverList::concat(&{}, &{})", clone!(0), clone!(1)),
"List.reverse" => format!("{}.reverse()", arg!(0)),
"List.contains" => {
let list = arg!(0);
let item = arg!(1);
format!("{}.contains(&{})", list, item)
}
"List.zip" => {
let a = arg!(0);
let b = arg!(1);
format!(
"aver_rt::AverList::from_vec({}.iter().zip({}.iter()).map(|(a, b)| (a.clone(), b.clone())).collect::<Vec<_>>())",
a, b
)
}
"List.fromVector" => format!("{}.to_list()", arg!(0)),
"Map.fromList" => format!(
"{{ let mut m = HashMap::new(); for (k, v) in {}.iter().cloned() {{ m = m.insert_owned(k, v); }} m }}",
clone!(0)
),
"Map.entries" => format!(
"{{ let mut es: Vec<_> = {}.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); es.sort_by(|a, b| a.0.cmp(&b.0)); aver_rt::AverList::from_vec(es) }}",
arg!(0)
),
"Map.get" => {
let map = arg!(0);
let key = arg!(1);
format!("{}.get(&{}).cloned()", map, key)
}
"Map.set" => format!("{}.insert_owned({}, {})", clone!(0), clone!(1), clone!(2)),
"Map.has" => {
let map = arg!(0);
let key = arg!(1);
format!("{}.contains_key(&{})", map, key)
}
"Map.remove" => {
let map = clone!(0);
let key = arg!(1);
format!("{}.remove_owned(&{})", map, key)
}
"Map.keys" => format!(
"{{ let mut ks: Vec<_> = {}.keys().cloned().collect(); ks.sort(); aver_rt::AverList::from_vec(ks) }}",
arg!(0)
),
"Map.values" => format!(
"aver_rt::AverList::from_vec({}.values().cloned().collect::<Vec<_>>())",
arg!(0)
),
"Map.len" => format!("({}.len() as i64)", arg!(0)),
"Bool.or" => format!("({} || {})", arg!(0), arg!(1)),
"Bool.and" => format!("({} && {})", arg!(0), arg!(1)),
"Bool.not" => format!("(!{})", arg!(0)),
"Char.toCode" => format!(
"({}.chars().next().map(|c| c as i64).unwrap_or(0i64))",
arg!(0)
),
"Char.fromCode" => format!("char::from_u32({} as u32).map(|c| c.to_string())", arg!(0)),
"Byte.toHex" => format!(
"{{ let __n = {}; if (0i64..=255i64).contains(&__n) {{ Ok(format!(\"{{:02x}}\", __n as u8)) }} else {{ Err(format!(\"Byte.toHex: {{}} is out of range 0–255\", __n)) }} }}",
arg!(0)
),
"Byte.fromHex" => format!(
"{{ let __s = {}; if __s.len() != 2 {{ Err(format!(\"Byte.fromHex: expected exactly 2 hex chars, got '{{}}'\", __s)) }} else {{ u8::from_str_radix(&__s, 16).map(|n| n as i64).map_err(|_| format!(\"Byte.fromHex: invalid hex '{{}}'\", __s)) }} }}",
arg!(0)
),
"Vector.new" => {
let size = arg!(0);
let default = clone!(1);
format!("aver_rt::AverVector::new({} as usize, {})", size, default)
}
"Vector.get" => {
let vec = arg!(0);
let idx = arg!(1);
format!("{}.get({} as usize).cloned()", vec, idx)
}
"Vector.set" => {
let vec = clone!(0);
let idx = arg!(1);
let val = clone!(2);
format!("{}.set_owned({} as usize, {})", vec, idx, val)
}
"Vector.len" => format!("({}.len() as i64)", arg!(0)),
"Vector.fromList" => format!("aver_rt::AverVector::from_vec({}.to_vec())", arg!(0)),
"BranchPath.child" => {
let path = mir_borrow_arg(emit_mir_expr(&args[0], ctx)?, &args[0].node, ctx);
let idx = arg!(1);
format!("aver_rt::BranchPath::child({}, {})", path, idx)
}
"BranchPath.parse" => {
let raw = mir_str_arg_or_deref(&args[0], ctx)?;
format!("aver_rt::BranchPath::parse({})", raw)
}
_ => return None,
};
if super::builtins::builtin_needs_str_conversion(name) {
Some(format!("({}).into_aver()", result))
} else {
Some(result)
}
}
fn emit_mir_effectful_builtin_call(
name: &str,
args: &[Spanned<MirExpr>],
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
let codegen = ctx.codegen?;
if codegen.emit_replay_runtime {
let mut arg_clones = Vec::with_capacity(args.len());
for a in args {
arg_clones.push(mir_clone_arg(emit_mir_expr(a, ctx)?, &a.node, ctx));
}
return super::builtins::compose_replay_effect_call(name, &arg_clones);
}
let mut arg_strs = Vec::with_capacity(args.len());
for a in args {
arg_strs.push(emit_mir_expr(a, ctx)?);
}
let result = super::builtins::compose_effectful_builtin_raw(name, &arg_strs)?;
let result = if super::builtins::builtin_needs_str_conversion(name) {
format!("({}).into_aver()", result)
} else {
result
};
let policy_active = codegen.policy.is_some() && !codegen.emit_replay_runtime;
let first_arg = if policy_active && !args.is_empty() {
Some(emit_mir_expr(&args[0], ctx)?)
} else {
None
};
Some(super::builtins::compose_effect_wrap(
name,
result,
policy_active,
first_arg,
))
}
fn emit_mir_intrinsic_call(
intrinsic: BuiltinIntrinsic,
args: &[Spanned<MirExpr>],
ctx: &MirEmitCtx<'_>,
) -> Option<String> {
match intrinsic {
BuiltinIntrinsic::BufNew => {
let cap = emit_mir_expr(&args[0], ctx)?;
Some(format!(
"aver_rt::Buffer::with_capacity(({}) as usize)",
cap
))
}
BuiltinIntrinsic::BufAppend => {
let buf = emit_mir_expr(&args[0], ctx)?;
let s = emit_mir_expr(&args[1], ctx)?;
Some(format!(
"{{ let mut __b = {}; __b.push_str(&{}); __b }}",
buf, s
))
}
BuiltinIntrinsic::BufAppendSepUnlessFirst => {
let buf = emit_mir_expr(&args[0], ctx)?;
let sep = emit_mir_expr(&args[1], ctx)?;
Some(format!(
"{{ let mut __b = {}; if !__b.is_empty() {{ __b.push_str(&{}); }} __b }}",
buf, sep
))
}
BuiltinIntrinsic::BufFinalize => {
let buf = emit_mir_expr(&args[0], ctx)?;
Some(format!("aver_rt::AverStr::from({})", buf))
}
BuiltinIntrinsic::ToStr => {
let arg = emit_mir_expr(&args[0], ctx)?;
Some(format!(
"aver_rt::AverStr::from(aver_rt::aver_display(&({})))",
arg
))
}
BuiltinIntrinsic::IntDivEuclid => {
let a = emit_mir_expr(&args[0], ctx)?;
let b = emit_mir_expr(&args[1], ctx)?;
Some(format!("({}).div_euclid({})", a, b))
}
BuiltinIntrinsic::IntModEuclid => {
let a = emit_mir_expr(&args[0], ctx)?;
let b = emit_mir_expr(&args[1], ctx)?;
Some(format!("({}).rem_euclid({})", a, b))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::SymbolTable;
use crate::ir::mir::{LocalId, MirBinOp, MirCall, MirExpr, MirLocal};
use std::sync::OnceLock;
fn span<T>(node: T) -> Spanned<T> {
Spanned {
node,
line: 0,
ty: OnceLock::new(),
}
}
fn span_ty<T>(node: T, ty: Type) -> Spanned<T> {
let stamp = OnceLock::new();
let _ = stamp.set(ty);
Spanned {
node,
line: 0,
ty: stamp,
}
}
fn empty_ctx() -> MirEmitCtx<'static> {
use std::sync::OnceLock;
static SYMBOLS: OnceLock<SymbolTable> = OnceLock::new();
static PREFIXES: OnceLock<HashSet<String>> = OnceLock::new();
MirEmitCtx::for_test(
SYMBOLS.get_or_init(SymbolTable::default),
PREFIXES.get_or_init(HashSet::new),
)
}
#[test]
fn emits_int_literal_as_i64_suffix() {
let lit = span(MirExpr::Literal(span(crate::ast::Literal::Int(42))));
assert_eq!(emit_mir_expr(&lit, &empty_ctx()).as_deref(), Some("42i64"));
}
#[test]
fn emits_local_via_aver_name_to_rust() {
let local = MirLocal {
slot: LocalId(0),
last_use: false,
name: "x".to_string(),
};
let expr = span(MirExpr::Local(span(local)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("local should emit");
assert!(
emit.contains("x"),
"local emit should reference `x`: {emit}"
);
}
#[test]
fn returns_none_for_synthetic_local() {
let local = MirLocal {
slot: LocalId(7),
last_use: false,
name: String::new(),
};
let expr = span(MirExpr::Local(span(local)));
assert!(emit_mir_expr(&expr, &empty_ctx()).is_none());
}
#[test]
fn empty_fn_policy_has_no_anchor() {
let policy = MirFnEmitPolicy::empty();
assert!(policy.local_types.is_empty());
assert!(policy.rc_wrapped.is_empty());
assert!(policy.borrowed_params.is_empty());
assert!(policy.current_module_scope.is_none());
}
#[test]
fn program_level_ctx_renders_free_expr() {
let policy = MirFnEmitPolicy::empty();
use std::sync::OnceLock;
static SYMBOLS: OnceLock<SymbolTable> = OnceLock::new();
static PREFIXES: OnceLock<HashSet<String>> = OnceLock::new();
static BUILTINS: OnceLock<Vec<String>> = OnceLock::new();
let ctx = MirEmitCtx {
symbol_table: SYMBOLS.get_or_init(SymbolTable::default),
module_prefixes: PREFIXES.get_or_init(HashSet::new),
codegen: None,
local_types: &policy.local_types,
rc_wrapped: &policy.rc_wrapped,
borrowed_params: &policy.borrowed_params,
owned_params: &policy.owned_params,
current_module_scope: policy.current_module_scope.as_deref(),
mir_builtins: BUILTINS.get_or_init(Vec::new),
};
let lit = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
assert_eq!(emit_mir_expr(&lit, &ctx).as_deref(), Some("7i64"));
}
#[test]
fn main_body_policy_borrows_by_default_like_hir() {
let resolved = crate::ir::hir::ResolvedFnDef {
fn_id: crate::ir::FnId(0),
name: "main".to_string(),
line: 1,
params: vec![
("xs".to_string(), Type::List(Box::new(Type::Int))),
("n".to_string(), Type::Int),
],
return_type: Type::Unit,
effects: vec![],
desc: None,
body: std::sync::Arc::new(crate::ir::hir::ResolvedFnBody::Block(vec![])),
resolution: None,
};
let policy = MirFnEmitPolicy::from_resolved(&resolved, None, true);
assert!(policy.borrowed_params.contains("xs"));
assert!(!policy.borrowed_params.contains("n"));
assert!(policy.current_module_scope.is_none());
}
#[test]
fn emits_int_binop_add_as_plus() {
let x = MirLocal {
slot: LocalId(0),
last_use: false,
name: "x".to_string(),
};
let bop = MirBinOp {
op: BinOp::Add,
lhs: Box::new(span_ty(MirExpr::Local(span(x.clone())), Type::Int)),
rhs: Box::new(span_ty(MirExpr::Local(span(x)), Type::Int)),
};
let expr = span(MirExpr::BinOp(span(bop)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("binop should emit");
assert!(
emit.contains(" + ") && !emit.contains(" + &"),
"Int+Int should emit plain `+`, got: {emit}"
);
}
#[test]
fn emits_str_binop_add_as_concat() {
let s = MirLocal {
slot: LocalId(0),
last_use: false,
name: "s".to_string(),
};
let bop = MirBinOp {
op: BinOp::Add,
lhs: Box::new(span_ty(MirExpr::Local(span(s.clone())), Type::Str)),
rhs: Box::new(span_ty(MirExpr::Local(span(s)), Type::Str)),
};
let expr = span(MirExpr::BinOp(span(bop)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("binop should emit");
assert!(
emit.contains(" + &"),
"Str+Str should emit `+ &` for AverStr concat: {emit}"
);
}
#[test]
fn emits_neg_as_paren_minus_inner() {
let inner = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let expr = span(MirExpr::Neg(Box::new(inner)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("neg should emit");
assert_eq!(emit, "(-7i64)");
}
#[test]
fn returns_none_for_builtin_call_without_table() {
let call = MirCall {
callee: MirCallee::Builtin(crate::ir::BuiltinId(0)),
args: vec![span(MirExpr::Literal(span(crate::ast::Literal::Str(
"hello".to_string(),
))))],
};
let expr = span(MirExpr::Call(span(call)));
assert!(emit_mir_expr(&expr, &empty_ctx()).is_none());
}
fn ctx_with_builtin(name: &str) -> MirEmitCtx<'static> {
use std::sync::OnceLock;
static SYMBOLS: OnceLock<SymbolTable> = OnceLock::new();
static PREFIXES: OnceLock<HashSet<String>> = OnceLock::new();
let builtins: &'static [String] = Box::leak(vec![name.to_string()].into_boxed_slice());
let mut ctx = MirEmitCtx::for_test(
SYMBOLS.get_or_init(SymbolTable::default),
PREFIXES.get_or_init(HashSet::new),
);
ctx.mir_builtins = builtins;
ctx
}
fn int_lit(n: i64) -> Spanned<MirExpr> {
span_ty(
MirExpr::Literal(span(crate::ast::Literal::Int(n))),
Type::Int,
)
}
#[test]
fn emits_pure_builtin_int_mod_with_into_aver() {
let call = MirCall {
callee: MirCallee::Builtin(crate::ir::BuiltinId(0)),
args: vec![int_lit(7), int_lit(3)],
};
let expr = span(MirExpr::Call(span(call)));
let emit = emit_mir_expr(&expr, &ctx_with_builtin("Int.mod")).expect("Int.mod emits");
assert_eq!(
emit,
"(if (3i64) == 0i64 { Err(\"division by zero\".to_string()) } else { Ok((7i64).rem_euclid(3i64)) }).into_aver()"
);
}
#[test]
fn emits_pure_builtin_bool_or() {
let call = MirCall {
callee: MirCallee::Builtin(crate::ir::BuiltinId(0)),
args: vec![
span_ty(
MirExpr::Literal(span(crate::ast::Literal::Bool(true))),
Type::Bool,
),
span_ty(
MirExpr::Literal(span(crate::ast::Literal::Bool(false))),
Type::Bool,
),
],
};
let expr = span(MirExpr::Call(span(call)));
let emit = emit_mir_expr(&expr, &ctx_with_builtin("Bool.or")).expect("Bool.or emits");
assert_eq!(emit, "(true || false)");
}
#[test]
fn effectful_builtin_returns_none_without_codegen_ctx() {
let call = MirCall {
callee: MirCallee::Builtin(crate::ir::BuiltinId(0)),
args: vec![span(MirExpr::Literal(span(crate::ast::Literal::Str(
"hi".to_string(),
))))],
};
let expr = span(MirExpr::Call(span(call)));
assert!(
emit_mir_expr(&expr, &ctx_with_builtin("Console.print")).is_none(),
"effectful Console.print needs a CodegenContext; without one it \
falls back to HIR"
);
}
#[test]
fn emits_buf_finalize_intrinsic() {
let buf = MirLocal {
slot: LocalId(0),
last_use: true,
name: "b".to_string(),
};
let call = MirCall {
callee: MirCallee::Intrinsic(BuiltinIntrinsic::BufFinalize),
args: vec![span(MirExpr::Local(span(buf)))],
};
let expr = span(MirExpr::Call(span(call)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("__buf_finalize emits");
assert_eq!(emit, "aver_rt::AverStr::from(b)");
}
#[test]
fn emits_return_keyword() {
let inner = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let expr = span(MirExpr::Return(Box::new(inner)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("return should emit");
assert_eq!(emit, "return 7i64");
}
fn symbols_with_one_type(name: &str, scoped: bool) -> SymbolTable {
use crate::ir::ModuleId;
use crate::ir::identity::TypeKey;
use crate::ir::symbol_table::{ModuleEntry, TypeEntry};
let mut st = SymbolTable::default();
st.modules.push(ModuleEntry { prefix: None });
let key = if scoped {
TypeKey::in_module("Tcp", name)
} else {
TypeKey::entry(name)
};
st.types.push(TypeEntry {
key,
module: ModuleId(0),
index_in_module: 0,
variants: vec![],
is_product: true,
});
st
}
#[test]
fn emits_record_create_unscoped() {
let field_x = crate::ir::mir::MirRecordField {
name: "x".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(1)))),
};
let field_y = crate::ir::mir::MirRecordField {
name: "y".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(2)))),
};
let rec = crate::ir::mir::MirRecordCreate {
type_id: Some(crate::ir::TypeId(0)),
type_name: "Point".to_string(),
fields: vec![field_x, field_y],
};
let expr = span(MirExpr::RecordCreate(span(rec)));
let st = symbols_with_one_type("Point", false);
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("record create should emit");
assert_eq!(emit, "Point { x: 1i64, y: 2i64 }");
}
#[test]
fn emits_tcp_connection_record_with_rename() {
let rec = crate::ir::mir::MirRecordCreate {
type_id: Some(crate::ir::TypeId(0)),
type_name: "Tcp.Connection".to_string(),
fields: vec![],
};
let expr = span(MirExpr::RecordCreate(span(rec)));
let st = symbols_with_one_type("Connection", true);
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("tcp connection record should emit");
assert_eq!(emit, "Tcp_Connection { }");
}
#[test]
fn emits_terminal_size_record_with_rename() {
let field_w = crate::ir::mir::MirRecordField {
name: "width".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(80)))),
};
let field_h = crate::ir::mir::MirRecordField {
name: "height".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(24)))),
};
let rec = crate::ir::mir::MirRecordCreate {
type_id: Some(crate::ir::TypeId(0)),
type_name: "Terminal.Size".to_string(),
fields: vec![field_w, field_h],
};
let expr = span(MirExpr::RecordCreate(span(rec)));
let st = symbols_with_one_type("Size", true);
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("terminal size record should emit");
assert_eq!(emit, "Terminal_Size { width: 80i64, height: 24i64 }");
}
#[test]
fn emits_record_create_dep_module_as_bare_name() {
let field = crate::ir::mir::MirRecordField {
name: "tag".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(1)))),
};
let rec = crate::ir::mir::MirRecordCreate {
type_id: Some(crate::ir::TypeId(0)),
type_name: "Expr".to_string(),
fields: vec![field],
};
let expr = span(MirExpr::RecordCreate(span(rec)));
use crate::ir::ModuleId;
use crate::ir::identity::TypeKey;
use crate::ir::symbol_table::{ModuleEntry, TypeEntry};
let mut st = SymbolTable::default();
st.modules.push(ModuleEntry { prefix: None });
st.types.push(TypeEntry {
key: TypeKey::in_module("ast", "Expr"),
module: ModuleId(0),
index_in_module: 0,
variants: vec![],
is_product: true,
});
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("dep-module record should emit");
assert_eq!(emit, "Expr { tag: 1i64 }");
}
#[test]
fn emits_record_create_dep_module_qualified_when_prefix_registered() {
let field = crate::ir::mir::MirRecordField {
name: "id".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(1)))),
};
let rec = crate::ir::mir::MirRecordCreate {
type_id: Some(crate::ir::TypeId(0)),
type_name: "Note".to_string(),
fields: vec![field],
};
let expr = span(MirExpr::RecordCreate(span(rec)));
use crate::ir::ModuleId;
use crate::ir::identity::TypeKey;
use crate::ir::symbol_table::{ModuleEntry, TypeEntry};
let mut st = SymbolTable::default();
st.modules.push(ModuleEntry { prefix: None });
st.types.push(TypeEntry {
key: TypeKey::in_module("Apps.Notepad.Store", "Note"),
module: ModuleId(0),
index_in_module: 0,
variants: vec![],
is_product: true,
});
let mut prefixes = HashSet::new();
prefixes.insert("Apps.Notepad.Store".to_string());
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("qualified dep-module record should emit");
assert_eq!(
emit,
"crate::aver_generated::apps::notepad::store::Note { id: 1i64 }"
);
}
#[test]
fn emits_record_update_unscoped() {
let base = MirLocal {
slot: LocalId(0),
last_use: false,
name: "base".to_string(),
};
let update = crate::ir::mir::MirRecordField {
name: "x".to_string(),
value: span(MirExpr::Literal(span(crate::ast::Literal::Int(9)))),
};
let upd = crate::ir::mir::MirRecordUpdate {
base: Box::new(span(MirExpr::Local(span(base)))),
type_id: Some(crate::ir::TypeId(0)),
type_name: "Point".to_string(),
updates: vec![update],
};
let expr = span(MirExpr::RecordUpdate(span(upd)));
let st = symbols_with_one_type("Point", false);
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("record update should emit");
assert_eq!(emit, "Point { x: 9i64, ..base.clone() }");
}
fn symbols_with_one_fn(name: &str) -> SymbolTable {
use crate::ir::ModuleId;
use crate::ir::identity::FnKey;
use crate::ir::symbol_table::{FnEntry, ModuleEntry};
let mut st = SymbolTable::default();
st.modules.push(ModuleEntry { prefix: None });
st.fns.push(FnEntry {
key: FnKey::entry(name),
module: ModuleId(0),
index_in_module: 0,
});
st
}
#[test]
fn emits_tail_call_as_regular_call() {
let arg = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let tc = span(MirExpr::TailCall(span(crate::ir::mir::MirTailCall {
target: crate::ir::FnId(0),
args: vec![arg],
})));
let st = symbols_with_one_fn("loop_step");
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&tc, &ctx).expect("tail call should emit");
assert_eq!(emit, "loop_step(7i64)");
}
#[test]
fn returns_none_for_unsupported_variant() {
let interp = span(MirExpr::InterpolatedStr(vec![
crate::ir::mir::MirStrPart::Literal("x".to_string()),
]));
assert!(emit_mir_expr(&interp, &empty_ctx()).is_none());
}
#[test]
fn emits_empty_map_as_hashmap_new() {
let expr = span(MirExpr::MapLiteral(vec![]));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("map should emit");
assert_eq!(emit, "HashMap::new()");
}
#[test]
fn emits_nonempty_map_as_vec_into_iter_collect() {
let k1 = span(MirExpr::Literal(span(crate::ast::Literal::Int(1))));
let v1 = span(MirExpr::Literal(span(crate::ast::Literal::Int(10))));
let k2 = span(MirExpr::Literal(span(crate::ast::Literal::Int(2))));
let v2 = span(MirExpr::Literal(span(crate::ast::Literal::Int(20))));
let expr = span(MirExpr::MapLiteral(vec![(k1, v1), (k2, v2)]));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("map should emit");
assert_eq!(
emit,
"vec![(1i64, 10i64), (2i64, 20i64)].into_iter().collect::<HashMap<_, _>>()"
);
}
#[test]
fn emits_try_as_question_mark() {
let inner = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let expr = span(MirExpr::Try(Box::new(inner)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("try should emit");
assert_eq!(emit, "7i64?");
}
#[test]
fn emits_tuple_literal_as_paren_list() {
let a = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let b = span(MirExpr::Literal(span(crate::ast::Literal::Int(9))));
let expr = span(MirExpr::Tuple(vec![a, b]));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("tuple should emit");
assert_eq!(emit, "(7i64, 9i64)");
}
#[test]
fn emits_bare_independent_product_as_parallel_tuple() {
let a = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let b = span(MirExpr::Literal(span(crate::ast::Literal::Int(9))));
let expr = span(MirExpr::IndependentProduct(span(
crate::ir::mir::MirIndependentProduct {
items: vec![a, b],
unwrap_results: false,
},
)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("bare product should emit");
assert_eq!(
emit,
"std::thread::scope(|_s| { let _h0 = _s.spawn(move || 7i64); \
let _h1 = _s.spawn(move || 9i64); let _r0 = _h0.join().unwrap(); \
let _r1 = _h1.join().unwrap(); (_r0, _r1) }) "
);
}
#[test]
fn emits_unwrap_independent_product_with_cancel_flag() {
let a = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let b = span(MirExpr::Literal(span(crate::ast::Literal::Int(9))));
let expr = span(MirExpr::IndependentProduct(span(
crate::ir::mir::MirIndependentProduct {
items: vec![a, b],
unwrap_results: true,
},
)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("unwrap product should emit");
assert!(
emit.starts_with(
"{ let __cancel_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); \
std::thread::scope(|_s| { "
),
"got: {emit}"
);
assert!(
emit.contains("crate::run_cancelable_branch(__cancel_flag0"),
"got: {emit}"
);
assert!(
emit.contains("crate::run_cancelable_branch(__cancel_flag1"),
"got: {emit}"
);
assert!(
emit.contains("crate::ParallelBranch::Completed"),
"got: {emit}"
);
assert!(emit.trim_end().ends_with("})? }"), "got: {emit}");
}
#[test]
fn emits_empty_list_as_averlist_empty() {
let expr = span(MirExpr::List(vec![]));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("list should emit");
assert_eq!(emit, "aver_rt::AverList::empty()");
}
#[test]
fn emits_nonempty_list_as_from_vec() {
let a = span(MirExpr::Literal(span(crate::ast::Literal::Int(1))));
let b = span(MirExpr::Literal(span(crate::ast::Literal::Int(2))));
let expr = span(MirExpr::List(vec![a, b]));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("list should emit");
assert_eq!(emit, "aver_rt::AverList::from_vec(vec![1i64, 2i64])");
}
#[test]
fn emits_project_as_dotted_field() {
let local = MirLocal {
slot: LocalId(0),
last_use: false,
name: "user".to_string(),
};
let base = span(MirExpr::Local(span(local)));
let proj = crate::ir::mir::MirProject {
base: Box::new(base),
field: "name".to_string(),
};
let expr = span(MirExpr::Project(span(proj)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("project should emit");
assert!(
emit.ends_with(".name"),
"project should end with `.name`, got: {emit}"
);
}
#[test]
fn emits_result_ok_as_ok_call() {
let arg = span(MirExpr::Literal(span(crate::ast::Literal::Int(42))));
let con = crate::ir::mir::MirConstruct {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk),
args: vec![arg],
};
let expr = span(MirExpr::Construct(span(con)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("construct should emit");
assert_eq!(emit, "Ok(42i64)");
}
#[test]
fn emits_option_none_as_bare_none() {
let con = crate::ir::mir::MirConstruct {
ctor: MirCtor::Builtin(BuiltinCtor::OptionNone),
args: vec![],
};
let expr = span(MirExpr::Construct(span(con)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("construct should emit");
assert_eq!(emit, "None");
}
#[test]
fn emits_let_as_block_expr() {
let value = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let body_local = MirLocal {
slot: LocalId(0),
last_use: false,
name: "x".to_string(),
};
let body = span(MirExpr::Local(span(body_local)));
let let_node = crate::ir::mir::MirLet {
binding: LocalId(0),
binding_name: "x".to_string(),
value: Box::new(value),
body: Box::new(body),
};
let expr = span(MirExpr::Let(span(let_node)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("let should emit");
assert_eq!(emit, "{ let x = 7i64; x }");
}
#[test]
fn synthetic_let_emits_bare_statement_not_none() {
let value = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let body = span(MirExpr::Literal(span(crate::ast::Literal::Int(0))));
let let_node = crate::ir::mir::MirLet {
binding: LocalId(7),
binding_name: String::new(),
value: Box::new(value),
body: Box::new(body),
};
let expr = span(MirExpr::Let(span(let_node)));
assert_eq!(
emit_mir_expr(&expr, &empty_ctx()).as_deref(),
Some("{ 7i64; 0i64 }")
);
}
fn symbols_with_one_user_ctor(
scope_prefix: Option<&str>,
type_name: &str,
variant_name: &str,
) -> SymbolTable {
use crate::ir::ModuleId;
use crate::ir::identity::TypeKey;
use crate::ir::symbol_table::{CtorEntry, ModuleEntry, TypeEntry};
let mut st = SymbolTable::default();
st.modules.push(ModuleEntry { prefix: None });
let key = match scope_prefix {
Some(p) => TypeKey::in_module(p, type_name),
None => TypeKey::entry(type_name),
};
st.types.push(TypeEntry {
key,
module: ModuleId(0),
index_in_module: 0,
variants: vec![crate::ir::CtorId(0)],
is_product: false,
});
st.ctors.push(CtorEntry {
owning_type: crate::ir::TypeId(0),
name: variant_name.to_string(),
});
st
}
#[test]
fn emits_user_ctor_unscoped() {
let arg = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let con = crate::ir::mir::MirConstruct {
ctor: MirCtor::User(crate::ir::CtorId(0)),
args: vec![arg],
};
let expr = span(MirExpr::Construct(span(con)));
let st = symbols_with_one_user_ctor(None, "Shape", "Circle");
let prefixes = HashSet::new();
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("user ctor should emit");
assert_eq!(emit, "Shape::Circle(7i64)");
}
#[test]
fn emits_user_ctor_scoped_via_module_prefix() {
let arg = span(MirExpr::Literal(span(crate::ast::Literal::Int(1))));
let con = crate::ir::mir::MirConstruct {
ctor: MirCtor::User(crate::ir::CtorId(0)),
args: vec![arg],
};
let expr = span(MirExpr::Construct(span(con)));
let st = symbols_with_one_user_ctor(Some("ast"), "Expr", "App");
let mut prefixes = HashSet::new();
prefixes.insert("ast".to_string());
let ctx = MirEmitCtx::for_test(&st, &prefixes);
let emit = emit_mir_expr(&expr, &ctx).expect("scoped user ctor should emit");
assert_eq!(emit, "crate::aver_generated::ast::Expr::App(1i64)");
}
#[test]
fn first_blocker_names_a_top_level_match() {
let m = span(MirExpr::Match(span(crate::ir::mir::MirMatch {
subject: Box::new(span(MirExpr::Literal(span(crate::ast::Literal::Int(0))))),
arms: vec![],
})));
assert!(emit_mir_expr(&m, &empty_ctx()).is_none());
assert_eq!(first_blocker(&m, &empty_ctx()), Some("Match"));
}
#[test]
fn first_blocker_recurses_to_deepest_builtin_call() {
let call = span(MirExpr::Call(span(MirCall {
callee: MirCallee::Builtin(crate::ir::BuiltinId(0)),
args: vec![span(MirExpr::Literal(span(crate::ast::Literal::Int(1))))],
})));
let ret = span(MirExpr::Return(Box::new(call)));
assert!(emit_mir_expr(&ret, &empty_ctx()).is_none());
assert_eq!(first_blocker(&ret, &empty_ctx()), Some("Call(Builtin)"));
}
#[test]
fn first_blocker_is_none_for_fully_covered_body() {
let lit = span(MirExpr::Literal(span(crate::ast::Literal::Int(42))));
assert!(first_blocker(&lit, &empty_ctx()).is_none());
}
fn fn_with_body(fn_id: crate::ir::FnId, body: Spanned<MirExpr>) -> crate::ir::mir::MirFn {
crate::ir::mir::MirFn {
fn_id,
name: String::new(),
params: vec![],
return_type: String::new(),
effects: vec![],
body,
local_count: 0,
aliased_slots: std::sync::Arc::new(vec![]),
}
}
#[test]
fn coverage_with_blockers_counts_and_buckets() {
let mut program = MirProgram::default();
let covered_body = span(MirExpr::Literal(span(crate::ast::Literal::Int(7))));
let blocked_body = span(MirExpr::Match(span(crate::ir::mir::MirMatch {
subject: Box::new(span(MirExpr::Literal(span(crate::ast::Literal::Int(0))))),
arms: vec![],
})));
program.fns.insert(
crate::ir::FnId(0),
fn_with_body(crate::ir::FnId(0), covered_body),
);
program.fns.insert(
crate::ir::FnId(1),
fn_with_body(crate::ir::FnId(1), blocked_body),
);
let (report, blockers) = coverage_report_with_blockers(&program, &empty_ctx());
assert_eq!(report.total, 2);
assert_eq!(report.mir_covered, 1);
assert_eq!(report.hir_fallback, 1);
assert_eq!(blockers.get("Match"), Some(&1));
}
fn let_chain(
a: (&str, Spanned<MirExpr>),
b: (&str, Spanned<MirExpr>),
body: Spanned<MirExpr>,
) -> Spanned<MirExpr> {
let inner = MirExpr::Let(span(crate::ir::mir::MirLet {
binding: LocalId(1),
binding_name: b.0.to_string(),
value: Box::new(b.1),
body: Box::new(body),
}));
span(MirExpr::Let(span(crate::ir::mir::MirLet {
binding: LocalId(0),
binding_name: a.0.to_string(),
value: Box::new(a.1),
body: Box::new(span(inner)),
})))
}
#[test]
fn fn_body_emits_let_chain_as_flat_statement_lines() {
let a_local = MirLocal {
slot: LocalId(0),
last_use: true,
name: "a".to_string(),
};
let body = let_chain(
("a", int_lit(1)),
("b", int_lit(2)),
span(MirExpr::Local(span(a_local))),
);
let emit = emit_mir_fn_body(&body, &empty_ctx()).expect("let chain emits");
assert_eq!(
emit,
" crate::cancel_checkpoint();\n let a = 1i64;\n let b = 2i64;\n a"
);
}
#[test]
fn fn_body_emits_discarded_intermediate_as_bare_statement() {
let g_local = MirLocal {
slot: LocalId(0),
last_use: true,
name: "g".to_string(),
};
let body = let_chain(
("g", int_lit(1)),
("", int_lit(2)), span(MirExpr::Local(span(g_local))),
);
let emit = emit_mir_fn_body(&body, &empty_ctx()).expect("discarded stmt emits");
assert_eq!(
emit,
" crate::cancel_checkpoint();\n let g = 1i64;\n 2i64;\n g"
);
}
#[test]
fn fn_body_emits_leading_discarded_statement() {
let g_local = MirLocal {
slot: LocalId(1),
last_use: true,
name: "g".to_string(),
};
let body = let_chain(
("", int_lit(1)), ("g", int_lit(2)),
span(MirExpr::Local(span(g_local))),
);
let emit = emit_mir_fn_body(&body, &empty_ctx()).expect("leading discard emits");
assert_eq!(
emit,
" crate::cancel_checkpoint();\n 1i64;\n let g = 2i64;\n g"
);
}
#[test]
fn inline_discarded_let_renders_as_bare_block_statement() {
let value = int_lit(7);
let body_local = MirLocal {
slot: LocalId(0),
last_use: true,
name: "x".to_string(),
};
let let_node = crate::ir::mir::MirLet {
binding: LocalId(0),
binding_name: String::new(),
value: Box::new(value),
body: Box::new(span(MirExpr::Local(span(body_local)))),
};
let expr = span(MirExpr::Let(span(let_node)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("inline discard emits");
assert_eq!(emit, "{ 7i64; x }");
}
#[test]
fn inline_let_still_renders_as_block_expr() {
let value = int_lit(7);
let body_local = MirLocal {
slot: LocalId(0),
last_use: true,
name: "x".to_string(),
};
let let_node = crate::ir::mir::MirLet {
binding: LocalId(0),
binding_name: "x".to_string(),
value: Box::new(value),
body: Box::new(span(MirExpr::Local(span(body_local)))),
};
let expr = span(MirExpr::Let(span(let_node)));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("inline let emits");
assert_eq!(emit, "{ let x = 7i64; x }");
}
#[test]
fn neg_folded_int_literal_re_wraps_like_hir_neg() {
let expr = span(MirExpr::Literal(span(crate::ast::Literal::Int(-5))));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("neg int literal emits");
assert_eq!(emit, "(-5i64)");
}
#[test]
fn neg_folded_float_literal_re_wraps_like_hir_neg() {
let expr = span(MirExpr::Literal(span(crate::ast::Literal::Float(-273.15))));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("neg float literal emits");
assert_eq!(emit, "(-273.15f64)");
}
#[test]
fn positive_literals_unchanged_by_neg_rewrap() {
let i = span(MirExpr::Literal(span(crate::ast::Literal::Int(5))));
assert_eq!(emit_mir_expr(&i, &empty_ctx()).as_deref(), Some("5i64"));
let f = span(MirExpr::Literal(span(crate::ast::Literal::Float(1.5))));
assert_eq!(emit_mir_expr(&f, &empty_ctx()).as_deref(), Some("1.5f64"));
}
fn if_compare(op: BinOp) -> Spanned<MirExpr> {
let lhs = MirLocal {
slot: LocalId(0),
last_use: false,
name: "code".to_string(),
};
let cond = MirExpr::BinOp(span(crate::ir::mir::MirBinOp {
op,
lhs: Box::new(span_ty(MirExpr::Local(span(lhs)), Type::Int)),
rhs: Box::new(int_lit(48)),
}));
span(MirExpr::IfThenElse(span(crate::ir::mir::MirIfThenElse {
cond: Box::new(span(cond)),
then_branch: Box::new(int_lit(1)),
else_branch: Box::new(int_lit(0)),
})))
}
#[test]
fn if_then_else_keeps_lt_canonical_no_swap() {
let emit = emit_mir_expr(&if_compare(BinOp::Lt), &empty_ctx()).expect("if emits");
assert_eq!(emit, "if (code < 48i64) { 1i64 } else { 0i64 }");
}
#[test]
fn if_then_else_inverts_gte_to_lt_and_swaps_branches() {
let emit = emit_mir_expr(&if_compare(BinOp::Gte), &empty_ctx()).expect("if emits");
assert_eq!(emit, "if (code < 48i64) { 0i64 } else { 1i64 }");
}
#[test]
fn if_then_else_inverts_lte_to_gt_and_swaps_branches() {
let emit = emit_mir_expr(&if_compare(BinOp::Lte), &empty_ctx()).expect("if emits");
assert_eq!(emit, "if (code > 48i64) { 0i64 } else { 1i64 }");
}
#[test]
fn if_then_else_inverts_neq_to_eq_and_swaps_branches() {
let emit = emit_mir_expr(&if_compare(BinOp::Neq), &empty_ctx()).expect("if emits");
assert_eq!(emit, "if (code == 48i64) { 0i64 } else { 1i64 }");
}
#[test]
fn if_then_else_cond_does_not_deref_string_literal() {
let name = MirLocal {
slot: LocalId(0),
last_use: false,
name: "name".to_string(),
};
let cond = MirExpr::BinOp(span(crate::ir::mir::MirBinOp {
op: BinOp::Eq,
lhs: Box::new(span_ty(MirExpr::Local(span(name)), Type::Str)),
rhs: Box::new(span_ty(
MirExpr::Literal(span(crate::ast::Literal::Str("_".to_string()))),
Type::Str,
)),
}));
let expr = span(MirExpr::IfThenElse(span(crate::ir::mir::MirIfThenElse {
cond: Box::new(span(cond)),
then_branch: Box::new(int_lit(1)),
else_branch: Box::new(int_lit(0)),
})));
let emit = emit_mir_expr(&expr, &empty_ctx()).expect("if emits");
assert_eq!(
emit,
"if (name == AverStr::from(\"_\")) { 1i64 } else { 0i64 }"
);
}
}