use crate::backends::zig::gen_bindings::errors::resolve_zig_error_type;
use crate::backends::zig::gen_bindings::functions::zig_return_type;
use crate::backends::zig::gen_bindings::helpers::emit_cleaned_zig_doc;
use crate::core::ir::{MethodDef, ParamDef, TypeDef, TypeRef};
use heck::AsSnakeCase;
use std::collections::{HashMap, HashSet};
use super::params::{
emit_method_param_conversion, emit_method_param_free, method_c_arg_names, method_param_needs_alloc,
method_param_needs_from_json, param_zig_type_with_enums,
};
use super::render;
use super::returns::method_unwrap_return_expr;
use super::streaming::emit_opaque_streaming_method;
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_opaque_method(
method: &MethodDef,
ty: &TypeDef,
prefix: &str,
type_snake: &str,
declared_errors: &[String],
struct_names: &HashSet<String>,
streaming_item_types: &HashMap<String, String>,
enum_names: &HashSet<String>,
out: &mut String,
) {
if let Some(item_type) = streaming_item_types.get(&method.name) {
emit_opaque_streaming_method(method, ty, prefix, type_snake, item_type, declared_errors, out);
return;
}
emit_cleaned_zig_doc(out, &method.doc, " ");
let renamed_params = renamed_method_params(method);
let effective_params: &[ParamDef] = &renamed_params;
let params_str = method_params_signature(ty, effective_params, struct_names, enum_names);
let zig_error_type = method
.error_type
.as_ref()
.map(|e| resolve_zig_error_type(e, declared_errors));
let return_ty = method_return_type(method, effective_params, struct_names, zig_error_type.as_ref());
out.push_str(&render(
"opaque_method_signature.jinja",
minijinja::context! {
method_name => &method.name,
params => ¶ms_str,
return_ty => &return_ty,
},
));
let json_error_return = zig_error_type
.as_ref()
.map_or("return error.InvalidJson;".to_string(), |err| {
format!("return _first_error({err});")
});
for p in effective_params {
emit_method_param_conversion(p, prefix, struct_names, enum_names, &json_error_return, out);
}
let returns_bytes = matches!(method.return_type, TypeRef::Bytes);
if returns_bytes {
out.push_str(&render("opaque_bytes_out_vars.jinja", minijinja::context! {}));
}
let c_call = method_c_call(
method,
ty,
prefix,
type_snake,
effective_params,
struct_names,
enum_names,
);
emit_method_body(
method,
prefix,
struct_names,
effective_params,
returns_bytes,
&c_call,
zig_error_type.as_ref(),
out,
);
out.push_str(" }\n");
}
pub(super) fn emit_opaque_free(ty: &TypeDef, prefix: &str, type_snake: &str, out: &mut String) {
let upper_prefix = prefix.to_uppercase();
out.push_str(&render(
"opaque_free_method.jinja",
minijinja::context! {
type_name => &ty.name,
prefix => prefix,
type_snake => type_snake,
upper_prefix => &upper_prefix,
},
));
}
fn renamed_method_params(method: &MethodDef) -> Vec<ParamDef> {
method
.params
.iter()
.map(|p| {
if p.name == method.name {
let mut p2 = p.clone();
p2.name = "value".to_string();
p2
} else {
p.clone()
}
})
.collect()
}
fn method_params_signature(
ty: &TypeDef,
params: &[ParamDef],
struct_names: &HashSet<String>,
enum_names: &HashSet<String>,
) -> String {
let mut param_parts = Vec::new();
param_parts.push(format!("self: *{}", ty.name));
for p in params {
let ty_str = param_zig_type_with_enums(&p.ty, p.optional, struct_names, enum_names);
param_parts.push(format!("{}: {}", p.name, ty_str));
}
param_parts.join(", ")
}
fn method_return_type(
method: &MethodDef,
params: &[ParamDef],
struct_names: &HashSet<String>,
zig_error_type: Option<&String>,
) -> String {
let body_needs_try = params.iter().any(method_param_needs_alloc)
|| matches!(
&method.return_type,
TypeRef::String | TypeRef::Path | TypeRef::Json | TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _)
)
|| matches!(&method.return_type, TypeRef::Named(name) if struct_names.contains(name));
let body_needs_invalid_json = params.iter().any(|p| method_param_needs_from_json(p, struct_names));
let ret_ty_inner = zig_return_type(&method.return_type, struct_names);
if let Some(err_ty) = zig_error_type {
format!("({err_ty}||error{{OutOfMemory}})!{ret_ty_inner}")
} 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}!{ret_ty_inner}")
} else {
ret_ty_inner
}
}
fn method_c_call(
method: &MethodDef,
ty: &TypeDef,
prefix: &str,
type_snake: &str,
params: &[ParamDef],
struct_names: &HashSet<String>,
enum_names: &HashSet<String>,
) -> String {
let method_snake = AsSnakeCase(&method.name).to_string();
let upper_prefix = prefix.to_uppercase();
let c_handle = format!(
"@as(*c.{upper_prefix}{type_name}, @ptrCast(self._handle))",
type_name = ty.name,
);
let mut c_args = vec![c_handle];
for p in params {
c_args.extend(method_c_arg_names(p, struct_names, enum_names));
}
if matches!(method.return_type, TypeRef::Bytes) {
c_args.push("&_out_ptr".to_string());
c_args.push("&_out_len".to_string());
c_args.push("&_out_cap".to_string());
}
format!(
"c.{prefix}_{type_snake}_{method_snake}({args})",
args = c_args.join(", ")
)
}
#[allow(clippy::too_many_arguments)]
fn emit_method_body(
method: &MethodDef,
prefix: &str,
struct_names: &HashSet<String>,
params: &[ParamDef],
returns_bytes: bool,
c_call: &str,
zig_error_type: Option<&String>,
out: &mut String,
) {
if let Some(err_ty) = zig_error_type {
emit_fallible_method_body(method, prefix, struct_names, params, returns_bytes, c_call, err_ty, out);
} else {
emit_infallible_method_body(method, prefix, struct_names, params, returns_bytes, c_call, out);
}
}
#[allow(clippy::too_many_arguments)]
fn emit_fallible_method_body(
method: &MethodDef,
prefix: &str,
struct_names: &HashSet<String>,
params: &[ParamDef],
returns_bytes: bool,
c_call: &str,
err_ty: &str,
out: &mut String,
) {
let result_is_pointer = !(matches!(method.return_type, TypeRef::Unit) || returns_bytes);
if !result_is_pointer {
out.push_str(&render(
"opaque_method_call_discard.jinja",
minijinja::context! {
c_call => c_call,
},
));
} else {
out.push_str(&render(
"opaque_method_call_result.jinja",
minijinja::context! {
c_call => c_call,
},
));
}
if result_is_pointer {
out.push_str(" if (_result == null) {\n");
out.push_str(&format!(" return _first_error({err_ty});\n"));
out.push_str(" }\n");
} else {
out.push_str(&render(
"opaque_method_error_check.jinja",
minijinja::context! {
prefix => prefix,
error_type => err_ty,
},
));
}
for p in params {
emit_method_param_free(p, struct_names);
}
if returns_bytes {
out.push_str(&render(
"opaque_bytes_return.jinja",
minijinja::context! {
prefix => prefix,
},
));
} else if !matches!(method.return_type, TypeRef::Unit) {
let ret_expr = method_unwrap_return_expr("_result", &method.return_type, prefix, struct_names);
out.push_str(&render(
"opaque_method_return.jinja",
minijinja::context! {
ret_expr => &ret_expr,
},
));
}
}
fn emit_infallible_method_body(
method: &MethodDef,
prefix: &str,
struct_names: &HashSet<String>,
params: &[ParamDef],
returns_bytes: bool,
c_call: &str,
out: &mut String,
) {
for p in params {
emit_method_param_free(p, struct_names);
}
if returns_bytes {
out.push_str(&render(
"opaque_method_call_discard.jinja",
minijinja::context! {
c_call => c_call,
},
));
out.push_str(&render(
"opaque_bytes_return.jinja",
minijinja::context! {
prefix => prefix,
},
));
} else if matches!(method.return_type, TypeRef::Unit) {
out.push_str(&render(
"opaque_method_unit_call.jinja",
minijinja::context! {
c_call => c_call,
},
));
} else {
out.push_str(&render(
"opaque_method_call_result.jinja",
minijinja::context! {
c_call => c_call,
},
));
let ret_expr = method_unwrap_return_expr("_result", &method.return_type, prefix, struct_names);
out.push_str(&render(
"opaque_method_return.jinja",
minijinja::context! {
ret_expr => &ret_expr,
},
));
}
}