use super::result_presence::free_function_presence_gate;
use crate::core::ir::{FunctionDef, ParamDef, PrimitiveType, TypeRef};
use super::errors::{UNMATCHED_ERROR_SET, resolve_zig_error_type};
use super::helpers::emit_cleaned_zig_doc;
use super::types::{c_symbol_component, zig_field_type};
fn is_struct_named(ty: &TypeRef, struct_names: &std::collections::HashSet<String>) -> bool {
match ty {
TypeRef::Named(name) => struct_names.contains(name),
TypeRef::Optional(inner) => is_struct_named(inner, struct_names),
_ => false,
}
}
fn struct_named_inner(ty: &TypeRef) -> Option<&str> {
match ty {
TypeRef::Named(name) => Some(name.as_str()),
TypeRef::Optional(inner) => struct_named_inner(inner),
_ => None,
}
}
pub(crate) fn opaque_type_name_inner(ty: &TypeRef) -> Option<&str> {
match ty {
TypeRef::Named(name) => Some(name.as_str()),
TypeRef::Optional(inner) => opaque_type_name_inner(inner),
_ => None,
}
}
fn get_opaque_named<'a>(
ty: &'a TypeRef,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> Option<&'a str> {
match ty {
TypeRef::Named(name) if opaque_creator_map.contains_key(name.as_str()) => Some(name.as_str()),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_creator_map.contains_key(name.as_str()) => Some(name.as_str()),
_ => None,
},
_ => None,
}
}
fn needs_alloc_param(p: &ParamDef) -> bool {
let inner = match &p.ty {
TypeRef::Optional(t) => t.as_ref(),
other => other,
};
matches!(
inner,
TypeRef::String | TypeRef::Path | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_)
)
}
fn needs_from_json_param(
p: &ParamDef,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> bool {
get_opaque_named(&p.ty, opaque_creator_map).is_some() || is_struct_named(&p.ty, struct_names)
}
fn return_type_can_be_null(ty: &TypeRef, _struct_names: &std::collections::HashSet<String>) -> bool {
match ty {
TypeRef::String
| TypeRef::Char
| TypeRef::Path
| TypeRef::Json
| TypeRef::Bytes
| TypeRef::Vec(_)
| TypeRef::Map(_, _) => true,
TypeRef::Optional(inner) => matches!(
inner.as_ref(),
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _)
),
_ => false,
}
}
mod free_functions;
pub(crate) use free_functions::emit_function;
fn emit_capsule_function(
f: &FunctionDef,
prefix: &str,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
cap: &crate::core::config::HostCapsuleTypeConfig,
declared_errors: &[String],
out: &mut String,
) {
emit_cleaned_zig_doc(out, &f.doc, "");
let params: Vec<String> = f
.params
.iter()
.map(|p| format_param_wrapper(p, struct_names, opaque_creator_map))
.collect();
let body_needs_try = f.params.iter().any(needs_alloc_param);
let host_type = match cap.required_host_type("Language", "zig") {
Ok(t) => t.to_string(),
Err(e) => {
out.push_str(&format!("// ALEF ERROR: {e}\n"));
return;
}
};
let zig_error_type = f
.error_type
.as_ref()
.map(|e| resolve_zig_error_type(e, declared_errors));
let return_ty = if let Some(err) = &zig_error_type {
format!("{err}!{host_type}")
} else if body_needs_try {
format!("error{{OutOfMemory}}!{host_type}")
} else {
host_type.clone()
};
out.push_str(&crate::backends::zig::template_env::render(
"function_signature.jinja",
crate::alef_context! {
func_name => &f.name,
params => params.join(", "),
return_ty => &return_ty,
},
));
for p in &f.params {
emit_param_conversion(
p,
prefix,
struct_names,
opaque_creator_map,
"return error.OutOfMemory;",
out,
);
}
let c_args: Vec<String> = f
.params
.iter()
.flat_map(|p| c_arg_names(p, struct_names, opaque_creator_map))
.collect();
let c_call = format!("c.{prefix}_{}({})", f.name, c_args.join(", "));
out.push_str(&crate::backends::zig::template_env::render(
"function_call_result.jinja",
crate::alef_context! {
c_call => &c_call,
},
));
for p in &f.params {
emit_param_free(p, prefix, struct_names, opaque_creator_map, out);
}
if let Some(error_type) = &zig_error_type {
out.push_str(&crate::backends::zig::template_env::render(
"function_error_check.jinja",
crate::alef_context! { prefix => prefix },
));
out.push_str(&crate::backends::zig::template_env::render(
"function_error_return.jinja",
crate::alef_context! { error_type => error_type },
));
out.push_str(" }\n");
}
out.push_str(" if (_result == null) return null;\n");
let construct = match cap.construct_required("_result", "Language", "zig") {
Ok(c) => c,
Err(e) => {
out.push_str(&format!(" // ALEF ERROR: {e}\n"));
out.push_str("}\n");
return;
}
};
out.push_str(&format!(" return {construct};\n"));
out.push_str("}\n");
}
fn format_param_wrapper(
p: &ParamDef,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> String {
let ty_str = zig_param_type(&p.ty, p.optional, struct_names, opaque_creator_map);
format!("{}: {}", p.name, ty_str)
}
pub(crate) fn wrapper_return_type(
f: &FunctionDef,
declared_errors: &[String],
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> String {
let zig_error_type = f
.error_type
.as_ref()
.map(|e| resolve_zig_error_type(e, declared_errors));
let body_needs_try = f.params.iter().any(needs_alloc_param)
|| return_conversion_needs_out_of_memory(&f.return_type, struct_names)
|| return_uses_bytes_out_params(&f.return_type);
let body_needs_invalid_json = f
.params
.iter()
.any(|p| needs_from_json_param(p, struct_names, opaque_creator_map));
if let Some(error_type) = &zig_error_type {
format!("{}!{}", error_type, zig_return_type(&f.return_type, struct_names))
} else if infallible_result_needs_null_check(&f.return_type) {
let err_set = if body_needs_invalid_json {
"error{OutOfMemory,InvalidJson,UnknownFfiError,Cancelled}"
} else {
UNMATCHED_ERROR_SET
};
format!("{err_set}!{}", zig_return_type(&f.return_type, struct_names))
} else if body_needs_try || body_needs_invalid_json {
let err_set = if body_needs_invalid_json {
"error{OutOfMemory,InvalidJson}"
} else {
"error{OutOfMemory}"
};
format!("{err_set}!{}", zig_return_type(&f.return_type, struct_names))
} else {
zig_return_type(&f.return_type, struct_names)
}
}
fn infallible_result_needs_null_check(ty: &TypeRef) -> bool {
!return_uses_bytes_out_params(ty)
&& matches!(
ty,
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _)
)
}
pub(crate) fn wrapper_param_types(
f: &FunctionDef,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> Vec<String> {
f.params
.iter()
.map(|p| zig_param_type(&p.ty, p.optional, struct_names, opaque_creator_map))
.collect()
}
pub(crate) fn zig_param_type(
ty: &TypeRef,
optional: bool,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> String {
if get_opaque_named(ty, opaque_creator_map).is_some() {
return "?[]const u8".to_string();
}
let inner = match ty {
TypeRef::String | TypeRef::Path | TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
"[]const u8".to_string()
}
TypeRef::Named(name) if struct_names.contains(name) => "[]const u8".to_string(),
TypeRef::Optional(inner) => {
let inner_str = zig_param_type(inner, false, struct_names, opaque_creator_map);
return format!("?{inner_str}");
}
other => zig_field_type(other, false),
};
if optional { format!("?{inner}") } else { inner }
}
fn emit_param_conversion(
p: &ParamDef,
prefix: &str,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
json_error_return: &str,
out: &mut String,
) {
let name = &p.name;
if let Some(opaque_name) = get_opaque_named(&p.ty, opaque_creator_map) {
if let Some((creator_fn, config_snake)) = opaque_creator_map.get(opaque_name) {
out.push_str(&crate::backends::zig::template_env::render(
"param_opaque_config_from_json.jinja",
crate::alef_context! {
name => name,
prefix => prefix,
creator_fn => creator_fn,
config_snake => config_snake,
name_snake => &c_symbol_component(opaque_name),
json_error_return => json_error_return,
},
));
}
return;
}
if let Some(inner_name) = struct_named_inner(&p.ty)
&& struct_names.contains(inner_name)
{
let snake = c_symbol_component(inner_name);
let is_optional = p.optional || matches!(p.ty, TypeRef::Optional(_));
if is_optional {
out.push_str(&crate::backends::zig::template_env::render(
"param_optional_string_alloc.jinja",
crate::alef_context! { name => name },
));
out.push_str(&crate::backends::zig::template_env::render(
"param_optional_struct_handle.jinja",
crate::alef_context! {
name => name,
prefix => prefix,
snake => &snake,
json_error_return => json_error_return,
},
));
} else {
out.push_str(&crate::backends::zig::template_env::render(
"param_string_line1.jinja",
crate::alef_context! { name => name },
));
out.push_str(&crate::backends::zig::template_env::render(
"param_string_line2.jinja",
crate::alef_context! { name => name },
));
out.push_str(&crate::backends::zig::template_env::render(
"param_struct_handle.jinja",
crate::alef_context! {
name => name,
prefix => prefix,
snake => &snake,
json_error_return => json_error_return,
},
));
}
return;
}
let is_optional_string = p.optional
|| matches!(
&p.ty,
TypeRef::Optional(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Path)
);
if is_optional_string && matches!(unwrap_optional(&p.ty), TypeRef::String | TypeRef::Path) {
out.push_str(&crate::backends::zig::template_env::render(
"param_optional_string_alloc.jinja",
crate::alef_context! { name => name },
));
return;
}
match &p.ty {
TypeRef::String | TypeRef::Path => {
out.push_str(&crate::backends::zig::template_env::render(
"param_string_line1.jinja",
crate::alef_context! {
name => name,
},
));
out.push_str(&crate::backends::zig::template_env::render(
"param_string_line2.jinja",
crate::alef_context! {
name => name,
},
));
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(" // Vec/Map parameters are passed as JSON strings across the FFI boundary.\n");
out.push_str(&crate::backends::zig::template_env::render(
"param_string_line1.jinja",
crate::alef_context! {
name => name,
},
));
out.push_str(&crate::backends::zig::template_env::render(
"param_string_line2.jinja",
crate::alef_context! {
name => name,
},
));
}
_ => {}
}
}
fn unwrap_optional(ty: &TypeRef) -> &TypeRef {
match ty {
TypeRef::Optional(inner) => inner,
other => other,
}
}
pub(crate) fn return_uses_bytes_out_params(ty: &TypeRef) -> bool {
match ty {
TypeRef::Bytes => true,
TypeRef::Optional(inner) => matches!(inner.as_ref(), TypeRef::Bytes),
_ => false,
}
}
pub(crate) fn return_uses_len_companion(ty: &TypeRef) -> bool {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
TypeRef::Vec(_) | TypeRef::Map(_, _) => true,
TypeRef::Optional(inner) => matches!(
inner.as_ref(),
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _)
),
_ => false,
}
}
pub(crate) fn return_conversion_needs_out_of_memory(
ty: &TypeRef,
struct_names: &std::collections::HashSet<String>,
) -> bool {
match ty {
TypeRef::String
| TypeRef::Char
| TypeRef::Path
| TypeRef::Json
| TypeRef::Bytes
| TypeRef::Vec(_)
| TypeRef::Map(_, _)
| TypeRef::Named(_) => true,
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
true
}
TypeRef::Named(name) => struct_names.contains(name),
_ => false,
},
_ => false,
}
}
fn body_error_literals(body: &str) -> std::collections::HashSet<String> {
const NEEDLE: &str = "error.";
let mut found = std::collections::HashSet::new();
let mut search_from = 0usize;
while let Some(offset) = body[search_from..].find(NEEDLE) {
let start = search_from + offset + NEEDLE.len();
let end = body[start..]
.find(|c: char| !(c.is_alphanumeric() || c == '_'))
.map_or(body.len(), |rel| start + rel);
found.insert(body[start..end].to_string());
search_from = end.max(start + 1);
}
found
}
pub(crate) fn assert_error_set_covers_body(context: &str, return_ty: &str, body: &str, declared_errors: &[String]) {
if return_ty.contains("anyerror") {
return;
}
let custom_error_declared = declared_errors.iter().any(|name| return_ty.contains(name.as_str()));
for token in body_error_literals(body) {
let admitted = return_ty.contains(&token)
|| (custom_error_declared && matches!(token.as_str(), "OutOfMemory" | "UnknownFfiError"));
assert!(
admitted,
"zig codegen invariant violated for `{context}`: body returns `error.{token}` but \
declared return type `{return_ty}` does not admit it -- the declared error set and \
the emitted body's `return error.X` sites have diverged."
);
}
}
pub(super) fn optional_int_sentinel(prim: &PrimitiveType) -> Option<&'static str> {
match prim {
PrimitiveType::U8 => Some("std.math.maxInt(u8)"),
PrimitiveType::U16 => Some("std.math.maxInt(u16)"),
PrimitiveType::U32 => Some("std.math.maxInt(u32)"),
PrimitiveType::U64 | PrimitiveType::Usize => Some("std.math.maxInt(u64)"),
PrimitiveType::I8 => Some("std.math.maxInt(i8)"),
PrimitiveType::I16 => Some("std.math.maxInt(i16)"),
PrimitiveType::I32 => Some("std.math.maxInt(i32)"),
PrimitiveType::I64 | PrimitiveType::Isize => Some("std.math.maxInt(i64)"),
_ => None,
}
}
pub(super) fn emit_param_free(
p: &ParamDef,
_prefix: &str,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
out: &mut String,
) {
let name = &p.name;
if let Some(opaque_name) = get_opaque_named(&p.ty, opaque_creator_map) {
if let Some((_, config_snake)) = opaque_creator_map.get(opaque_name) {
let config_name = format!("{name}_config");
out.push_str(&crate::backends::zig::template_env::render(
"param_optional_free.jinja",
crate::alef_context! {
name => &config_name,
},
));
let _ = config_snake;
}
return;
}
if let Some(inner_name) = struct_named_inner(&p.ty)
&& struct_names.contains(inner_name)
{
let _ = inner_name;
}
}
fn c_arg_names(
p: &ParamDef,
struct_names: &std::collections::HashSet<String>,
opaque_creator_map: &std::collections::HashMap<String, (String, String)>,
) -> Vec<String> {
if get_opaque_named(&p.ty, opaque_creator_map).is_some() {
return vec![format!("{}_handle", p.name)];
}
if is_struct_named(&p.ty, struct_names) {
return vec![format!("{}_handle", p.name)];
}
let is_optional_string = p.optional
|| matches!(
&p.ty,
TypeRef::Optional(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Path)
);
if is_optional_string && matches!(unwrap_optional(&p.ty), TypeRef::String | TypeRef::Path) {
return vec![format!("if ({0}_z) |z| z.ptr else null", p.name)];
}
{
let prim_opt = match &p.ty {
TypeRef::Optional(inner) => {
if let TypeRef::Primitive(prim) = inner.as_ref() {
Some(prim)
} else {
None
}
}
TypeRef::Primitive(prim) if p.optional => Some(prim),
_ => None,
};
if let Some(prim) = prim_opt
&& let Some(sentinel) = optional_int_sentinel(prim)
{
return vec![format!("if ({name}) |v| v else {sentinel}", name = p.name)];
}
}
match &p.ty {
TypeRef::String | TypeRef::Path | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
vec![format!("{}_z", p.name)]
}
TypeRef::Bytes => {
vec![format!("{}.ptr", p.name), format!("{}.len", p.name)]
}
_ => vec![p.name.clone()],
}
}
fn unwrap_return_expr(
raw: &str,
ty: &TypeRef,
prefix: &str,
struct_names: &std::collections::HashSet<String>,
error_type: Option<&str>,
) -> String {
match ty {
TypeRef::Primitive(PrimitiveType::Bool) => format!("{raw} != 0"),
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
crate::backends::zig::template_env::render(
"return_owned_bytes_block.jinja",
crate::alef_context! {
raw => raw,
error_type => error_type,
},
)
}
TypeRef::Named(name) if struct_names.contains(name) => {
let snake = c_symbol_component(name);
crate::backends::zig::template_env::render(
"return_named_json_block.jinja",
crate::alef_context! {
prefix => prefix,
snake => &snake,
raw => raw,
error_type => error_type,
},
)
}
TypeRef::Named(name) => {
let fallback = if error_type.is_some() {
"error.UnknownFfiError"
} else {
"error.OutOfMemory"
};
format!("blk: {{ if ({raw} == 0) return {fallback}; break :blk {name}{{ ._handle = {raw} }}; }}")
}
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
crate::backends::zig::template_env::render(
"return_optional_owned_bytes_block.jinja",
crate::alef_context! {
raw => raw,
},
)
}
TypeRef::Named(name) if struct_names.contains(name) => {
let snake = c_symbol_component(name);
let inner_block = crate::backends::zig::template_env::render(
"return_named_json_block.jinja",
crate::alef_context! {
prefix => prefix,
snake => &snake,
raw => raw,
error_type => error_type,
},
);
format!("if ({raw} == 0) null else {inner_block}")
}
TypeRef::Named(name) => {
format!("if ({raw} == 0) null else {name}{{ ._handle = {raw} }}")
}
_ => raw.to_string(),
},
_ => raw.to_string(),
}
}
pub(crate) fn zig_return_type(ty: &TypeRef, struct_names: &std::collections::HashSet<String>) -> String {
match ty {
TypeRef::String
| TypeRef::Char
| TypeRef::Path
| TypeRef::Json
| TypeRef::Bytes
| TypeRef::Vec(_)
| TypeRef::Map(_, _) => "[]u8".to_string(),
TypeRef::Named(name) if struct_names.contains(name) => "[]u8".to_string(),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::String
| TypeRef::Char
| TypeRef::Path
| TypeRef::Json
| TypeRef::Bytes
| TypeRef::Vec(_)
| TypeRef::Map(_, _) => "?[]u8".to_string(),
TypeRef::Named(name) if struct_names.contains(name) => "?[]u8".to_string(),
other => zig_field_type(other, true),
},
other => zig_field_type(other, false),
}
}
#[cfg(test)]
mod capsule_tests;
#[cfg(test)]
mod tests;