use super::builtins;
use super::emit_ctx::{EmitCtx, expr_can_move, expr_skip_clone, should_borrow_param};
use super::pattern::emit_pattern;
use crate::ast::{BinOp, Literal, Spanned, TypeDef};
use crate::codegen::CodegenContext;
use crate::codegen::common::{module_prefix_to_rust_path, resolve_module_call};
use crate::ir::hir::{
BuiltinIntrinsic, ResolvedBodyExprPlan, ResolvedBodyPlan, ResolvedBoolSubjectPlan,
ResolvedCallee, ResolvedCtor, ResolvedExpr, ResolvedFnDef, ResolvedLeafOp, ResolvedMatchArm,
ResolvedPattern, ResolvedStmt, ResolvedThinBodyPlan, ThinKind, call_plan_from_resolved_callee,
classify_body_expr_plan_resolved, classify_body_plan_resolved,
classify_bool_subject_plan_resolved, classify_leaf_op_resolved,
classify_list_match_shape_resolved, classify_match_dispatch_plan_resolved,
classify_thin_fn_def_resolved, resolved_to_dotted, semantic_constructor_from_resolved_ctor,
};
use crate::ir::vars::resolved_pattern_bindings;
use crate::ir::{
BoolCompareOp, CallPlan, DispatchArmPlan, DispatchBindingPlan, DispatchDefaultPlan,
DispatchLiteral, DispatchTableShape, ListMatchShape, MatchDispatchPlan, SemanticConstructor,
SemanticDispatchPattern, WrapperKind,
};
use crate::types::{self, Type};
use std::collections::HashSet;
pub use super::syntax::{aver_name_to_rust, emit_stmt};
pub(super) use super::syntax::{has_list_patterns, has_string_literal_patterns};
fn is_user_type_fn(ctx: &CodegenContext) -> impl Fn(&str) -> bool + '_ {
move |name: &str| crate::codegen::common::is_user_type(name, ctx)
}
pub(super) fn classify_dispatch_plan_for_rust(
arms: &[ResolvedMatchArm],
_ctx: &CodegenContext,
_ectx: &EmitCtx,
) -> Option<MatchDispatchPlan> {
classify_match_dispatch_plan_resolved(arms)
}
pub(super) fn classify_body_plan_for_rust<'a>(
body: &'a crate::ir::hir::ResolvedFnBody,
ctx: &CodegenContext,
_ectx: &EmitCtx,
) -> Option<ResolvedBodyPlan<'a>> {
classify_body_plan_resolved(body, &is_user_type_fn(ctx))
}
#[allow(dead_code)]
pub(super) fn classify_body_expr_plan_for_rust<'a>(
expr: &'a ResolvedExpr,
ctx: &CodegenContext,
_ectx: &EmitCtx,
) -> ResolvedBodyExprPlan<'a> {
classify_body_expr_plan_resolved(expr, &is_user_type_fn(ctx))
}
pub(super) fn classify_thin_fn_def_for_rust<'a>(
fd: &'a ResolvedFnDef,
ctx: &'a CodegenContext,
_ectx: &'a EmitCtx,
) -> Option<ResolvedThinBodyPlan<'a>> {
classify_thin_fn_def_resolved(fd, &is_user_type_fn(ctx))
}
pub fn emit_expr(expr: &ResolvedExpr, ctx: &CodegenContext, ectx: &EmitCtx) -> String {
emit_expr_with_options(expr, ctx, ectx, true)
}
fn emit_tuple_from_vars(prefix: &str, count: usize) -> String {
match count {
0 => "()".to_string(),
1 => format!("({prefix}0,)"),
_ => format!(
"({})",
(0..count)
.map(|i| format!("{prefix}{i}"))
.collect::<Vec<_>>()
.join(", ")
),
}
}
fn emit_result_tuple_unwrap(result_prefix: &str, value_prefix: &str, count: usize) -> String {
let matched_results = emit_tuple_from_vars(result_prefix, count);
let ok_pattern = match count {
0 => "()".to_string(),
1 => format!("(Ok({value_prefix}0),)"),
_ => format!(
"({})",
(0..count)
.map(|i| format!("Ok({value_prefix}{i})"))
.collect::<Vec<_>>()
.join(", ")
),
};
let ok_tuple = emit_tuple_from_vars(value_prefix, count);
let mut out = format!(
"match {} {{ {} => Ok({}), {} => {{ ",
matched_results, ok_pattern, ok_tuple, matched_results
);
for i in 0..count {
out.push_str(&format!(
"if let Err(__err) = {result_prefix}{i} {{ Err(__err) }} else "
));
}
out.push_str("{ unreachable!(\"independent product unwrap requires Result branches\") } } }");
out
}
fn emit_parallel_result_tuple_unwrap(
branch_prefix: &str,
result_prefix: &str,
value_prefix: &str,
count: usize,
) -> String {
let matched_branches = emit_tuple_from_vars(branch_prefix, count);
let completed_pattern = match count {
0 => "()".to_string(),
1 => format!("(crate::ParallelBranch::Completed({result_prefix}0),)"),
_ => format!(
"({})",
(0..count)
.map(|i| format!("crate::ParallelBranch::Completed({result_prefix}{i})"))
.collect::<Vec<_>>()
.join(", ")
),
};
let mut out = format!(
"match {} {{ {} => {}, {} => {{ ",
matched_branches,
completed_pattern,
emit_result_tuple_unwrap(result_prefix, value_prefix, count),
matched_branches
);
for i in 0..count {
out.push_str(&format!(
"if let crate::ParallelBranch::Completed(Err(__err)) = {branch_prefix}{i} {{ Err(__err) }} else "
));
}
out.push_str("{ panic!(\"independent product branch cancelled by sibling branch\") } } }");
out
}
fn emit_expr_with_options(
expr: &ResolvedExpr,
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
if let Some(leaf) = classify_leaf_op_resolved(expr, &is_user_type_fn(ctx)) {
return emit_leaf_op_with_options(&leaf, ctx, ectx, allow_callsite_inlining);
}
match expr {
ResolvedExpr::Literal(lit) => emit_literal(lit),
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } => aver_name_to_rust(name),
ResolvedExpr::Attr(_, _) => unreachable!(
"ResolvedExpr::Attr must be lowered through classify_leaf_op_resolved (field access, \
None, variant constructor, or static ref); reaching this arm means \
classify_field_access returned None for an Attr shape — a bug in ir::hir::classify"
),
ResolvedExpr::Call(callee, args) => {
emit_fn_call_with_options(callee, args, ctx, ectx, allow_callsite_inlining)
}
ResolvedExpr::Neg(inner) => format!("(-{})", emit_expr(&inner.node, ctx, ectx)),
ResolvedExpr::BinOp(op, left, right) => {
let l = emit_expr(&left.node, ctx, ectx);
let r = emit_expr(&right.node, ctx, ectx);
match op {
BinOp::Add => {
if expr_is_numeric(&left.node, ectx) || expr_is_numeric(&right.node, ectx) {
format!("({} + {})", l, r)
} else {
let l = maybe_clone(l, &left.node, ectx);
format!("({} + &{})", l, r)
}
}
_ => {
let op_str = match op {
BinOp::Add => unreachable!(),
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Eq => "==",
BinOp::Neq => "!=",
BinOp::Lt => "<",
BinOp::Gt => ">",
BinOp::Lte => "<=",
BinOp::Gte => ">=",
};
if matches!(op, BinOp::Eq | BinOp::Neq) {
if let ResolvedExpr::Literal(Literal::Str(s)) = &right.node {
return format!("(&*{} {} {:?})", l, op_str, s);
}
if let ResolvedExpr::Literal(Literal::Str(s)) = &left.node {
return format!("({:?} {} &*{})", s, op_str, r);
}
}
format!("({} {} {})", l, op_str, r)
}
}
}
ResolvedExpr::Match { subject, arms, .. } => emit_match(&subject.node, arms, ctx, ectx),
ResolvedExpr::Ctor(ctor, args) => emit_constructor(ctor, args, ctx, ectx),
ResolvedExpr::ErrorProp(inner) => {
let inner_str = emit_expr(&inner.node, ctx, ectx);
format!("{}?", inner_str)
}
ResolvedExpr::InterpolatedStr(_) => unreachable!(
"InterpolatedStr should have been lowered by ir::interp_lower; \
Rust codegen runs only on lowered IR (see ir::pipeline contract)"
),
ResolvedExpr::List(elements) => {
if elements.is_empty() {
"aver_rt::AverList::empty()".to_string()
} else {
let parts: Vec<String> = elements
.iter()
.map(|e| clone_arg(&e.node, ctx, ectx))
.collect();
format!("aver_rt::AverList::from_vec(vec![{}])", parts.join(", "))
}
}
ResolvedExpr::Tuple(items) => {
let parts: Vec<String> = items
.iter()
.map(|e| clone_arg(&e.node, ctx, ectx))
.collect();
format!("({})", parts.join(", "))
}
ResolvedExpr::IndependentProduct(items, unwrap) => {
let parts: Vec<String> = items
.iter()
.map(|e| clone_arg(&e.node, ctx, ectx))
.collect();
let n = parts.len();
let has_replay = ctx.emit_replay_runtime;
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('}');
}
code
}
ResolvedExpr::MapLiteral(entries) => {
if entries.is_empty() {
"HashMap::new()".to_string()
} else {
let mut parts = Vec::new();
for (k, v) in entries.iter() {
parts.push(format!(
"({}, {})",
clone_arg(&k.node, ctx, ectx),
clone_arg(&v.node, ctx, ectx)
));
}
format!(
"vec![{}].into_iter().collect::<HashMap<_, _>>()",
parts.join(", ")
)
}
}
ResolvedExpr::RecordCreate {
type_name, fields, ..
} => {
let rust_type = if type_name == "Tcp.Connection" {
"Tcp_Connection"
} else {
type_name
};
let parts: Vec<String> = fields
.iter()
.map(|(name, expr)| {
format!(
"{}: {}",
aver_name_to_rust(name),
clone_arg(&expr.node, ctx, ectx)
)
})
.collect();
format!("{} {{ {} }}", rust_type, parts.join(", "))
}
ResolvedExpr::RecordUpdate {
type_name,
base,
updates,
..
} => {
let rust_type = if type_name == "Tcp.Connection" {
"Tcp_Connection"
} else {
type_name
};
let base_str = clone_arg(&base.node, ctx, ectx);
let parts: Vec<String> = updates
.iter()
.map(|(name, expr)| {
format!(
"{}: {}",
aver_name_to_rust(name),
clone_arg(&expr.node, ctx, ectx)
)
})
.collect();
format!("{} {{ {}, ..{} }}", rust_type, parts.join(", "), base_str)
}
ResolvedExpr::TailCall { target, args } => {
let target_name = ctx.symbol_table.fn_entry(*target).key.canonical();
emit_named_function_call(&target_name, args, ctx, ectx)
}
}
}
pub(super) fn emit_body_plan_for_rust(
plan: &ResolvedBodyPlan<'_>,
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> String {
emit_body_plan_for_rust_with_options(plan, ctx, ectx, true)
}
fn emit_body_plan_for_rust_with_options(
plan: &ResolvedBodyPlan<'_>,
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
match plan {
ResolvedBodyPlan::SingleExpr(body_expr) => {
emit_body_expr_plan_with_options(body_expr, ctx, ectx, allow_callsite_inlining)
}
ResolvedBodyPlan::Block {
stmts: _,
bindings,
tail,
} => {
let mut lines = Vec::with_capacity(bindings.len() + 1);
for binding in bindings.iter() {
lines.push(format!(
"let {} = {};",
aver_name_to_rust(binding.name),
emit_body_expr_plan_with_options(
&binding.expr,
ctx,
ectx,
allow_callsite_inlining,
)
));
}
lines.push(emit_body_expr_plan_with_options(
tail,
ctx,
ectx,
allow_callsite_inlining,
));
lines.join("\n ")
}
}
}
fn emit_body_expr_plan_with_options(
plan: &ResolvedBodyExprPlan<'_>,
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
match plan {
ResolvedBodyExprPlan::Expr(expr) => {
let code = emit_expr_with_options(expr, ctx, ectx, allow_callsite_inlining);
if let ResolvedExpr::Attr(obj, _) = expr
&& let ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } = &obj.node
&& ectx.is_borrowed_param(name)
{
return format!("{}.clone()", code);
}
code
}
ResolvedBodyExprPlan::Leaf(leaf) => {
let code = emit_leaf_op_with_options(leaf, ctx, ectx, allow_callsite_inlining);
if let ResolvedLeafOp::FieldAccess { object, .. } = leaf
&& let ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } =
&object.node
&& ectx.is_borrowed_param(name)
{
return format!("{}.clone()", code);
}
code
}
ResolvedBodyExprPlan::Call { callee, args } => {
emit_call_dispatch_with_options(callee, args, ctx, ectx, allow_callsite_inlining)
}
ResolvedBodyExprPlan::ForwardCall(plan) => {
emit_forward_call_plan_with_options(plan, ctx, ectx, allow_callsite_inlining)
}
}
}
fn emit_literal(lit: &Literal) -> String {
match lit {
Literal::Int(i) => format!("{}i64", i),
Literal::Float(f) => {
let s = f.to_string();
if s.contains('.') || s.contains('e') || s.contains('E') {
format!("{}f64", s)
} else {
format!("{}.0f64", s)
}
}
Literal::Str(s) => format!("AverStr::from({:?})", s),
Literal::Bool(b) => if *b { "true" } else { "false" }.to_string(),
Literal::Unit => "()".to_string(),
}
}
fn emit_codegen_error_expr(message: String) -> String {
let message_lit = format!("{:?}", message);
format!(
"{{ compile_error!({}); unreachable!(\"unreachable after compile_error\") }}",
message_lit
)
}
fn emit_fn_call_with_options(
callee: &ResolvedCallee,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
let plan = call_plan_from_resolved_callee(callee, &ctx.symbol_table);
match plan {
CallPlan::Dynamic => {
let func = match callee {
ResolvedCallee::LocalSlot { name, .. } => aver_name_to_rust(name),
ResolvedCallee::Unresolved { callee } => {
emit_expr_with_options(&callee.node, ctx, ectx, allow_callsite_inlining)
}
_ => unreachable!("CallPlan::Dynamic only from LocalSlot/Unresolved"),
};
let arg_strs: Vec<String> =
args.iter().map(|a| clone_arg(&a.node, ctx, ectx)).collect();
format!("{}({})", func, arg_strs.join(", "))
}
_ => emit_call_plan_with_args_with_options(&plan, args, ctx, ectx, allow_callsite_inlining),
}
}
fn emit_call_dispatch_with_options(
callee: &ResolvedCallee,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
emit_fn_call_with_options(callee, args, ctx, ectx, allow_callsite_inlining)
}
fn emit_call_plan_with_args_with_options(
plan: &CallPlan,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
_allow_callsite_inlining: bool,
) -> String {
match plan {
CallPlan::Builtin(name) => builtins::emit_builtin_call(name, args, ctx, ectx)
.unwrap_or_else(|| {
emit_codegen_error_expr(format!(
"Rust codegen: unhandled builtin call lowering for {}",
name
))
}),
CallPlan::Wrapper(_) | CallPlan::NoneValue | CallPlan::TypeConstructor { .. } => {
emit_codegen_error_expr(format!(
"Rust codegen: ctor-shaped CallPlan in call position: {:?}",
plan
))
}
CallPlan::Function(name) => emit_named_function_call(name, args, ctx, ectx),
CallPlan::Dynamic => emit_codegen_error_expr(
"Rust codegen: dynamic call passed to direct call emitter".to_string(),
),
}
}
fn emit_forward_call_plan_with_options(
plan: &crate::ir::hir::ResolvedForwardCallPlan<'_>,
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
let target = call_plan_from_resolved_callee(plan.callee, &ctx.symbol_table);
emit_call_plan_with_args_with_options(&target, plan.args, ctx, ectx, allow_callsite_inlining)
}
fn emit_leaf_op_with_options(
leaf: &ResolvedLeafOp<'_>,
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
match leaf {
ResolvedLeafOp::FieldAccess { object, field_name } => {
let object = emit_expr_with_options(&object.node, ctx, ectx, allow_callsite_inlining);
format!("{}.{}", object, aver_name_to_rust(field_name))
}
ResolvedLeafOp::MapGet { map, key } => emit_leaf_builtin_call_with_options(
"Map.get",
&[map, key],
ctx,
ectx,
allow_callsite_inlining,
),
ResolvedLeafOp::MapSet { map, key, value } => emit_leaf_builtin_call_with_options(
"Map.set",
&[map, key, value],
ctx,
ectx,
allow_callsite_inlining,
),
ResolvedLeafOp::VectorNew { size, fill } => emit_leaf_builtin_call_with_options(
"Vector.new",
&[size, fill],
ctx,
ectx,
allow_callsite_inlining,
),
ResolvedLeafOp::VectorSetOrDefaultSameVector {
vector,
index,
value,
} => {
let vector = clone_arg_with_options(&vector.node, ctx, ectx, allow_callsite_inlining);
let index = emit_expr_with_options(&index.node, ctx, ectx, allow_callsite_inlining);
let value = clone_arg_with_options(&value.node, ctx, ectx, allow_callsite_inlining);
format!(
"{{ let __vec = {}; let __idx = {} as usize; if __idx < __vec.len() {{ __vec.set_unchecked(__idx, {}) }} else {{ __vec }} }}",
vector, index, value
)
}
ResolvedLeafOp::VectorGetOrDefaultLiteral {
vector,
index,
default_literal,
} => {
let vector = emit_expr_with_options(&vector.node, ctx, ectx, allow_callsite_inlining);
let index = emit_expr_with_options(&index.node, ctx, ectx, allow_callsite_inlining);
let default = emit_literal(default_literal);
format!(
"{}.get({} as usize).cloned().unwrap_or({})",
vector, index, default
)
}
ResolvedLeafOp::IntModOrDefaultLiteral {
a,
b,
default_literal,
} => {
if let ResolvedExpr::Literal(Literal::Int(n)) = &b.node
&& *n != 0
{
let a = emit_expr_with_options(&a.node, ctx, ectx, allow_callsite_inlining);
let b_str = emit_literal(&Literal::Int(*n));
format!("({}).rem_euclid({})", a, b_str)
} else {
let a = emit_expr_with_options(&a.node, ctx, ectx, allow_callsite_inlining);
let b = emit_expr_with_options(&b.node, ctx, ectx, allow_callsite_inlining);
let default = emit_literal(default_literal);
format!(
"{{ let __b = {}; if __b == 0i64 {{ {} }} else {{ ({}).rem_euclid(__b) }} }}",
b, default, a
)
}
}
ResolvedLeafOp::ListIndexGet { list, index } => {
let list = emit_expr_with_options(&list.node, ctx, ectx, allow_callsite_inlining);
let index = emit_expr_with_options(&index.node, ctx, ectx, allow_callsite_inlining);
format!("{}.to_vec().get({} as usize).cloned()", list, index)
}
ResolvedLeafOp::NoneValue => "None".to_string(),
ResolvedLeafOp::VariantConstructor {
qualified_type_name,
variant_name,
} => {
if let Some((prefix, _)) = resolve_module_call(qualified_type_name, ctx) {
let module_path = module_prefix_to_rust_path(prefix);
let bare_type = qualified_type_name
.rsplit_once('.')
.map(|(_, t)| t)
.unwrap_or(qualified_type_name);
format!("{}::{}::{}", module_path, bare_type, variant_name)
} else {
format!("{}::{}", qualified_type_name, variant_name)
}
}
ResolvedLeafOp::StaticRef(name) => {
if name == "Option.None" || name == "None" {
return "None".to_string();
}
if let Some((type_name, variant_name)) = name.rsplit_once('.') {
let is_user = |n: &str| crate::codegen::common::is_user_type(n, ctx);
if is_user(type_name) {
return if let Some((prefix, _)) = resolve_module_call(name, ctx) {
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) {
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) {
let module_path = module_prefix_to_rust_path(prefix);
format!("{}::{}", module_path, aver_name_to_rust(bare))
} else {
aver_name_to_rust(name)
}
}
}
}
fn emit_leaf_builtin_call_with_options(
name: &str,
args: &[&Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
_allow_callsite_inlining: bool,
) -> String {
let owned_args = args
.iter()
.map(|spanned| (*spanned).clone())
.collect::<Vec<_>>();
builtins::emit_builtin_call(name, &owned_args, ctx, ectx).unwrap_or_else(|| {
emit_codegen_error_expr(format!(
"Rust codegen: missing leaf builtin lowering for {}",
name
))
})
}
fn borrow_mask_from_fn_def(
fd: &crate::ast::FnDef,
arg_count: usize,
ctx: &CodegenContext,
) -> Vec<bool> {
let is_memo = ctx.memo_fns.contains(&fd.name);
let is_mutual_tco = crate::codegen::common::fn_id_for_decl(ctx, fd)
.is_some_and(|id| ctx.mutual_tco_members.contains(&id));
if !is_memo && !is_mutual_tco && super::toplevel::body_has_self_tailcall(&fd.body, &fd.name) {
return vec![false; arg_count];
}
fd.params
.iter()
.take(arg_count)
.map(|(_, type_ann)| {
let ty = crate::types::parse_type_str(type_ann);
should_borrow_param(&ty)
})
.collect()
}
fn callee_borrow_mask(name: &str, arg_count: usize, ctx: &CodegenContext) -> Vec<bool> {
let fd = find_fn_def_by_name(name, ctx);
if let Some(fd) = fd {
return borrow_mask_from_fn_def(fd, arg_count, ctx);
}
let lookup_name = if let Some((prefix, bare)) = resolve_module_call(name, ctx) {
crate::visibility::qualified_name(prefix, bare)
} else {
name.to_string()
};
let resolved = crate::codegen::common::fn_id_for_dotted_name(ctx, &lookup_name)
.or_else(|| crate::codegen::common::fn_id_for_dotted_name(ctx, name))
.and_then(|id| ctx.resolved_program.fn_by_id(id));
if let Some(rfd) = resolved {
return rfd
.params
.iter()
.take(arg_count)
.map(|(_, ty)| should_borrow_param(ty))
.collect();
}
vec![false; arg_count]
}
fn find_fn_def_by_name<'a>(name: &str, ctx: &'a CodegenContext) -> Option<&'a crate::ast::FnDef> {
if let Some(fd) = ctx.fn_defs.iter().find(|fd| fd.name == name) {
return Some(fd);
}
if let Some(fd) = ctx.extra_fn_defs.iter().find(|fd| fd.name == name) {
return Some(fd);
}
if let Some((prefix, bare)) = resolve_module_call(name, ctx) {
for module in &ctx.modules {
if module.prefix == prefix
&& let Some(fd) = module.fn_defs.iter().find(|fd| fd.name == bare)
{
return Some(fd);
}
}
}
None
}
fn emit_named_function_call(
name: &str,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> String {
let borrow_mask = callee_borrow_mask(name, args.len(), ctx);
let arg_strs: Vec<String> = args
.iter()
.enumerate()
.map(|(i, a)| {
if borrow_mask.get(i).copied().unwrap_or(false) {
borrow_arg(&a.node, ctx, ectx)
} else {
clone_arg(&a.node, ctx, ectx)
}
})
.collect();
if let Some((prefix, bare)) = resolve_module_call(name, ctx) {
let module_path = module_prefix_to_rust_path(prefix);
format!(
"{}::{}({})",
module_path,
aver_name_to_rust(bare),
arg_strs.join(", ")
)
} else {
format!("{}({})", aver_name_to_rust(name), arg_strs.join(", "))
}
}
fn emit_type_constructor_call(
qualified_type_name: &str,
variant_name: &str,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> String {
let ctor_name = format!("{}.{}", qualified_type_name, variant_name);
let boxed_positions = constructor_boxed_positions(&ctor_name, ctx);
let arg_strs: Vec<String> = args
.iter()
.enumerate()
.map(|(idx, a)| {
let arg = clone_arg(&a.node, ctx, ectx);
if boxed_positions.contains(&idx) {
format!("std::sync::Arc::new({})", arg)
} else {
arg
}
})
.collect();
if let Some((prefix, bare_type_name)) = resolve_module_call(qualified_type_name, ctx) {
let module_path = module_prefix_to_rust_path(prefix);
format!(
"{}::{}::{}({})",
module_path,
bare_type_name,
variant_name,
arg_strs.join(", ")
)
} else {
format!(
"{}::{}({})",
qualified_type_name,
variant_name,
arg_strs.join(", ")
)
}
}
pub(super) fn maybe_clone(code: String, expr: &ResolvedExpr, ectx: &EmitCtx) -> String {
match expr {
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } => {
if expr_skip_clone(expr, ectx) {
code
} else if ectx.is_rc_wrapped(name) {
format!("(*{}).clone()", code)
} else if ectx.is_borrowed_param(name) {
format!("{}.clone()", code)
} else {
format!("{}.clone()", code)
}
}
ResolvedExpr::Attr(obj, _field) => {
if matches!(&obj.node, ResolvedExpr::Ident(name) if crate::ir::is_builtin_namespace(name))
{
code
} else {
format!("{}.clone()", code)
}
}
_ => code,
}
}
fn expr_is_numeric(expr: &ResolvedExpr, ectx: &EmitCtx) -> bool {
match expr {
ResolvedExpr::Literal(Literal::Int(_) | Literal::Float(_)) => true,
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } => {
matches!(
ectx.local_types.get(name.as_str()),
Some(Type::Int | Type::Float)
)
}
ResolvedExpr::BinOp(op, _, _) => !matches!(op, BinOp::Add),
ResolvedExpr::Call(ResolvedCallee::Builtin(name), _) => matches!(
name.as_str(),
"Int.abs"
| "Int.min"
| "Int.max"
| "Float.abs"
| "Float.floor"
| "Float.ceil"
| "Float.round"
| "Float.min"
| "Float.max"
| "Float.sqrt"
| "Float.pow"
| "Float.sin"
| "Float.cos"
| "Float.atan2"
| "Float.fromInt"
| "List.len"
| "Vector.len"
| "Map.len"
| "String.len"
| "String.byteLength"
| "Char.toCode"
),
_ => false,
}
}
fn attr_result_is_copy(
obj: &ResolvedExpr,
field: &str,
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> bool {
let obj_type = match obj {
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } => {
ectx.local_types.get(name)
}
_ => None,
};
let Some(named_ty) = obj_type.filter(|t| matches!(t, Type::Named { .. })) else {
return false;
};
let Some(record_key) = crate::codegen::common::backend_named_type_key(ctx, named_ty) else {
return false;
};
let candidates = ctx
.type_defs
.iter()
.map(|td| (None, td))
.chain(ctx.modules.iter().flat_map(|m| {
m.type_defs
.iter()
.map(move |td| (Some(m.prefix.as_str()), td))
}));
for (scope, td) in candidates {
if let TypeDef::Product { name, fields, .. } = td {
let canonical = match scope {
Some(prefix) => format!("{}.{}", prefix, name),
None => name.clone(),
};
if canonical == record_key
&& let Some((_, type_ann)) = fields.iter().find(|(n, _)| n == field)
{
let ty = types::parse_type_str(type_ann);
return super::emit_ctx::is_copy_type(&ty);
}
}
}
false
}
pub(super) fn borrow_arg(expr: &ResolvedExpr, ctx: &CodegenContext, ectx: &EmitCtx) -> String {
let code = emit_expr(expr, ctx, ectx);
match expr {
ResolvedExpr::Ident(name) => {
if ectx.is_copy(name) {
code
} else if ectx
.local_types
.get(name)
.is_some_and(|ty| matches!(ty, Type::Str))
{
if ectx.is_rc_wrapped(name) {
format!("(*{}).clone()", code)
} else {
code
}
} else if ectx.is_borrowed_param(name) {
code
} else if ectx.is_rc_wrapped(name) {
format!("&*{}", code)
} else {
format!("&{}", code)
}
}
ResolvedExpr::Resolved { name, .. } => {
if ectx.is_copy(name) {
code
} else if ectx
.local_types
.get(name)
.is_some_and(|ty| matches!(ty, Type::Str))
{
if expr_can_move(expr) {
code
} else if ectx.is_rc_wrapped(name) {
format!("(*{}).clone()", code)
} else {
format!("{}.clone()", code)
}
} else if ectx.is_borrowed_param(name) {
code
} else if ectx.is_rc_wrapped(name) {
format!("&*{}", code)
} else if expr_can_move(expr) {
format!("&{}", code)
} else {
format!("&{}", code)
}
}
_ => {
format!("&{}", code)
}
}
}
pub(super) fn clone_arg(expr: &ResolvedExpr, ctx: &CodegenContext, ectx: &EmitCtx) -> String {
clone_arg_with_options(expr, ctx, ectx, true)
}
fn clone_arg_with_options(
expr: &ResolvedExpr,
ctx: &CodegenContext,
ectx: &EmitCtx,
allow_callsite_inlining: bool,
) -> String {
let code = emit_expr_with_options(expr, ctx, ectx, allow_callsite_inlining);
if let ResolvedExpr::Attr(obj, field) = expr
&& attr_result_is_copy(&obj.node, field, ctx, ectx)
{
return code;
}
maybe_clone(code, expr, ectx)
}
fn emit_constructor(
ctor: &ResolvedCtor,
args: &[Spanned<ResolvedExpr>],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> String {
match semantic_constructor_from_resolved_ctor(ctor, &ctx.symbol_table) {
SemanticConstructor::Wrapper(kind) => {
let wrapper_name = match kind {
WrapperKind::ResultOk => "Result.Ok",
WrapperKind::ResultErr => "Result.Err",
WrapperKind::OptionSome => "Option.Some",
};
builtins::emit_builtin_call(wrapper_name, args, ctx, ectx).unwrap_or_else(|| {
emit_codegen_error_expr(format!(
"Rust codegen: missing constructor wrapper lowering for {}",
wrapper_name
))
})
}
SemanticConstructor::NoneValue => "None".to_string(),
SemanticConstructor::TypeConstructor {
qualified_type_name,
variant_name,
} => emit_type_constructor_call(&qualified_type_name, &variant_name, args, ctx, ectx),
SemanticConstructor::Unknown(name) => {
let inner = args
.first()
.map(|a| clone_arg(&a.node, ctx, ectx))
.unwrap_or_else(|| "()".to_string());
format!("{}({})", name, inner)
}
}
}
fn is_irrefutable_pattern(pat: &ResolvedPattern) -> bool {
match pat {
ResolvedPattern::Wildcard | ResolvedPattern::Ident(_) => true,
ResolvedPattern::Tuple(pats) => pats.iter().all(is_irrefutable_pattern),
_ => false,
}
}
fn emit_match(
subject: &ResolvedExpr,
arms: &[ResolvedMatchArm],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> String {
if arms.len() == 1 && is_irrefutable_pattern(&arms[0].pattern) {
let arm = &arms[0];
let subj = clone_arg(subject, ctx, ectx);
let pat = emit_pattern(&arm.pattern, false, ctx);
let body = maybe_clone(emit_expr(&arm.body.node, ctx, ectx), &arm.body.node, ectx);
return match &arm.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 no_bindings = arms
.iter()
.all(|arm| resolved_pattern_bindings(&arm.pattern).is_empty());
let match_on_ref = no_bindings
&& matches!(
subject,
ResolvedExpr::Ident(name) | ResolvedExpr::Resolved { name, .. } if ectx.is_borrowed_param(name)
);
let subj = if match_on_ref {
emit_expr(subject, ctx, ectx)
} else {
clone_arg(subject, ctx, ectx)
};
let dispatch_plan = classify_dispatch_plan_for_rust(arms, ctx, ectx);
if let Some(MatchDispatchPlan::Bool(shape)) = dispatch_plan.as_ref()
&& let Some(code) = try_emit_bool_if_else(subject, &subj, arms, *shape, ctx, ectx, ectx)
{
return code;
}
let needs_as_str = subject_might_be_string(subject, ctx);
let _needs_as_slice = subject_might_be_list(subject, arms, ctx);
if has_list_patterns(arms) {
let list_shape = match dispatch_plan.as_ref() {
Some(MatchDispatchPlan::List(shape)) => Some(*shape),
_ => None,
};
return emit_list_match(subj, arms, list_shape, true, ctx, |arm| {
maybe_clone(emit_expr(&arm.body.node, ctx, ectx), &arm.body.node, ectx)
});
}
if let Some(MatchDispatchPlan::Table(shape)) = dispatch_plan.as_ref() {
return emit_dispatch_table_match(subj, arms, shape, |arm| {
maybe_clone(emit_expr(&arm.body.node, ctx, ectx), &arm.body.node, ectx)
});
}
let match_expr = if needs_as_str && has_string_literal_patterns(arms) {
format!("&*{}", subj)
} else {
subj
};
let mut arm_strs = Vec::new();
for arm in arms {
let pat = emit_pattern(&arm.pattern, needs_as_str, ctx);
let body = maybe_clone(emit_expr(&arm.body.node, ctx, ectx), &arm.body.node, ectx);
let mut rebindings = emit_pattern_rebindings(&arm.pattern, ctx);
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
));
}
format!("match {} {{\n{}\n }}", match_expr, arm_strs.join(",\n"))
}
fn try_emit_bool_if_else(
subject: &ResolvedExpr,
subj: &str,
arms: &[ResolvedMatchArm],
shape: crate::ir::BoolMatchShape,
ctx: &CodegenContext,
subj_ectx: &EmitCtx,
ectx: &EmitCtx,
) -> Option<String> {
let (true_body, false_body, cond) = match classify_bool_subject_plan_resolved(subject) {
ResolvedBoolSubjectPlan::Expr(_) => (
&arms[shape.true_arm_index].body,
&arms[shape.false_arm_index].body,
subj.to_string(),
),
ResolvedBoolSubjectPlan::Compare {
lhs,
rhs,
op,
invert,
} => {
let lhs_code = emit_expr(&lhs.node, ctx, subj_ectx);
let rhs_code = emit_expr(&rhs.node, ctx, subj_ectx);
let op_code = match op {
BoolCompareOp::Eq => "==",
BoolCompareOp::Lt => "<",
BoolCompareOp::Gt => ">",
};
let cond = format!("({lhs_code} {op_code} {rhs_code})");
if invert {
(
&arms[shape.false_arm_index].body,
&arms[shape.true_arm_index].body,
cond,
)
} else {
(
&arms[shape.true_arm_index].body,
&arms[shape.false_arm_index].body,
cond,
)
}
}
};
let t = maybe_clone(emit_expr(&true_body.node, ctx, ectx), &true_body.node, ectx);
let f = maybe_clone(
emit_expr(&false_body.node, ctx, ectx),
&false_body.node,
ectx,
);
Some(format!("if {} {{ {} }} else {{ {} }}", cond, t, f))
}
fn subject_might_be_string(_subject: &ResolvedExpr, _ctx: &CodegenContext) -> bool {
true
}
fn subject_might_be_list(
_subject: &ResolvedExpr,
_arms: &[ResolvedMatchArm],
_ctx: &CodegenContext,
) -> bool {
true
}
pub(super) fn emit_dispatch_table_match<F>(
subject: String,
arms: &[ResolvedMatchArm],
shape: &DispatchTableShape,
body_for_arm: F,
) -> String
where
F: Fn(&ResolvedMatchArm) -> String,
{
if let Some(code) = try_emit_wrapper_match(&subject, arms, shape, &body_for_arm) {
return code;
}
let subject_name = "__dispatch_subject";
let fallback = match &shape.default_arm {
Some(default_arm) => emit_default_dispatch_arm(
subject_name,
&arms[default_arm.arm_index],
default_arm,
&body_for_arm,
),
None => "panic!(\"Aver Rust codegen: non-exhaustive dispatch match\")".to_string(),
};
let body = shape
.entries
.iter()
.rev()
.fold(fallback, |else_branch, entry| {
let arm = &arms[entry.arm_index];
let cond = emit_dispatch_condition(subject_name, &entry.pattern);
let body = emit_dispatch_arm_body(subject_name, arm, entry, &body_for_arm);
format!("if {} {{ {} }} else {{ {} }}", cond, body, else_branch)
});
format!("{{ let {} = {}; {} }}", subject_name, subject, body)
}
fn try_emit_wrapper_match<F>(
subject: &str,
arms: &[ResolvedMatchArm],
shape: &DispatchTableShape,
body_for_arm: &F,
) -> Option<String>
where
F: Fn(&ResolvedMatchArm) -> String,
{
let all_wrappers = shape.entries.iter().all(|e| {
matches!(
e.pattern,
SemanticDispatchPattern::WrapperTag(_) | SemanticDispatchPattern::NoneValue
)
});
if !all_wrappers {
return None;
}
let mut match_arms = Vec::new();
for entry in &shape.entries {
let arm = &arms[entry.arm_index];
let body = body_for_arm(arm);
match (&entry.pattern, &entry.binding) {
(
SemanticDispatchPattern::WrapperTag(kind),
DispatchBindingPlan::WrapperPayload(name),
) => {
let binding = aver_name_to_rust(name);
let extractor = match kind {
WrapperKind::ResultOk => "Ok",
WrapperKind::ResultErr => "Err",
WrapperKind::OptionSome => "Some",
};
match_arms.push(format!("{extractor}({binding}) => {{ {body} }}"));
}
(SemanticDispatchPattern::WrapperTag(kind), DispatchBindingPlan::None) => {
let pattern = match kind {
WrapperKind::ResultOk => "Ok(_)",
WrapperKind::ResultErr => "Err(_)",
WrapperKind::OptionSome => "Some(_)",
};
match_arms.push(format!("{pattern} => {{ {body} }}"));
}
(SemanticDispatchPattern::NoneValue, _) => {
match_arms.push(format!("None => {{ {body} }}"));
}
_ => return None,
}
}
if let Some(default_arm) = &shape.default_arm {
let arm = &arms[default_arm.arm_index];
let body = body_for_arm(arm);
if let Some(name) = &default_arm.binding_name {
let binding = aver_name_to_rust(name);
match_arms.push(format!("{binding} => {{ {body} }}"));
} else {
match_arms.push(format!("_ => {{ {body} }}"));
}
}
Some(format!("match {} {{ {} }}", subject, match_arms.join(", ")))
}
fn emit_dispatch_condition(subject_name: &str, pattern: &SemanticDispatchPattern) -> String {
match pattern {
SemanticDispatchPattern::Literal(lit) => match lit {
DispatchLiteral::Int(i) => format!("{subject_name} == {i}i64"),
DispatchLiteral::Float(f) => format!("{subject_name} == {f}f64"),
DispatchLiteral::Bool(b) => format!("{subject_name} == {b}"),
DispatchLiteral::Str(s) => format!("&*{subject_name} == {:?}", s),
DispatchLiteral::Unit => format!("{subject_name} == ()"),
},
SemanticDispatchPattern::EmptyList => format!("{subject_name}.is_empty()"),
SemanticDispatchPattern::NoneValue => format!("{subject_name}.is_none()"),
SemanticDispatchPattern::WrapperTag(kind) => match kind {
WrapperKind::ResultOk => format!("{subject_name}.is_ok()"),
WrapperKind::ResultErr => format!("{subject_name}.is_err()"),
WrapperKind::OptionSome => format!("{subject_name}.is_some()"),
},
}
}
fn emit_dispatch_arm_body(
subject_name: &str,
arm: &ResolvedMatchArm,
entry: &DispatchArmPlan,
body_for_arm: &impl Fn(&ResolvedMatchArm) -> String,
) -> String {
let body = body_for_arm(arm);
match (&entry.pattern, &entry.binding) {
(
SemanticDispatchPattern::WrapperTag(kind),
DispatchBindingPlan::WrapperPayload(binding_name),
) => {
let binding = aver_name_to_rust(binding_name);
let extractor = match kind {
WrapperKind::ResultOk => "Ok",
WrapperKind::ResultErr => "Err",
WrapperKind::OptionSome => "Some",
};
format!(
"{{ let {binding} = if let {extractor}({binding}) = &{subject_name} {{ {binding}.clone() }} else {{ unreachable!(\"Aver Rust codegen: dispatch tag mismatch\") }}; {body} }}"
)
}
_ => body,
}
}
fn emit_default_dispatch_arm(
subject_name: &str,
arm: &ResolvedMatchArm,
default_arm: &DispatchDefaultPlan,
body_for_arm: &impl Fn(&ResolvedMatchArm) -> String,
) -> String {
let body = body_for_arm(arm);
match &default_arm.binding_name {
None => body,
Some(name) => {
let name = aver_name_to_rust(name);
format!("{{ let {} = {}.clone(); {} }}", name, subject_name, body)
}
}
}
pub(super) fn emit_list_match<F>(
subject: String,
arms: &[ResolvedMatchArm],
list_shape: Option<ListMatchShape>,
allow_fast_macro: bool,
ctx: &CodegenContext,
body_for_arm: F,
) -> String
where
F: Fn(&ResolvedMatchArm) -> String,
{
if allow_fast_macro
&& let Some(code) =
try_emit_list_match_macro(&subject, arms, list_shape, ctx, &body_for_arm)
{
return code;
}
let subject_name = "__list_subject";
let arms_code = emit_list_match_arms(subject_name, arms, ctx, &body_for_arm);
format!("{{ let {} = {}; {} }}", subject_name, subject, arms_code)
}
fn try_emit_list_match_macro<F>(
subject: &str,
arms: &[ResolvedMatchArm],
list_shape: Option<ListMatchShape>,
ctx: &CodegenContext,
body_for_arm: &F,
) -> Option<String>
where
F: Fn(&ResolvedMatchArm) -> String,
{
let shape = match list_shape {
Some(shape) => shape,
None => classify_list_match_shape_resolved(arms)?,
};
let empty_arm = &arms[shape.empty_arm_index];
let cons_arm = &arms[shape.cons_arm_index];
let ResolvedPattern::Cons(head, tail) = &cons_arm.pattern else {
return None;
};
if head == "_" || tail == "_" {
return None;
}
let head_name = aver_name_to_rust(head);
let tail_name = aver_name_to_rust(tail);
let empty_body = emit_list_arm_body(empty_arm, ctx, body_for_arm(empty_arm));
let cons_body = emit_list_arm_body(cons_arm, ctx, body_for_arm(cons_arm));
let wrap = |body: &str| -> String {
if body.contains("return ") || body.contains("let ") || body.contains(';') {
format!("{{ {} }}", body)
} else {
body.to_string()
}
};
Some(format!(
"aver_list_match!({}, [] => {}, [{}, {}] => {})",
subject,
wrap(&empty_body),
head_name,
tail_name,
wrap(&cons_body)
))
}
fn emit_list_match_arms<F>(
subject_name: &str,
arms: &[ResolvedMatchArm],
ctx: &CodegenContext,
body_for_arm: &F,
) -> String
where
F: Fn(&ResolvedMatchArm) -> String,
{
let Some((first, rest)) = arms.split_first() else {
return "panic!(\"Aver Rust codegen: empty list match\")".to_string();
};
let body = emit_list_arm_body(first, ctx, body_for_arm(first));
let fallback = if rest.is_empty() {
"panic!(\"Aver Rust codegen: non-exhaustive list match\")".to_string()
} else {
emit_list_match_arms(subject_name, rest, ctx, body_for_arm)
};
match &first.pattern {
ResolvedPattern::EmptyList => format!(
"if {}.is_empty() {{ {} }} else {{ {} }}",
subject_name, body, fallback
),
ResolvedPattern::Cons(head, tail) => {
let head_pat = if head == "_" {
"_".to_string()
} else {
aver_name_to_rust(head)
};
let tail_pat = if tail == "_" {
"_".to_string()
} else {
aver_name_to_rust(tail)
};
format!(
"if let Some(({}, {})) = aver_rt::list_uncons_cloned(&{}) {{ {} }} else {{ {} }}",
head_pat, tail_pat, subject_name, body, fallback
)
}
ResolvedPattern::Wildcard => body,
ResolvedPattern::Ident(name) => {
let name = aver_name_to_rust(name);
format!("{{ let {} = {}.clone(); {} }}", name, subject_name, body)
}
other => {
let pat = emit_pattern(other, false, ctx);
format!(
"match &{} {{ {} => {{ {} }}, _ => {{ {} }} }}",
subject_name, pat, body, fallback
)
}
}
}
fn emit_list_arm_body(arm: &ResolvedMatchArm, ctx: &CodegenContext, body: String) -> String {
let rebindings = emit_pattern_rebindings(&arm.pattern, ctx);
if rebindings.is_empty() {
body
} else {
format!("{{ {}{} }}", rebindings, body)
}
}
pub(super) fn constructor_boxed_positions(name: &str, ctx: &CodegenContext) -> HashSet<usize> {
let mut out = HashSet::new();
let Some(decl) = find_ctor_owning_type(name, ctx) else {
return out;
};
let owning_bare = decl
.owning_type_name
.as_deref()
.map(|n| n.rsplit_once('.').map(|(_, b)| b).unwrap_or(n));
for (idx, field_ty) in decl.field_types.iter().enumerate() {
let matches = match (decl.owning_type_id, field_ty.named_id()) {
(Some(r), Some(p)) => r == p,
_ => {
field_ty.named_name() == decl.owning_type_name.as_deref()
|| field_ty.named_name() == owning_bare
}
};
if matches {
out.insert(idx);
}
}
out
}
struct CtorDecl {
field_types: Vec<crate::types::Type>,
owning_type_id: Option<crate::ir::TypeId>,
owning_type_name: Option<String>,
}
fn find_ctor_owning_type(name: &str, ctx: &CodegenContext) -> Option<CtorDecl> {
use crate::ast::{TopLevel, TypeDef};
let consider =
|type_name: &str, ctor_name: &str, fields: Vec<crate::types::Type>| -> CtorDecl {
let owning_type_id = ctx
.symbol_table
.type_id_of(&crate::ir::TypeKey::entry(type_name))
.or_else(|| {
type_name.rsplit_once('.').and_then(|(prefix, bare)| {
ctx.symbol_table
.type_id_of(&crate::ir::TypeKey::in_module(prefix.to_string(), bare))
})
});
let _ = ctor_name;
CtorDecl {
field_types: fields,
owning_type_id,
owning_type_name: Some(type_name.to_string()),
}
};
let walk_one = |td: &TypeDef, scope: Option<&str>| -> Option<CtorDecl> {
match td {
TypeDef::Sum {
name: parent,
variants,
..
} => {
let parent_full = match scope {
Some(prefix) => format!("{prefix}.{parent}"),
None => parent.clone(),
};
for v in variants {
let bare_form = format!("{parent}.{}", v.name);
let full_form = format!("{parent_full}.{}", v.name);
if name == bare_form || name == full_form {
let fields = v
.fields
.iter()
.map(|t| crate::types::parse_type_str(t))
.collect();
return Some(consider(&parent_full, &v.name, fields));
}
}
None
}
TypeDef::Product {
name: parent,
fields,
..
} => {
let parent_full = match scope {
Some(prefix) => format!("{prefix}.{parent}"),
None => parent.clone(),
};
if name == parent || name == parent_full {
let fts = fields
.iter()
.map(|(_, t)| crate::types::parse_type_str(t))
.collect();
return Some(consider(&parent_full, parent, fts));
}
None
}
}
};
for item in &ctx.items {
if let TopLevel::TypeDef(td) = item
&& let Some(decl) = walk_one(td, None)
{
return Some(decl);
}
}
for m in &ctx.modules {
for td in &m.type_defs {
if let Some(decl) = walk_one(td, Some(&m.prefix)) {
return Some(decl);
}
}
}
None
}
pub(super) fn constructor_boxed_bindings(
name: &str,
bindings: &[String],
ctx: &CodegenContext,
) -> Vec<String> {
let boxed = constructor_boxed_positions(name, ctx);
bindings
.iter()
.enumerate()
.filter_map(|(idx, b)| {
if b != "_" && boxed.contains(&idx) {
Some(b.clone())
} else {
None
}
})
.collect()
}
fn emit_ref_match_rebindings(pattern: &ResolvedPattern) -> String {
let bindings = resolved_pattern_bindings(pattern);
if bindings.is_empty() {
return String::new();
}
let mut lines = Vec::new();
for b in &bindings {
let rb = super::syntax::aver_name_to_rust(b);
lines.push(format!("let {} = {}.clone();", rb, rb));
}
format!("{}\n ", lines.join("\n "))
}
fn emit_pattern_rebindings(pattern: &ResolvedPattern, ctx: &CodegenContext) -> String {
let mut lines = Vec::new();
if let ResolvedPattern::Ctor(ctor, bindings) = pattern {
let semantic = semantic_constructor_from_resolved_ctor(ctor, &ctx.symbol_table);
let ctor_name = match semantic {
SemanticConstructor::TypeConstructor {
qualified_type_name,
variant_name,
} => format!("{}.{}", qualified_type_name, variant_name),
SemanticConstructor::Wrapper(_) | SemanticConstructor::NoneValue => {
String::new()
}
SemanticConstructor::Unknown(name) => name,
};
if !ctor_name.is_empty() {
for b in constructor_boxed_bindings(&ctor_name, bindings, ctx) {
let b = aver_name_to_rust(&b);
lines.push(format!("let {} = (*{}).clone();", b, b));
}
}
}
if lines.is_empty() {
String::new()
} else {
format!("{}\n ", lines.join("\n "))
}
}
#[allow(dead_code)]
fn _silence_intrinsic_import(_: BuiltinIntrinsic) {}
#[allow(dead_code)]
fn _resolved_to_dotted_unused(expr: &ResolvedExpr) -> Option<String> {
resolved_to_dotted(expr)
}
#[allow(dead_code)]
fn _thin_kind_unused(_: ThinKind) {}
#[allow(dead_code)]
fn _resolved_stmt_unused(_: &ResolvedStmt) {}