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);
format!("{}: {rust_ty}", p.name)
})
.collect();
let has_error = f.error_type.is_some();
let return_ty = if has_error {
format!("Result<{}, String>", frb_rust_type_mirror(&f.return_type, false))
} else {
frb_rust_type_mirror(&f.return_type, false)
};
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(),
},
));
let resolved_path = if f.rust_path.is_empty() {
format!("{source_crate_name}::{fn_name}")
} else {
f.rust_path.replace('-', "_")
};
if stub_methods.contains(fn_name) {
out.push_str(" ::std::unimplemented!(\"this method is listed in dart.stub_methods and cannot be bridged through FRB\")\n");
out.push_str("}\n");
return;
}
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_transmute = return_transmute_expr(
&f.return_type,
source_crate_name,
type_paths,
opaque_type_names,
f.returns_ref,
);
let result_cast = if ret_transmute.is_empty() {
build_primitive_result_cast(&f.return_type, f.returns_ref)
} else {
String::new()
};
let body = build_body(&call, &result_cast, &ret_transmute, has_error, f.is_async);
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}; ::std::unimplemented!(\"batch_extract_bytes from Dart not yet bridged\") }}"
);
}
}
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_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.optional);
}
return build_named_in_transmute(name, type_name, &core_ty, p.is_ref, 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::mem::transmute::<*const {type_name}, *const {core_ty}>({name}.as_ptr()) }}"
);
}
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, optional: bool) -> String {
if optional {
return format!("{name}.map({core_ty}::from)");
}
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, optional: bool) -> String {
if optional {
return format!("{name}.map(|v| unsafe {{ std::mem::transmute::<{mirror_name}, {core_ty}>(v) }})");
}
if is_ref {
return format!("unsafe {{ std::mem::transmute::<&{mirror_name}, &{core_ty}>(&{name}) }}");
}
format!("unsafe {{ std::mem::transmute::<{mirror_name}, {core_ty}>({name}) }}")
}
fn return_transmute_expr(
ty: &TypeRef,
_source_crate_name: &str,
_type_paths: &std::collections::HashMap<String, String>,
opaque_type_names: &std::collections::HashSet<String>,
returns_ref: bool,
) -> String {
match ty {
TypeRef::Named(mirror_name) => {
if opaque_type_names.contains(mirror_name.as_str()) {
format!("|inner| {mirror_name} {{ inner }}")
} else {
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()) {
format!("|v| v.iter().map(|inner| {mirror_name} {{ inner: inner.clone() }}).collect()")
} else {
format!("|v| v.iter().map(|x| {mirror_name}::from(x.clone())).collect()")
}
} else if opaque_type_names.contains(mirror_name.as_str()) {
format!("|v| v.into_iter().map(|inner| {mirror_name} {{ inner }}).collect()")
} else {
format!("|v| v.into_iter().map({mirror_name}::from).collect()")
}
} else {
String::new()
}
}
TypeRef::Optional(inner) => {
if let TypeRef::Named(mirror_name) = inner.as_ref() {
if opaque_type_names.contains(mirror_name.as_str()) {
format!("|v: Option<_>| v.map(|inner| {mirror_name} {{ inner }})")
} else {
format!("|v: Option<_>| v.map({mirror_name}::from)")
}
} else {
String::new()
}
}
_ => String::new(),
}
}
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(_) => {
let target = frb_rust_type_inner(ty);
format!(" as {target}")
}
TypeRef::String | TypeRef::Path | 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) => match inner.as_ref() {
TypeRef::Primitive(prim) => {
let target = primitive_name(prim);
if target == "f64" || target == "i64" || target == "bool" {
String::new()
} else {
format!(
".into_iter().map(|x| x as {}).collect::<Vec<_>>()",
frb_rust_type_inner(inner)
)
}
}
TypeRef::String | TypeRef::Path | TypeRef::Char => {
".into_iter().map(|s| s.to_string()).collect::<Vec<_>>()".to_string()
}
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!(
".into_iter().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_transmute: &str, has_error: bool, is_async: bool) -> String {
if !ret_transmute.is_empty() {
let transmute_fn = ret_transmute;
if has_error {
if is_async {
return format!(" {call}.await.map({transmute_fn}).map_err(|e| e.to_string())\n");
}
return format!(" {call}.map({transmute_fn}).map_err(|e| e.to_string())\n");
}
if is_async {
return format!(" ({transmute_fn})({call}.await)\n");
}
return format!(" ({transmute_fn})({call})\n");
}
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_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")
}
}