use crate::core::ir::{FunctionDef, ParamDef, TypeRef};
use super::conversions::{dart_call_arg, frb_rust_type_inner, primitive_name};
use super::helpers::emit_cleaned_dartdoc;
pub(crate) fn emit_bridge_fn(
out: &mut String,
f: &FunctionDef,
source_crate_name: &str,
type_paths: &std::collections::HashMap<String, String>,
types_needing_from_conversion: &std::collections::HashSet<String>,
opaque_type_names: &std::collections::HashSet<String>,
stub_methods: &[String],
) {
emit_cleaned_dartdoc(out, &f.doc, "");
let fn_name = &f.name;
let async_kw = if f.is_async { "async " } else { "" };
let params: Vec<String> = f
.params
.iter()
.map(|p| {
let rust_ty = frb_rust_type_mirror(&p.ty, p.optional);
let is_opaque_handle = if let TypeRef::Named(type_name) = &p.ty {
opaque_type_names.contains(type_name.as_str())
} else {
false
};
let mut_prefix = if p.is_mut && is_opaque_handle { "mut " } else { "" };
format!("{mut_prefix}{}: {rust_ty}", p.name)
})
.collect();
let has_error = f.error_type.is_some();
let (return_ty, has_explicit_return) = if has_error {
(
format!("Result<{}, String>", frb_rust_type_mirror(&f.return_type, false)),
true,
)
} else if matches!(f.return_type, TypeRef::Unit) {
(String::new(), false)
} else {
(frb_rust_type_mirror(&f.return_type, false), true)
};
out.push_str(&crate::backends::dart::template_env::render(
"rust_bridge_fn_open.jinja",
minijinja::context! {
async_kw => async_kw,
fn_name => fn_name.as_str(),
params => params.join(", "),
return_ty => return_ty.as_str(),
has_explicit_return => has_explicit_return,
},
));
if stub_methods.contains(fn_name) {
out.push_str(&crate::backends::dart::template_env::render(
"rust_bridge_stub_body.rs.jinja",
minijinja::context! {
fn_name => fn_name.as_str(),
},
));
return;
}
let resolved_path = if f.rust_path.is_empty() {
format!("{source_crate_name}::{fn_name}")
} else {
f.rust_path.replace('-', "_")
};
if f.return_sanitized {
let suppress = if f.params.is_empty() {
String::new()
} else {
let names: Vec<&str> = f.params.iter().map(|p| p.name.as_str()).collect();
if names.len() == 1 {
format!(" let _ = {};\n", names[0])
} else {
format!(" let _ = ({});\n", names.join(", "))
}
};
let default_value = sanitized_return_default(&f.return_type);
let body = if has_error {
format!("{suppress} Ok({default_value})\n")
} else {
format!("{suppress} {default_value}\n")
};
out.push_str(&body);
out.push_str("}\n");
return;
}
let mut pre_call_bindings: Vec<String> = Vec::new();
let call_args: Vec<String> = f
.params
.iter()
.map(|p| {
if let TypeRef::Map(_, _) = &p.ty {
if p.map_is_ahash && p.map_key_is_cow {
let bound_name = format!("__{}_ahash", p.name);
pre_call_bindings.push(format!(
" let {bound_name} = {}.map(|m| m.into_iter().map(|(k, v)| (std::borrow::Cow::Owned(k), serde_json::Value::String(v))).collect::<ahash::AHashMap<std::borrow::Cow<'static, str>, serde_json::Value>>());",
p.name
));
return if p.optional && p.is_ref {
format!("{bound_name}.as_ref()")
} else if p.is_ref {
format!("{bound_name}.as_ref().unwrap()")
} else {
bound_name
};
}
}
dart_call_arg_with_mirror_transmute(
p,
source_crate_name,
type_paths,
types_needing_from_conversion,
opaque_type_names,
)
})
.collect();
let call = format!("{resolved_path}({})", call_args.join(", "));
let ret_transform = return_transform(
&f.return_type,
source_crate_name,
type_paths,
opaque_type_names,
f.returns_ref,
);
let result_cast = if matches!(ret_transform, RetTransform::None) {
build_primitive_result_cast(&f.return_type, f.returns_ref)
} else {
String::new()
};
let body = build_body(
&call,
&result_cast,
&ret_transform,
has_error,
f.is_async,
matches!(f.return_type, TypeRef::Unit),
);
if !pre_call_bindings.is_empty() {
for binding in &pre_call_bindings {
out.push_str(binding);
out.push('\n');
}
}
out.push_str(&body);
out.push_str("}\n");
}
pub(crate) fn frb_rust_type_mirror(ty: &TypeRef, optional: bool) -> String {
let inner = frb_rust_type_mirror_inner(ty);
if optional { format!("Option<{inner}>") } else { inner }
}
fn frb_rust_type_mirror_inner(ty: &TypeRef) -> String {
match ty {
TypeRef::Primitive(p) => frb_rust_type_inner(&TypeRef::Primitive(p.clone())),
TypeRef::String | TypeRef::Char => "String".to_string(),
TypeRef::Bytes => "Vec<u8>".to_string(),
TypeRef::Optional(inner) => format!("Option<{}>", frb_rust_type_mirror_inner(inner)),
TypeRef::Vec(inner) => format!("Vec<{}>", frb_rust_type_mirror_inner(inner)),
TypeRef::Map(k, v) => format!(
"std::collections::HashMap<{}, {}>",
frb_rust_type_mirror_inner(k),
frb_rust_type_mirror_inner(v)
),
TypeRef::Named(name) => name.clone(),
TypeRef::Path => "String".to_string(),
TypeRef::Unit => "()".to_string(),
TypeRef::Json => "String".to_string(),
TypeRef::Duration => "i64".to_string(),
}
}
fn dart_call_arg_with_mirror_transmute(
p: &ParamDef,
source_crate_name: &str,
type_paths: &std::collections::HashMap<String, String>,
types_needing_from_conversion: &std::collections::HashSet<String>,
opaque_type_names: &std::collections::HashSet<String>,
) -> String {
let name = &p.name;
let original = p.original_type.as_deref().unwrap_or("");
let stripped_orig = original
.trim()
.trim_start_matches('&')
.trim_start_matches("mut ")
.trim();
if !stripped_orig.is_empty() && stripped_orig.starts_with("Vec(") && stripped_orig.contains("Named(\"(") {
let tuple_inner = stripped_orig
.find("Named(\"(")
.and_then(|start| {
let rest = &stripped_orig[start + 8..];
rest.find(")\")")
.map(|end| rest[..end].trim_end_matches(')').to_string())
})
.unwrap_or_default();
if tuple_inner.starts_with("PathBuf,") || tuple_inner.starts_with("PathBuf ,") {
return format!("{name}.into_iter().map(|p| (std::path::PathBuf::from(p), None)).collect::<Vec<_>>()");
}
if tuple_inner.starts_with("Vec<u8>,") || tuple_inner.starts_with("Vec<u8> ,") {
return format!(
"{{ let _ = {name}; compile_error!(\"alef cannot bridge Vec<(Vec<u8>, ...)> through Dart FRB; configure dart.exclude_functions for this item\") }}"
);
}
}
if matches!(p.ty, TypeRef::Path) {
if p.optional {
if p.is_ref {
return format!("{name}.as_ref().map(std::path::Path::new)");
}
return format!("{name}.map(std::path::PathBuf::from)");
}
if p.is_ref {
return format!("std::path::Path::new(&{name})");
}
return format!("std::path::PathBuf::from({name})");
}
if let TypeRef::Primitive(prim) = &p.ty {
let target = primitive_name(prim);
if target != "i64" && target != "f64" && target != "bool" {
if p.optional {
return format!("{name}.map(|v| v as {target})");
}
return if p.is_ref {
format!("&({name} as {target})")
} else {
format!("{name} as {target}")
};
}
}
if let TypeRef::Vec(inner) = &p.ty {
if let TypeRef::Primitive(prim) = inner.as_ref() {
let target = primitive_name(prim);
if target != "i64" && target != "f64" && target != "bool" {
if p.optional {
if p.is_ref {
return format!(
"{name}.as_ref().map(|v| v.iter().map(|x| *x as {target}).collect::<Vec<_>>()).as_deref()"
);
}
return format!("{name}.map(|v| v.into_iter().map(|x| x as {target}).collect::<Vec<_>>())");
}
if p.is_ref {
return format!("{name}.iter().map(|x| *x as {target}).collect::<Vec<_>>().as_slice()");
}
return format!("{name}.into_iter().map(|x| x as {target}).collect::<Vec<_>>()");
}
}
}
if let TypeRef::Named(type_name) = &p.ty {
if opaque_type_names.contains(type_name.as_str()) {
if p.optional {
if p.is_ref {
return format!("{name}.as_ref().map(|h| &h.inner)");
}
return format!("{name}.map(|h| h.inner)");
}
if p.is_mut {
return format!("&mut {name}.inner");
}
if p.is_ref {
return format!("&{name}.inner");
}
return format!("{name}.inner");
}
}
if let TypeRef::Named(type_name) = &p.ty {
let core_ty = resolve_core_type(type_name, source_crate_name, type_paths);
if types_needing_from_conversion.contains(type_name.as_str()) {
return build_named_in_from(name, type_name, &core_ty, p.is_ref, p.is_mut, p.optional);
}
return build_named_in_transmute(name, type_name, &core_ty, p.is_ref, p.is_mut, p.optional);
}
if let TypeRef::Vec(inner) = &p.ty {
if let TypeRef::Named(type_name) = inner.as_ref() {
let core_ty = resolve_core_type(type_name, source_crate_name, type_paths);
if types_needing_from_conversion.contains(type_name.as_str()) {
if p.optional {
return format!("{name}.map(|v| v.into_iter().map({core_ty}::from).collect::<Vec<_>>())");
}
return format!("{name}.into_iter().map({core_ty}::from).collect::<Vec<_>>()");
}
if p.optional {
return format!(
"{name}.map(|v| v.into_iter().map(|x| unsafe {{ std::mem::transmute::<{type_name}, {core_ty}>(x) }}).collect::<Vec<_>>())"
);
}
if p.is_ref {
return format!(
"unsafe {{ std::slice::from_raw_parts(\
std::mem::transmute::<*const {type_name}, *const {core_ty}>({name}.as_ptr()), \
{name}.len()) }}"
);
}
if p.is_mut {
return format!(
"unsafe {{ std::slice::from_raw_parts_mut(\
std::mem::transmute::<*mut {type_name}, *mut {core_ty}>({name}.as_mut_ptr()), \
{name}.len()) }}"
);
}
return format!(
"{name}.into_iter().map(|x| unsafe {{ std::mem::transmute::<{type_name}, {core_ty}>(x) }}).collect::<Vec<_>>()"
);
}
}
if let TypeRef::Optional(inner) = &p.ty {
if let TypeRef::Named(type_name) = inner.as_ref() {
let core_ty = resolve_core_type(type_name, source_crate_name, type_paths);
if types_needing_from_conversion.contains(type_name.as_str()) {
return format!("{name}.map({core_ty}::from)");
}
return format!("{name}.map(|v| unsafe {{ std::mem::transmute::<{type_name}, {core_ty}>(v) }})");
}
}
dart_call_arg(p)
}
fn resolve_core_type(
type_name: &str,
source_crate_name: &str,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
match type_paths.get(type_name) {
Some(path) => path.clone(),
None => format!("{source_crate_name}::{type_name}"),
}
}
fn build_named_in_from(
name: &str,
_mirror_name: &str,
core_ty: &str,
is_ref: bool,
is_mut: bool,
optional: bool,
) -> String {
if optional {
return format!("{name}.map({core_ty}::from)");
}
if is_mut {
return format!("&mut {core_ty}::from({name})");
}
if is_ref {
return format!("&{core_ty}::from({name})");
}
format!("{core_ty}::from({name})")
}
fn build_named_in_transmute(
name: &str,
mirror_name: &str,
core_ty: &str,
is_ref: bool,
is_mut: bool,
optional: bool,
) -> String {
if optional {
return format!("{name}.map(|v| unsafe {{ std::mem::transmute::<{mirror_name}, {core_ty}>(v) }})");
}
if is_mut {
return format!("unsafe {{ std::mem::transmute::<&mut {mirror_name}, &mut {core_ty}>(&mut {name}) }}");
}
if is_ref {
return format!("unsafe {{ std::mem::transmute::<&{mirror_name}, &{core_ty}>(&{name}) }}");
}
format!("unsafe {{ std::mem::transmute::<{mirror_name}, {core_ty}>({name}) }}")
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RetTransform {
None,
Map(String),
Suffix(String),
}
fn return_transform(
ty: &TypeRef,
_source_crate_name: &str,
_type_paths: &std::collections::HashMap<String, String>,
opaque_type_names: &std::collections::HashSet<String>,
returns_ref: bool,
) -> RetTransform {
match ty {
TypeRef::Named(mirror_name) => {
if opaque_type_names.contains(mirror_name.as_str()) {
RetTransform::Map(format!("|inner| {mirror_name} {{ inner }}"))
} else {
RetTransform::Map(format!("{mirror_name}::from"))
}
}
TypeRef::Vec(inner) => {
if let TypeRef::Named(mirror_name) = inner.as_ref() {
if returns_ref {
if opaque_type_names.contains(mirror_name.as_str()) {
RetTransform::Suffix(
".iter().map(|inner| ".to_string()
+ mirror_name
+ " { inner: inner.clone() }).collect::<Vec<_>>()",
)
} else {
RetTransform::Suffix(format!(
".iter().map(|x| {mirror_name}::from(x.clone())).collect::<Vec<_>>()"
))
}
} else if opaque_type_names.contains(mirror_name.as_str()) {
RetTransform::Suffix(format!(
".into_iter().map(|inner| {mirror_name} {{ inner }}).collect::<Vec<_>>()"
))
} else {
RetTransform::Suffix(format!(".into_iter().map({mirror_name}::from).collect::<Vec<_>>()"))
}
} else {
RetTransform::None
}
}
TypeRef::Optional(inner) => {
if let TypeRef::Named(mirror_name) = inner.as_ref() {
if opaque_type_names.contains(mirror_name.as_str()) {
RetTransform::Suffix(format!(".map(|inner| {mirror_name} {{ inner }})"))
} else {
RetTransform::Suffix(format!(".map({mirror_name}::from)"))
}
} else {
RetTransform::None
}
}
_ => RetTransform::None,
}
}
fn sanitized_return_default(ty: &TypeRef) -> String {
match ty {
TypeRef::Unit => "()".to_string(),
TypeRef::String | TypeRef::Char | TypeRef::Path => "String::new()".to_string(),
TypeRef::Bytes => "Vec::new()".to_string(),
TypeRef::Primitive(p) => match p {
crate::core::ir::PrimitiveType::Bool => "false".to_string(),
crate::core::ir::PrimitiveType::F32 => "0.0f32".to_string(),
crate::core::ir::PrimitiveType::F64 => "0.0f64".to_string(),
_ => "0".to_string(),
},
TypeRef::Optional(_) => "None".to_string(),
TypeRef::Vec(_) => "Vec::new()".to_string(),
TypeRef::Map(_, _) => "Default::default()".to_string(),
TypeRef::Duration => "0".to_string(),
TypeRef::Named(_) | TypeRef::Json => "Default::default()".to_string(),
}
}
fn build_primitive_result_cast(ty: &TypeRef, returns_ref: bool) -> String {
match ty {
TypeRef::Primitive(prim) => {
let source = primitive_name(prim);
let target = frb_rust_type_inner(ty);
if source == target {
String::new()
} else {
format!(" as {target}")
}
}
TypeRef::Path => ".display().to_string()".to_string(),
TypeRef::String | TypeRef::Char => ".to_string()".to_string(),
TypeRef::Optional(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Path | TypeRef::Char) && returns_ref =>
{
".map(|v| v.to_string())".to_string()
}
TypeRef::Vec(inner) => {
let iter_method = if returns_ref { "iter" } else { "into_iter" };
match inner.as_ref() {
TypeRef::Primitive(prim) => {
let target = primitive_name(prim);
if target == "f64" || target == "i64" || target == "bool" {
String::new()
} else {
format!(
".{iter_method}().map(|x| x as {}).collect::<Vec<_>>()",
frb_rust_type_inner(inner)
)
}
}
TypeRef::Path => {
format!(".{iter_method}().map(|p| p.to_string_lossy().into_owned()).collect::<Vec<_>>()")
}
TypeRef::String | TypeRef::Char => {
format!(".{iter_method}().map(|s| s.to_string()).collect::<Vec<_>>()")
}
TypeRef::Vec(inner2) => {
if let TypeRef::Primitive(prim) = inner2.as_ref() {
let target = primitive_name(prim);
let frb_target = frb_rust_type_inner(inner2);
if target != frb_target.as_str() {
format!(
".{iter_method}().map(|row| row.into_iter().map(|x| x as {frb_target}).collect::<Vec<_>>()).collect::<Vec<_>>()"
)
} else {
String::new()
}
} else {
String::new()
}
}
_ => String::new(),
}
}
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::String | TypeRef::Path | TypeRef::Char => ".map(|s| s.to_string())".to_string(),
_ => String::new(),
},
_ => String::new(),
}
}
fn build_body(
call: &str,
result_cast: &str,
ret_transform: &RetTransform,
has_error: bool,
is_async: bool,
is_unit_return: bool,
) -> String {
match ret_transform {
RetTransform::Map(callable) => {
let map_fn = callable.as_str();
if has_error {
if is_async {
return format!(" {call}.await.map({map_fn}).map_err(|e| e.to_string())\n");
}
return format!(" {call}.map({map_fn}).map_err(|e| e.to_string())\n");
}
let raw = if is_async {
format!("{call}.await")
} else {
call.to_string()
};
format!(" {expr}\n", expr = call_callable(map_fn, &raw))
}
RetTransform::Suffix(suffix) => {
let s = suffix.as_str();
if has_error {
if is_async {
return format!(" {call}.await.map(|v| v{s}).map_err(|e| e.to_string())\n");
}
return format!(" {call}.map(|v| v{s}).map_err(|e| e.to_string())\n");
}
if is_async {
return format!(" {call}.await{s}\n");
}
format!(" {call}{s}\n")
}
RetTransform::None => {
if has_error {
if is_async {
return format!(" {call}.await.map(|v| v{result_cast}).map_err(|e| e.to_string())\n");
}
return format!(" {call}.map(|v| v{result_cast}).map_err(|e| e.to_string())\n");
}
if is_unit_return {
if is_async {
return format!(" {call}.await;\n");
}
return format!(" {call};\n");
}
if is_async {
if result_cast.is_empty() {
return format!(" {call}.await\n");
}
return format!(" {call}.await{result_cast}\n");
}
if result_cast.is_empty() {
format!(" {call}\n")
} else {
format!(" {call}{result_cast}\n")
}
}
}
}
fn call_callable(callable: &str, ident: &str) -> String {
if let Some(rest) = callable.strip_prefix("|inner| ") {
if let Some(name) = rest.strip_suffix(" { inner }") {
return format!("{name} {{ inner: {ident} }}");
}
}
format!("{callable}({ident})")
}
#[cfg(test)]
mod tests;