use super::super::methods::{GoParamCtx, gen_param_to_c};
use super::super::types::emit_type_doc;
use crate::backends::go::c_symbols;
use crate::backends::go::template_env::render;
use crate::backends::go::type_map::{alef_handle_c_type, go_optional_type, go_type};
use crate::codegen::naming::{go_free_function_name, go_param_name, pascal_to_snake, to_go_name};
use crate::core::config::TraitBridgeConfig;
use crate::core::ir::{FunctionDef, TypeRef};
use heck::ToSnakeCase;
use std::collections::HashSet;
fn capsule_param_decls(func: &FunctionDef) -> String {
func.params
.iter()
.map(|p| {
let param_type = if p.optional {
go_optional_type(&p.ty).into_owned()
} else {
go_type(&p.ty).into_owned()
};
format!("{} {}", go_param_name(&p.name), param_type)
})
.collect::<Vec<_>>()
.join(", ")
}
fn render_capsule_return(out: &mut String, is_fallible: bool, c_call: &str, construct: &str) {
out.push_str(&format!("\tcLang := {c_call}\n"));
if is_fallible {
out.push_str("\tif err := lastError(); err != nil {\n\t\treturn nil, err\n\t}\n");
out.push_str(&format!("\treturn {construct}, nil\n"));
} else {
out.push_str("\tif cLang == nil {\n\t\treturn nil\n\t}\n");
out.push_str(&format!("\treturn {construct}\n"));
}
}
pub(in crate::backends::go::gen_bindings) fn gen_capsule_function_wrapper(
func: &FunctionDef,
ffi_prefix: &str,
opaque_names: &std::collections::HashSet<&str>,
ffi_param_enum_names: &std::collections::HashSet<String>,
cfg: &crate::core::config::HostCapsuleTypeConfig,
reserved_type_names: &HashSet<String>,
) -> String {
let mut out = String::with_capacity(1024);
let func_go_name = go_free_function_name(&func.name, reserved_type_names);
emit_type_doc(&mut out, &func_go_name, &func.doc, "calls the FFI function.");
let params_str = capsule_param_decls(func);
let host_type = match cfg.required_host_type("Language", "go") {
Ok(t) => t.to_string(),
Err(e) => return format!("// ALEF ERROR: {e}\n"),
};
let is_fallible = func.error_type.is_some();
let ret_type_str = if is_fallible {
format!(" ({host_type}, error)")
} else {
format!(" {host_type}")
};
out.push_str(&render(
"function_signature.jinja",
crate::alef_context! {
func_name => func_go_name,
params => ¶ms_str,
return_type => &ret_type_str,
},
));
out.push_str(&render("lock_os_thread.jinja", minijinja::Value::default()));
let param_ctx = GoParamCtx {
err_return_prefix: if is_fallible { "nil, " } else { "nil" },
can_return_error: false,
ffi_prefix,
opaque_names,
ffi_param_enum_names,
};
for param in func.params.iter() {
out.push_str(&gen_param_to_c(param, ¶m_ctx));
}
let ffi_name = format!("C.{}", c_symbols::free_function_symbol(ffi_prefix, &func.name));
let c_params: Vec<String> = func
.params
.iter()
.map(|p| go_param_name(&format!("c_{}", p.name)))
.collect();
let c_call = format!("{}({})", ffi_name, c_params.join(", "));
let construct = match cfg.construct_required("cLang", "Language", "go") {
Ok(c) => c,
Err(e) => return format!("// ALEF ERROR: {e}\n"),
};
render_capsule_return(&mut out, is_fallible, &c_call, &construct);
out.push_str(&render("function_body_end.jinja", minijinja::Value::default()));
out
}
struct VisitorWrapperNames<'a> {
options_type: &'a str,
options_type_snake: String,
options_field_go: String,
options_go_name: Option<String>,
has_options_param: bool,
return_type_name: &'a str,
return_type_snake: String,
}
fn visitor_param_decls(func: &FunctionDef, opaque_names: &HashSet<&str>) -> String {
func.params
.iter()
.map(|p| {
let param_type = if p.optional {
go_optional_type(&p.ty).into_owned()
} else if let TypeRef::Named(name) = &p.ty {
if opaque_names.contains(name.as_str()) {
format!("*{}", go_type(&p.ty))
} else {
go_type(&p.ty).into_owned()
}
} else {
go_type(&p.ty).into_owned()
};
format!("{} {}", go_param_name(&p.name), param_type)
})
.collect::<Vec<_>>()
.join(", ")
}
fn render_visitor_helper_guard(
out: &mut String,
func: &FunctionDef,
names: &VisitorWrapperNames<'_>,
helper_name: &str,
) {
let Some(options_var) = names.options_go_name.as_deref() else {
return;
};
let helper_args = func
.params
.iter()
.map(|p| go_param_name(&p.name))
.chain(std::iter::once(format!("{options_var}.{}", names.options_field_go)))
.collect::<Vec<_>>()
.join(", ");
out.push_str(&render(
"visitor_helper_guard.jinja",
crate::alef_context! {
options_var => options_var,
options_field_go => &names.options_field_go,
helper_name => helper_name,
helper_args => &helper_args,
},
));
}
fn render_visitor_c_args(out: &mut String, func: &FunctionDef, options_type: &str) -> Vec<String> {
let mut c_args = Vec::new();
for param in &func.params {
if type_ref_named_type(¶m.ty) == Some(options_type) {
c_args.push("cOptions".to_string());
continue;
}
let go_name = go_param_name(¶m.name);
let c_name = go_param_name(&format!("c_{}", param.name));
if matches!(param.ty, TypeRef::String | TypeRef::Path) {
out.push_str(&render(
"c_string_arg_setup.jinja",
crate::alef_context! {
c_name => &c_name,
go_name => &go_name,
},
));
c_args.push(c_name);
} else {
c_args.push(go_name);
}
}
c_args
}
fn render_visitor_options_conversion(out: &mut String, ffi_prefix: &str, names: &VisitorWrapperNames<'_>) {
if !names.has_options_param {
return;
}
let options_var = names.options_go_name.as_deref().expect("checked above");
let from_json_fn = format!("{ffi_prefix}_{}_from_json", names.options_type_snake);
let free_fn = format!("{ffi_prefix}_{}_free", names.options_type_snake);
let options_description = names.options_type_snake.replace('_', " ");
let options_c_type = alef_handle_c_type(ffi_prefix);
out.push_str(&render(
"options_json_to_c.jinja",
crate::alef_context! {
options_c_type => &options_c_type,
options_var => options_var,
from_json_fn => &from_json_fn,
free_fn => &free_fn,
options_description => &options_description,
},
));
}
fn render_visitor_result(out: &mut String, ffi_prefix: &str, names: &VisitorWrapperNames<'_>) {
out.push_str("\tif ptr == 0 {\n");
out.push_str("\t\tif err := lastError(); err != nil {\n");
out.push_str("\t\t\treturn nil, err\n");
out.push_str("\t\t}\n");
out.push_str("\t\treturn nil, fmt.Errorf(\"conversion returned nil\")\n");
out.push_str("\t}\n");
out.push_str(&render(
"free_type.jinja",
crate::alef_context! {
ffi_prefix => ffi_prefix,
type_snake => &names.return_type_snake,
ptr => "ptr",
},
));
out.push('\n');
let to_json_fn = format!("{ffi_prefix}_{}_to_json", names.return_type_snake);
out.push_str(&render(
"result_json_unmarshal.jinja",
crate::alef_context! {
to_json_fn => &to_json_fn,
use_prefix_free_string => true,
free_string_fn => c_symbols::free_string_symbol(ffi_prefix),
return_type_name => names.return_type_name,
},
));
}
pub(in crate::backends::go::gen_bindings) fn gen_convert_with_visitor_wrapper(
func: &FunctionDef,
ffi_prefix: &str,
opaque_names: &std::collections::HashSet<&str>,
_value_only_types: &std::collections::HashSet<String>,
bridge_cfg: &TraitBridgeConfig,
reserved_type_names: &HashSet<String>,
) -> String {
let mut out = String::with_capacity(2048);
let func_go_name = go_free_function_name(&func.name, reserved_type_names);
emit_type_doc(&mut out, &func_go_name, &func.doc, "runs the generated conversion.");
let options_type = bridge_cfg
.options_type
.as_deref()
.expect("go options-field bridge requires options_type");
let options_field = bridge_cfg
.resolved_options_field()
.expect("go options-field bridge requires options_field or param_name");
let options_param = func
.params
.iter()
.find(|p| type_ref_named_type(&p.ty) == Some(options_type));
let return_type_name = named_return_type(&func.return_type)
.expect("go options-field visitor wrapper currently requires a named return type");
let names = VisitorWrapperNames {
options_type,
options_type_snake: pascal_to_snake(options_type),
options_field_go: to_go_name(options_field),
options_go_name: options_param.map(|p| go_param_name(&p.name)),
has_options_param: options_param.is_some(),
return_type_name,
return_type_snake: pascal_to_snake(return_type_name),
};
let helper_name = format!("{}WithVisitorHelper", func.name.to_snake_case());
let return_go_type = go_optional_type(&func.return_type).into_owned();
out.push_str(&render(
"function_signature.jinja",
crate::alef_context! {
func_name => func_go_name,
params => &visitor_param_decls(func, opaque_names),
return_type => &format!(" ({return_go_type}, error)"),
},
));
out.push_str(&render("lock_os_thread.jinja", minijinja::Value::default()));
render_visitor_helper_guard(&mut out, func, &names, &helper_name);
let ffi_name = format!("C.{}", c_symbols::free_function_symbol(ffi_prefix, &func.name));
let c_args = render_visitor_c_args(&mut out, func, names.options_type);
render_visitor_options_conversion(&mut out, ffi_prefix, &names);
out.push_str(&render(
"ffi_ptr_call.jinja",
crate::alef_context! {
ffi_name => &ffi_name,
c_args => c_args.join(", "),
},
));
render_visitor_result(&mut out, ffi_prefix, &names);
out.push_str(&render("function_body_end.jinja", minijinja::Value::default()));
out
}
fn type_ref_named_type(ty: &TypeRef) -> Option<&str> {
match ty {
TypeRef::Named(name) => Some(name.as_str()),
TypeRef::Optional(inner) => type_ref_named_type(inner),
_ => None,
}
}
fn named_return_type(ty: &TypeRef) -> Option<&str> {
type_ref_named_type(ty)
}