use super::emit_ctx::{EmitCtx, should_borrow_param};
use super::pattern::emit_pattern;
use crate::ast::{Literal, TypeDef};
use crate::codegen::CodegenContext;
use crate::codegen::common::resolve_module_call;
use crate::ir::hir::{
ResolvedFnDef, ResolvedMatchArm, ResolvedPattern, ResolvedThinBodyPlan,
classify_list_match_shape_resolved, classify_thin_fn_def_resolved,
semantic_constructor_from_resolved_ctor,
};
use crate::ir::vars::resolved_pattern_bindings;
use crate::ir::{
DispatchArmPlan, DispatchBindingPlan, DispatchDefaultPlan, DispatchLiteral, DispatchTableShape,
ListMatchShape, SemanticConstructor, SemanticDispatchPattern, WrapperKind,
};
use crate::types::{self, Type};
use std::collections::HashSet;
pub use super::syntax::aver_name_to_rust;
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_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(super) 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(", ")
),
}
}
pub(super) 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
}
pub(super) 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
}
pub(super) fn emit_literal(lit: &Literal) -> String {
match lit {
Literal::Int(i) => format!("aver_rt::AverInt::from_i64({})", i),
Literal::BigInt(s) => format!("{:?}.parse::<aver_rt::AverInt>().unwrap()", s),
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 borrow_mask_from_fn_def(
fd: &crate::ast::FnDef,
arg_count: usize,
ctx: &CodegenContext,
) -> Vec<bool> {
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_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()
}
pub(super) fn callee_borrow_mask(name: &str, arg_count: usize, ctx: &CodegenContext) -> Vec<bool> {
let fd = find_fn_def_by_name(name, ctx);
let mut mask = if let Some(fd) = fd {
borrow_mask_from_fn_def(fd, arg_count, ctx)
} else {
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));
match resolved {
Some(rfd) => rfd
.params
.iter()
.take(arg_count)
.map(|(_, ty)| should_borrow_param(ty))
.collect(),
None => vec![false; arg_count],
}
};
clear_owned_param_borrows(name, &mut mask, ctx);
mask
}
fn clear_owned_param_borrows(name: &str, mask: &mut [bool], ctx: &CodegenContext) {
let lookup_name = if let Some((prefix, bare)) = resolve_module_call(name, ctx) {
crate::visibility::qualified_name(prefix, bare)
} else {
name.to_string()
};
let fn_id = crate::codegen::common::fn_id_for_dotted_name(ctx, &lookup_name)
.or_else(|| crate::codegen::common::fn_id_for_dotted_name(ctx, name));
let Some(fn_id) = fn_id else { return };
if let Some(fd) = find_fn_def_by_name(name, ctx) {
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_mutual_tco || super::toplevel::body_has_self_tailcall(&fd.body, &fd.name) {
return;
}
}
let Some(mir_fn) = ctx.mir_program.as_ref().and_then(|p| p.fn_by_id(fn_id)) else {
return;
};
let Some(rfd) = ctx.resolved_program.fn_by_id(fn_id) else {
return;
};
let owned = super::from_mir::owned_collection_param_names(mir_fn, &rfd.params);
if owned.is_empty() {
return;
}
for (i, (pname, _)) in rfd.params.iter().enumerate() {
if i >= mask.len() {
break;
}
if owned.contains(&aver_name_to_rust(pname)) {
mask[i] = false;
}
}
}
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
}
pub(super) fn record_field_is_copy(named_ty: &Type, field: &str, ctx: &CodegenContext) -> bool {
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 emit_dispatch_table_match<F>(
subject: String,
arms: &[ResolvedMatchArm],
shape: &DispatchTableShape,
subject_is_bare: bool,
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, subject_is_bare);
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,
subject_is_bare: bool,
) -> String {
match pattern {
SemanticDispatchPattern::Literal(lit) => match lit {
DispatchLiteral::Int(i) if subject_is_bare => {
format!("{subject_name} == {i}i64")
}
DispatchLiteral::Int(i) => {
format!("{subject_name} == aver_rt::AverInt::from_i64({i})")
}
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_full = decl.owning_type_name.as_deref();
let owning_bare = owning_full.map(|n| n.rsplit_once('.').map(|(_, b)| b).unwrap_or(n));
for (idx, field_str) in decl.field_type_strs.iter().enumerate() {
let f = field_str.trim();
if Some(f) == owning_full || Some(f) == owning_bare {
out.insert(idx);
}
}
out
}
struct CtorDecl {
field_type_strs: Vec<String>,
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, fields: Vec<String>| -> CtorDecl {
CtorDecl {
field_type_strs: fields,
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 {
return Some(consider(&parent_full, v.fields.clone()));
}
}
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)| t.clone()).collect();
return Some(consider(&parent_full, 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()
}
pub(super) 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 "))
}
pub(super) 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 "))
}
}