use crate::codegen::generators::type_paths::build_type_path_lookup;
use crate::codegen::naming::{PublicIdentifierKind, public_host_identifier};
use crate::codegen::shared::binding_fields;
use crate::core::backend::GeneratedFile;
use crate::core::config::Language;
use crate::core::config::{AdapterConfig, AdapterPattern, ResolvedCrateConfig, resolve_output_dir};
use crate::core::ir::{ApiSurface, CoreWrapper, EnumDef, FieldDef, MethodDef, ReceiverKind, TypeDef, TypeRef};
use std::collections::HashSet;
mod bridge_fn;
mod cargo;
mod conversions;
mod helpers;
mod mirror;
mod trait_bridge;
mod trait_types;
use bridge_fn::emit_bridge_fn;
use cargo::{emit_build_rs, emit_cargo_toml, emit_frb_yaml};
use mirror::{emit_mirror_enum, emit_mirror_error, emit_mirror_struct};
use trait_bridge::{emit_excluded_bridge_types, emit_trait_bridge, needs_excluded_bridge_type};
pub fn emit(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let rust_dir = resolve_output_dir(None, &config.name, "packages/dart/rust");
let module_name = dart_module_name(&config.name);
let source_crate_name = config.name.replace('-', "_");
let exclude_functions: std::collections::HashSet<String> = config
.dart
.as_ref()
.map(|c| c.exclude_functions.iter().cloned().collect())
.unwrap_or_default();
let exclude_types: std::collections::HashSet<String> = config
.dart
.as_ref()
.map(|c| c.exclude_types.iter().cloned().collect())
.unwrap_or_default();
let stub_methods: Vec<String> = config.dart.as_ref().map(|c| c.stub_methods.clone()).unwrap_or_default();
Ok(vec![
emit_cargo_toml(&rust_dir, api, config, &source_crate_name),
emit_lib_rs(
&rust_dir,
api,
config,
&source_crate_name,
&exclude_functions,
&exclude_types,
&stub_methods,
),
emit_build_rs(&rust_dir, &config.dart_pubspec_name(), &module_name, &source_crate_name),
emit_frb_yaml(&rust_dir, &module_name),
])
}
fn build_type_path_lookup_for_source(
api: &ApiSurface,
source_crate_name: &str,
) -> std::collections::HashMap<String, String> {
let _ = source_crate_name;
build_type_path_lookup(api)
}
fn emit_lib_rs(
rust_dir: &str,
api: &ApiSurface,
config: &ResolvedCrateConfig,
source_crate_name: &str,
exclude_functions: &std::collections::HashSet<String>,
exclude_types: &std::collections::HashSet<String>,
stub_methods: &[String],
) -> GeneratedFile {
let mut content = String::new();
content.push_str("// Generated by alef. Do not edit by hand.\n");
content.push_str("#![allow(unused_variables, unreachable_code)]\n");
content.push_str("#![allow(\n");
content.push_str(" clippy::map_identity,\n");
content.push_str(" clippy::let_and_return,\n");
content.push_str(" clippy::collapsible_match,\n");
content.push_str(" clippy::manual_flatten,\n");
content.push_str(" clippy::too_many_arguments,\n");
content.push_str(" clippy::unit_arg,\n");
content.push_str(" clippy::type_complexity,\n");
content.push_str(" clippy::useless_conversion,\n");
content.push_str(")]\n");
content.push_str("mod frb_generated;\n");
content.push_str("use flutter_rust_bridge::frb;\n");
content.push_str("pub use flutter_rust_bridge::DartFnFuture;\n");
let has_excluded_type_trait_bridge = config
.trait_bridges
.iter()
.filter(|cfg| !cfg.exclude_languages.iter().any(|l| l == "dart"))
.filter_map(|cfg| api.types.iter().find(|t| t.name == cfg.trait_name && t.is_trait))
.flat_map(|trait_def| trait_def.methods.iter())
.filter(|m| m.trait_source.is_none())
.any(|m| {
needs_excluded_bridge_type(&m.return_type, &api.excluded_type_paths)
|| m.params
.iter()
.any(|p| needs_excluded_bridge_type(&p.ty, &api.excluded_type_paths))
});
if has_excluded_type_trait_bridge {
emit_excluded_bridge_types(&mut content, api);
}
{
use crate::core::ir::TypeRef;
fn collect_named(ty: &TypeRef, out: &mut std::collections::BTreeSet<String>) {
match ty {
TypeRef::Named(n) => {
out.insert(n.clone());
}
TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named(inner, out),
TypeRef::Map(k, v) => {
collect_named(k, out);
collect_named(v, out);
}
_ => {}
}
}
let mut referenced: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for ty in &api.types {
if !ty.is_trait {
continue;
}
for method in &ty.methods {
if method.trait_source.is_some() || trait_bridge::return_type_references_trait(&method.return_type, api)
{
continue;
}
for p in &method.params {
collect_named(&p.ty, &mut referenced);
}
collect_named(&method.return_type, &mut referenced);
}
}
let opaque_struct_names_in_scope: std::collections::HashSet<&str> = api
.types
.iter()
.filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
.map(|t| t.name.as_str())
.collect();
let mut emitted: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for name in &referenced {
if opaque_struct_names_in_scope.contains(name.as_str()) {
continue;
}
if let Some(path) = api.excluded_type_paths.get(name) {
if path.is_empty() || emitted.contains(path) {
continue;
}
content.push_str("#[allow(unused_imports)]\n");
content.push_str(&format!("pub use {};\n", path.replace('-', "_")));
emitted.insert(path.clone());
}
}
}
let types_with_direct_sanitized_fields: HashSet<String> = api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
.filter(|t| {
t.fields
.iter()
.any(|f| f.sanitized || has_duration_or_path_field(&f.ty))
})
.map(|t| t.name.clone())
.chain(
api.enums
.iter()
.filter(|e| !exclude_types.contains(&e.name))
.filter(|e| {
e.variants.iter().any(|v| {
v.fields.iter().any(|f| {
f.sanitized || f.optional || has_duration_or_path_field(&f.ty)
})
})
})
.map(|e| e.name.clone()),
)
.collect();
let types_needing_from_conversion: HashSet<String> =
compute_types_containing_sanitized(api, &types_with_direct_sanitized_fields, exclude_types);
for ty in api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.binding_excluded)
{
content.push('\n');
emit_mirror_struct(&mut content, ty, source_crate_name);
}
let streaming_adapters: std::collections::HashMap<String, &AdapterConfig> = config
.adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
.filter_map(|a| {
a.owner_type.as_deref().map(|owner| {
let key = format!("{}.{}", owner, a.name);
(key, a)
})
})
.collect();
let type_paths_for_impls = build_type_path_lookup_for_source(api, source_crate_name);
let mirror_type_names: HashSet<String> = api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque && !t.binding_excluded)
.map(|t| t.name.clone())
.chain(
api.enums
.iter()
.filter(|e| !exclude_types.contains(&e.name) && !e.binding_excluded)
.map(|e| e.name.clone()),
)
.collect();
let opaque_type_names: HashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
.map(|t| t.name.clone())
.collect();
for ty in api.types.iter().filter(|t| {
!exclude_types.contains(&t.name) && !t.is_trait && t.is_opaque && !t.binding_excluded && !t.methods.is_empty()
}) {
content.push('\n');
emit_opaque_impl_block(
&mut content,
ty,
source_crate_name,
stub_methods,
&types_needing_from_conversion,
&opaque_type_names,
&streaming_adapters,
config,
&type_paths_for_impls,
&mirror_type_names,
);
}
for ty in api
.types
.iter()
.filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name) && !t.binding_excluded)
{
if let Some(ctor) = config.client_constructors.get(&ty.name) {
let ctor_body =
crate::codegen::generators::gen_opaque_constructor(ctor, &ty.name, source_crate_name, "#[frb]");
content.push('\n');
content.push_str(&format!("impl {} {{\n{}}}", ty.name, ctor_body));
content.push('\n');
}
}
for en in api
.enums
.iter()
.filter(|e| !exclude_types.contains(&e.name) && !e.binding_excluded)
{
content.push('\n');
emit_mirror_enum(&mut content, en);
}
for error in api.errors.iter().filter(|e| !e.binding_excluded) {
content.push('\n');
emit_mirror_error(&mut content, error, source_crate_name);
}
content.push_str("\n// From<SourceT> conversions for bridge return types.\n");
for ty in api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque && !t.binding_excluded)
{
content.push('\n');
emit_from_impl_for_struct(&mut content, ty, source_crate_name);
}
for en in api
.enums
.iter()
.filter(|e| !exclude_types.contains(&e.name) && !e.binding_excluded)
{
content.push('\n');
emit_from_impl_for_enum(&mut content, en, source_crate_name);
}
let param_types_needing_from: HashSet<String> = api
.functions
.iter()
.filter(|f| !exclude_functions.contains(&f.name) && !has_unbridgeable_param(f))
.flat_map(|f| f.params.iter())
.flat_map(|p| collect_named_types_from_type_ref(&p.ty))
.chain(
api.types
.iter()
.filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
.flat_map(|t| t.methods.iter())
.filter(|m| !m.sanitized)
.flat_map(|m| m.params.iter())
.filter(|p| !p.sanitized)
.flat_map(|p| collect_named_types_from_type_ref(&p.ty)),
)
.filter(|name| types_needing_from_conversion.contains(name))
.chain(
config
.trait_bridges
.iter()
.filter(|cfg| !cfg.exclude_languages.iter().any(|l| l == "dart"))
.filter_map(|cfg| api.types.iter().find(|t| t.name == cfg.trait_name && t.is_trait))
.flat_map(|trait_def| trait_def.methods.iter())
.filter(|m| m.trait_source.is_none())
.flat_map(|m| collect_named_types_from_type_ref(&m.return_type)),
)
.collect();
let types_needing_from_impl = compute_types_needing_from_impl(api, ¶m_types_needing_from, exclude_types);
content.push_str("\n// From<T> for SourceT conversions (mirror-to-core direction).\n");
content.push_str("// Used in bridge functions for types with sanitized fields, and by\n");
content.push_str("// nested conversions within those types.\n");
for ty in api
.types
.iter()
.filter(|t| types_needing_from_impl.contains(&t.name) && !t.is_trait && !t.is_opaque && !t.binding_excluded)
{
content.push('\n');
emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
}
for en in api
.enums
.iter()
.filter(|e| types_needing_from_impl.contains(&e.name) && !e.binding_excluded)
{
content.push('\n');
emit_from_mirror_to_core_enum(&mut content, en, source_crate_name);
}
{
let streaming_param_mirror_types: Vec<&TypeDef> = config
.adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
.flat_map(|a| a.params.iter())
.map(|p| p.ty.as_str())
.filter(|ty_name| mirror_type_names.contains(*ty_name))
.filter(|ty_name| !types_needing_from_impl.contains(*ty_name))
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.filter_map(|ty_name| api.types.iter().find(|t| t.name == ty_name))
.collect();
if !streaming_param_mirror_types.is_empty() {
content.push_str("\n// From<T> for SourceT conversions for streaming-adapter mirror request types.\n");
for ty in streaming_param_mirror_types {
content.push('\n');
emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
}
}
}
let type_paths = build_type_path_lookup_for_source(api, source_crate_name);
for f in api
.functions
.iter()
.filter(|f| !exclude_functions.contains(&f.name))
.filter(|f| !stub_methods.contains(&f.name))
.filter(|f| !has_unbridgeable_param(f))
.filter(|f| {
!crate::codegen::generators::trait_bridge::is_trait_bridge_managed_fn(&f.name, &config.trait_bridges)
})
{
content.push('\n');
emit_bridge_fn(
&mut content,
f,
source_crate_name,
&type_paths,
&types_needing_from_conversion,
&opaque_type_names,
stub_methods,
);
}
content.push_str("\n// `create_<Type>_from_json` helpers — deserialize a JSON string into a mirror type.\n");
for ty in api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque && !t.binding_excluded)
.filter(|t| t.has_serde)
{
content.push('\n');
emit_from_json_fn(&mut content, ty, source_crate_name);
}
let dart_backend_name = "dart";
for bridge_cfg in &config.trait_bridges {
if bridge_cfg.exclude_languages.iter().any(|l| l == dart_backend_name) {
continue;
}
if let Some(trait_def) = api.types.iter().find(|t| t.name == bridge_cfg.trait_name && t.is_trait) {
content.push('\n');
let lifetime_type_names: std::collections::HashSet<String> = api
.types
.iter()
.filter(|t| t.has_lifetime_params)
.map(|t| t.name.clone())
.collect();
emit_trait_bridge(
&mut content,
trait_def,
bridge_cfg,
api,
source_crate_name,
&type_paths,
&lifetime_type_names,
);
}
}
if !api.services.is_empty() {
content.push_str("\nmod service_api;\npub use service_api::*;\n");
}
GeneratedFile {
path: std::path::PathBuf::from(format!("{rust_dir}/src/lib.rs")),
content,
generated_header: false,
}
}
fn dart_module_name(crate_name: &str) -> String {
crate_name.replace('-', "_")
}
fn compute_types_containing_sanitized(
api: &ApiSurface,
direct_sanitized: &HashSet<String>,
exclude_types: &HashSet<String>,
) -> HashSet<String> {
let struct_by_name: std::collections::HashMap<&str, &TypeDef> = api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
.map(|t| (t.name.as_str(), t))
.collect();
let enum_by_name: std::collections::HashMap<&str, &EnumDef> = api
.enums
.iter()
.filter(|e| !exclude_types.contains(&e.name))
.map(|e| (e.name.as_str(), e))
.collect();
let mut result: HashSet<String> = direct_sanitized.clone();
let mut changed = true;
while changed {
changed = false;
for ty in struct_by_name.values() {
if result.contains(&ty.name) {
continue;
}
let references_sanitized = ty
.fields
.iter()
.any(|f| collect_named_types(&f.ty).iter().any(|n| result.contains(n)));
if references_sanitized {
result.insert(ty.name.clone());
changed = true;
}
}
for en in enum_by_name.values() {
if result.contains(&en.name) {
continue;
}
let references_sanitized = en.variants.iter().any(|v| {
v.fields
.iter()
.any(|f| collect_named_types(&f.ty).iter().any(|n| result.contains(n)))
});
if references_sanitized {
result.insert(en.name.clone());
changed = true;
}
}
}
result
}
fn compute_types_needing_from_impl(
api: &ApiSurface,
seed_types: &HashSet<String>,
exclude_types: &HashSet<String>,
) -> HashSet<String> {
let struct_by_name: std::collections::HashMap<&str, &TypeDef> = api
.types
.iter()
.filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
.map(|t| (t.name.as_str(), t))
.collect();
let enum_by_name: std::collections::HashMap<&str, &EnumDef> = api
.enums
.iter()
.filter(|e| !exclude_types.contains(&e.name))
.map(|e| (e.name.as_str(), e))
.collect();
let mut result: HashSet<String> = seed_types.clone();
let mut worklist: Vec<String> = seed_types.iter().cloned().collect();
while let Some(type_name) = worklist.pop() {
if let Some(ty) = struct_by_name.get(type_name.as_str()) {
for field in binding_fields(&ty.fields) {
if field.sanitized {
continue; }
for named in collect_named_types(&field.ty) {
if !result.contains(&named)
&& (struct_by_name.contains_key(named.as_str()) || enum_by_name.contains_key(named.as_str()))
{
result.insert(named.clone());
worklist.push(named);
}
}
}
} else if let Some(en) = enum_by_name.get(type_name.as_str()) {
for variant in &en.variants {
for field in &variant.fields {
if field.sanitized {
continue;
}
for named in collect_named_types(&field.ty) {
if !result.contains(&named)
&& (struct_by_name.contains_key(named.as_str())
|| enum_by_name.contains_key(named.as_str()))
{
result.insert(named.clone());
worklist.push(named);
}
}
}
}
}
}
result
}
fn collect_named_types(ty: &TypeRef) -> Vec<String> {
collect_named_types_from_type_ref(ty)
}
fn collect_named_types_from_type_ref(ty: &TypeRef) -> Vec<String> {
match ty {
TypeRef::Named(name) => vec![name.clone()],
TypeRef::Vec(inner) | TypeRef::Optional(inner) => collect_named_types_from_type_ref(inner),
TypeRef::Map(k, v) => {
let mut names = collect_named_types_from_type_ref(k);
names.extend(collect_named_types_from_type_ref(v));
names
}
_ => vec![],
}
}
fn emit_rust_struct_field(out: &mut String, cfg: Option<&str>, field_name: &str, expr: &str) {
out.push_str(&crate::backends::dart::template_env::render(
"rust_struct_field_assignment.jinja",
minijinja::context! {
cfg => cfg,
field_name => field_name,
expr => expr,
},
));
}
fn emit_from_impl_for_struct(out: &mut String, ty: &TypeDef, source_crate_name: &str) {
let name = &ty.name;
let core_ty_base = if ty.rust_path.is_empty() {
format!("{source_crate_name}::{name}")
} else {
ty.rust_path.replace('-', "_")
};
let core_ty = if ty.has_lifetime_params {
format!("{core_ty_base}<'_>")
} else {
core_ty_base
};
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_core_struct_open.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
name => name.as_str(),
},
));
for field in binding_fields(&ty.fields) {
if field.sanitized {
let fallback = sanitized_field_from_expr(field);
emit_rust_struct_field(out, None, &field.name, &fallback);
} else {
let expr = field_from_expr(field, source_crate_name);
emit_rust_struct_field(out, None, &field.name, &expr);
}
}
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_impl_close.jinja",
minijinja::context! {},
));
}
fn field_from_expr(field: &FieldDef, source_crate_name: &str) -> String {
let name = &field.name;
let _ = source_crate_name;
match &field.ty {
TypeRef::Json => {
if field.optional {
format!("v.{name}.map(|j| serde_json::to_string(&j).unwrap_or_default())")
} else {
format!("serde_json::to_string(&v.{name}).unwrap_or_default()")
}
}
TypeRef::String => {
if field.optional {
format!("v.{name}.map(|s| s.into())")
} else {
format!("v.{name}.into()")
}
}
TypeRef::Char => {
if field.optional {
format!("v.{name}.map(|c| c.to_string())")
} else {
format!("v.{name}.to_string()")
}
}
TypeRef::Path => {
if field.optional {
format!("v.{name}.map(|p| p.to_string_lossy().into_owned())")
} else {
format!("v.{name}.to_string_lossy().into_owned()")
}
}
TypeRef::Bytes => {
match field.core_wrapper {
CoreWrapper::Arc | CoreWrapper::ArcMutex => {
if field.optional {
format!("v.{name}.map(|a| (*a).clone().into())")
} else {
format!("(*v.{name}).clone().into()")
}
}
_ => {
if field.optional {
format!("v.{name}.map(|b| b.into())")
} else {
format!("v.{name}.into()")
}
}
}
}
TypeRef::Named(inner_name) => {
match field.core_wrapper {
CoreWrapper::Arc | CoreWrapper::ArcMutex => {
if field.optional {
format!("v.{name}.map(|a| {inner_name}::from((*a).clone()))")
} else {
format!("{inner_name}::from((*v.{name}).clone())")
}
}
_ => {
if field.optional && field.is_boxed {
format!("v.{name}.map(|b| {inner_name}::from(*b))")
} else if field.optional {
format!("v.{name}.map({inner_name}::from)")
} else if field.is_boxed {
format!("{inner_name}::from(*v.{name})")
} else {
format!("{inner_name}::from(v.{name})")
}
}
}
}
TypeRef::Vec(inner) => vec_inner_from_expr(
inner,
&field.vec_inner_core_wrapper,
field.newtype_wrapper.as_deref(),
name,
field.optional,
),
TypeRef::Optional(inner) => {
let flatten = if field.optional { ".flatten()" } else { "" };
match inner.as_ref() {
TypeRef::Named(inner_name) => {
format!("v.{name}{flatten}.map({inner_name}::from)")
}
TypeRef::String => {
format!("v.{name}{flatten}.map(|s| s.into())")
}
TypeRef::Char => {
format!("v.{name}{flatten}.map(|s| s.into())")
}
TypeRef::Path => {
format!("v.{name}{flatten}.map(|p| p.to_string_lossy().into_owned())")
}
TypeRef::Primitive(_) => {
format!("v.{name}{flatten}.map(|x| x as _)")
}
_ => format!("v.{name}{flatten}"),
}
}
TypeRef::Map(k, v_ty) => {
map_from_expr(name, k, v_ty, field.optional, field.core_wrapper.clone())
}
TypeRef::Duration => {
if field.optional {
format!("v.{name}.map(|d| d.as_millis() as i64)")
} else {
format!("v.{name}.as_millis() as i64")
}
}
TypeRef::Primitive(_) | TypeRef::Unit => {
if let Some(_nw) = &field.newtype_wrapper {
if field.optional {
format!("v.{name}.map(|x| x.0 as _)")
} else {
format!("v.{name}.0 as _")
}
} else if field.optional {
format!("v.{name}.map(|x| x as _)")
} else {
format!("v.{name} as _")
}
}
}
}
fn vec_inner_from_expr(
inner: &TypeRef,
vec_inner_core_wrapper: &CoreWrapper,
field_newtype_wrapper: Option<&str>,
name: &str,
optional: bool,
) -> String {
let item_conv = match (inner, vec_inner_core_wrapper) {
(TypeRef::Named(inner_name), CoreWrapper::Arc | CoreWrapper::ArcMutex) => {
format!("|a| {inner_name}::from((*a).clone())")
}
(TypeRef::Named(inner_name), _) => {
format!("{inner_name}::from")
}
(TypeRef::String, _) => {
"|s| s.into()".to_string()
}
(TypeRef::Char, _) => "|s| s.into()".to_string(),
(TypeRef::Json, _) => "|j| serde_json::to_string(&j).unwrap_or_default()".to_string(),
(TypeRef::Path, _) => "|p: std::path::PathBuf| p.to_string_lossy().into_owned()".to_string(),
(TypeRef::Bytes, CoreWrapper::Arc | CoreWrapper::ArcMutex) => "|a| (*a).clone().into()".to_string(),
(TypeRef::Bytes, _) => "|b| b.into()".to_string(),
(TypeRef::Primitive(_), _) => {
if field_newtype_wrapper.is_some() {
"|x| x.0 as _".to_string()
} else {
"|x| x as _".to_string()
}
}
(TypeRef::Vec(inner2), _) => {
match inner2.as_ref() {
TypeRef::Primitive(_) => {
return if optional {
format!(
"v.{name}.map(|vec| vec.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect::<Vec<_>>()).collect::<Vec<_>>())"
)
} else {
format!(
"v.{name}.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect::<Vec<_>>()).collect::<Vec<_>>()"
)
};
}
_ => {
return format!("v.{name}");
}
}
}
_ => {
return format!("v.{name}");
}
};
if optional {
format!("v.{name}.map(|vec| vec.into_iter().map({item_conv}).collect::<Vec<_>>())")
} else {
format!("v.{name}.into_iter().map({item_conv}).collect::<Vec<_>>()")
}
}
fn emit_from_mirror_to_core_struct(out: &mut String, ty: &TypeDef, source_crate_name: &str) {
let name = &ty.name;
let core_ty = if ty.rust_path.is_empty() {
format!("{source_crate_name}::{name}")
} else {
ty.rust_path.replace('-', "_")
};
if ty.has_stripped_cfg_fields {
out.push_str("#[allow(clippy::needless_update)]\n");
}
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_mirror_struct_open.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
name => name.as_str(),
},
));
for field in &ty.fields {
if field.binding_excluded {
emit_rust_struct_field(out, None, &field.name, "Default::default()");
continue;
}
let safe_sanitized_string = matches!(field.ty, TypeRef::String) && field.core_wrapper == CoreWrapper::Cow;
if field.sanitized && !safe_sanitized_string {
emit_rust_struct_field(out, None, &field.name, "Default::default()");
} else {
let expr = field_from_expr_to_core(field, source_crate_name);
emit_rust_struct_field(out, None, &field.name, &expr);
}
}
if ty.has_stripped_cfg_fields {
out.push_str(" ..Default::default()\n");
}
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_impl_close.jinja",
minijinja::context! {},
));
}
fn emit_from_mirror_to_core_enum(out: &mut String, en: &EnumDef, source_crate_name: &str) {
let name = &en.name;
let core_ty = if en.rust_path.is_empty() {
format!("{source_crate_name}::{name}")
} else {
en.rust_path.replace('-', "_")
};
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_mirror_enum_open.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
name => name.as_str(),
},
));
for variant in &en.variants {
let vname = &variant.name;
if variant.originally_had_data_fields {
let stripped_fields: Vec<&crate::core::ir::FieldDef> =
variant.fields.iter().filter(|f| f.binding_excluded).collect();
if variant.is_tuple {
let args: Vec<String> = stripped_fields
.iter()
.map(|_| "Default::default()".to_string())
.collect();
out.push_str(&format!(
" {name}::{vname} => {core_ty}::{vname}({}),\n",
args.join(", ")
));
} else {
let args: Vec<String> = stripped_fields
.iter()
.map(|f| format!("{}: Default::default()", f.name))
.collect();
out.push_str(&format!(
" {name}::{vname} => {core_ty}::{vname} {{ {} }},\n",
args.join(", ")
));
}
} else {
let visible_fields: Vec<&crate::core::ir::FieldDef> =
variant.fields.iter().filter(|f| !f.binding_excluded).collect();
if visible_fields.is_empty() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_enum_unit_to_core_arm.jinja",
minijinja::context! {
name => name.as_str(),
vname => vname.as_str(),
core_ty => core_ty.as_str(),
},
));
} else if variant.is_tuple {
let mirror_bindings: Vec<String> = (0..visible_fields.len()).map(|i| format!("field{i}")).collect();
let core_args: Vec<String> = visible_fields
.iter()
.enumerate()
.map(|(i, field)| enum_variant_field_conv_to_core(&format!("field{i}"), field))
.collect();
out.push_str(&crate::backends::dart::template_env::render(
"rust_enum_tuple_to_core_arm.jinja",
minijinja::context! {
name => name.as_str(),
vname => vname.as_str(),
core_ty => core_ty.as_str(),
mirror_bindings => mirror_bindings.join(", "),
core_args => core_args.join(", "),
},
));
} else {
let mirror_field_names: Vec<&str> = visible_fields.iter().map(|f| f.name.as_str()).collect();
let mut core_args: Vec<String> = visible_fields
.iter()
.map(|field| {
let fname = &field.name;
let conv = enum_variant_field_conv_to_core(fname, field);
format!("{fname}: {conv}")
})
.collect();
let excluded_args: Vec<String> = variant
.fields
.iter()
.filter(|f| f.binding_excluded)
.map(|f| format!("{}: Default::default()", f.name))
.collect();
core_args.extend(excluded_args);
out.push_str(&crate::backends::dart::template_env::render(
"rust_enum_struct_to_core_arm.jinja",
minijinja::context! {
name => name.as_str(),
vname => vname.as_str(),
core_ty => core_ty.as_str(),
field_names => mirror_field_names.join(", "),
core_args => core_args.join(", "),
},
));
}
}
}
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_impl_close.jinja",
minijinja::context! {},
));
}
fn enum_variant_field_conv_to_core(binding: &str, field: &FieldDef) -> String {
if field.sanitized {
return "Default::default()".to_string();
}
match &field.ty {
TypeRef::Named(_) => {
match field.core_wrapper {
CoreWrapper::Arc | CoreWrapper::ArcMutex => {
if field.optional {
format!("{binding}.map(|x| std::sync::Arc::new(x.into()))")
} else {
format!("std::sync::Arc::new({binding}.into())")
}
}
_ if field.is_boxed => {
if field.optional {
format!("{binding}.map(|x| Box::new(x.into()))")
} else {
format!("Box::new({binding}.into())")
}
}
_ => {
if field.optional {
format!("{binding}.map(Into::into)")
} else {
format!("{binding}.into()")
}
}
}
}
TypeRef::String => {
if field.optional {
if matches!(field.core_wrapper, CoreWrapper::Cow) {
format!("if {binding}.is_empty() {{ None }} else {{ Some({binding}.into()) }}")
} else {
format!("if {binding}.is_empty() {{ None }} else {{ Some({binding}) }}")
}
} else if matches!(field.core_wrapper, CoreWrapper::Cow) {
format!("{binding}.into()")
} else {
binding.to_string()
}
}
TypeRef::Char => {
if field.optional {
format!("{binding}.as_deref().and_then(|s| s.chars().next())")
} else {
format!("{binding}.chars().next().unwrap_or_default()")
}
}
TypeRef::Path => {
if field.optional {
format!("if {binding}.is_empty() {{ None }} else {{ Some(std::path::PathBuf::from({binding})) }}")
} else {
format!("std::path::PathBuf::from({binding})")
}
}
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(_) => format!("{binding}.into_iter().map(Into::into).collect()"),
TypeRef::String => binding.to_string(),
_ => format!("{binding}.into_iter().map(|x| x as _).collect()"),
},
TypeRef::Primitive(prim) => {
use crate::core::ir::PrimitiveType;
if matches!(prim, PrimitiveType::Bool) {
return match field.optional {
true => format!("if {binding} {{ Some({binding}) }} else {{ None }}"),
false => binding.to_string(),
};
}
match (&field.newtype_wrapper, field.optional) {
(Some(nw), true) => format!("if {binding} == 0 {{ None }} else {{ Some({nw}({binding} as _)) }}"),
(Some(nw), false) => format!("{nw}({binding} as _)"),
(None, true) => format!("if {binding} == 0 {{ None }} else {{ Some({binding} as _) }}"),
(None, false) => format!("{binding} as _"),
}
}
_ => {
if field.optional {
format!("{binding}.map(Into::into)")
} else {
format!("{binding}.into()")
}
}
}
}
fn field_from_expr_to_core(field: &FieldDef, _source_crate_name: &str) -> String {
let name = &field.name;
match &field.ty {
TypeRef::String => {
if field.optional {
format!("v.{name}.map(Into::into)")
} else {
format!("v.{name}.into()")
}
}
TypeRef::Char => {
if field.optional {
format!("v.{name}.as_deref().and_then(|s| s.chars().next())")
} else {
format!("v.{name}.chars().next().unwrap_or_default()")
}
}
TypeRef::Path => {
if field.optional {
format!("v.{name}.map(std::path::PathBuf::from)")
} else {
format!("std::path::PathBuf::from(v.{name})")
}
}
TypeRef::Bytes => {
if field.optional {
format!("v.{name}.map(Into::into)")
} else {
format!("v.{name}.into()")
}
}
TypeRef::Json => {
if field.optional {
format!("v.{name}.as_deref().and_then(|s| serde_json::from_str(s).ok())")
} else {
format!("serde_json::from_str(&v.{name}).unwrap_or_default()")
}
}
TypeRef::Named(_) => {
match field.core_wrapper {
CoreWrapper::Arc | CoreWrapper::ArcMutex => {
if field.optional {
format!("v.{name}.map(|x| std::sync::Arc::new(x.into()))")
} else {
format!("std::sync::Arc::new(v.{name}.into())")
}
}
_ if field.is_boxed => {
if field.optional {
format!("v.{name}.map(|x| Box::new(x.into()))")
} else {
format!("Box::new(v.{name}.into())")
}
}
_ => {
if field.optional {
format!("v.{name}.map(Into::into)")
} else {
format!("v.{name}.into()")
}
}
}
}
TypeRef::Vec(inner) => {
match inner.as_ref() {
TypeRef::Named(_) => {
match field.vec_inner_core_wrapper {
CoreWrapper::Arc | CoreWrapper::ArcMutex => {
if field.optional {
format!(
"v.{name}.map(|vec| vec.into_iter().map(|x| std::sync::Arc::new(x.into())).collect())"
)
} else {
format!("v.{name}.into_iter().map(|x| std::sync::Arc::new(x.into())).collect()")
}
}
_ => {
if field.optional {
format!("v.{name}.map(|vec| vec.into_iter().map(Into::into).collect())")
} else {
format!("v.{name}.into_iter().map(Into::into).collect()")
}
}
}
}
TypeRef::Vec(inner_inner) => {
match inner_inner.as_ref() {
TypeRef::Primitive(_) => {
if field.optional {
format!(
"v.{name}.map(|vv| vv.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect()).collect())"
)
} else {
format!(
"v.{name}.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect()).collect()"
)
}
}
_ => {
if field.optional {
format!(
"v.{name}.map(|vv| vv.into_iter().map(|inner| inner.into_iter().map(Into::into).collect()).collect())"
)
} else {
format!(
"v.{name}.into_iter().map(|inner| inner.into_iter().map(Into::into).collect()).collect()"
)
}
}
}
}
TypeRef::Primitive(_) => {
let elem_conv = if let Some(nw) = &field.newtype_wrapper {
format!("|x| {nw}(x as _)")
} else {
"|x| x as _".to_string()
};
if field.optional {
format!("v.{name}.map(|vec| vec.into_iter().map({elem_conv}).collect())")
} else {
format!("v.{name}.into_iter().map({elem_conv}).collect()")
}
}
_ => {
if field.optional {
format!("v.{name}.map(|vec| vec.into_iter().map(Into::into).collect())")
} else {
format!("v.{name}.into_iter().map(Into::into).collect()")
}
}
}
}
TypeRef::Optional(inner) => {
let wrap_some = if field.optional { ".map(Some)" } else { "" };
match inner.as_ref() {
TypeRef::Named(_) => format!("v.{name}.map(Into::into){wrap_some}"),
TypeRef::String | TypeRef::Char => format!("v.{name}.map(Into::into){wrap_some}"),
TypeRef::Path => format!("v.{name}.map(std::path::PathBuf::from){wrap_some}"),
TypeRef::Primitive(_) => format!("v.{name}.map(|x| x as _){wrap_some}"),
_ => format!("v.{name}{wrap_some}"),
}
}
TypeRef::Primitive(_) => {
if let Some(nw) = &field.newtype_wrapper {
if field.optional {
format!("v.{name}.map(|x| {nw}(x as _))")
} else {
format!("{nw}(v.{name} as _)")
}
} else if field.optional {
format!("v.{name}.map(|x| x as _)")
} else {
format!("v.{name} as _")
}
}
TypeRef::Duration => {
if field.optional {
format!("v.{name}.map(|ms| std::time::Duration::from_millis(ms as u64))")
} else {
format!("std::time::Duration::from_millis(v.{name} as u64)")
}
}
TypeRef::Map(_, v_ty) => {
let val_conv = match v_ty.as_ref() {
TypeRef::Primitive(_) => "v as _",
TypeRef::Named(_) => "v.into()",
_ => "v.into()",
};
if field.optional {
format!("v.{name}.map(|m| m.into_iter().map(|(k, v)| (k.into(), {val_conv})).collect())")
} else {
format!("v.{name}.into_iter().map(|(k, v)| (k.into(), {val_conv})).collect()")
}
}
TypeRef::Unit => "()".to_string(),
}
}
fn map_from_expr(name: &str, _k: &TypeRef, v_ty: &TypeRef, optional: bool, core_wrapper: CoreWrapper) -> String {
let value_conv = match v_ty {
TypeRef::Json => {
"serde_json::to_string(&v).unwrap_or_default()"
}
TypeRef::Named(mirror_name) => return map_named_from_expr(name, mirror_name, optional, core_wrapper),
TypeRef::Primitive(_) => {
"v as _"
}
_ => "v.into()",
};
let iter_method = if core_wrapper == CoreWrapper::Cow {
"into_owned().into_iter()"
} else {
"into_iter()"
};
let iter_expr = format!("{iter_method}.map(|(k, v)| (k.into(), {value_conv})).collect()");
if optional {
format!("v.{name}.map(|m| m.{iter_expr})")
} else {
format!("v.{name}.{iter_expr}")
}
}
fn map_named_from_expr(field_name: &str, mirror_name: &str, optional: bool, core_wrapper: CoreWrapper) -> String {
let iter_method = if core_wrapper == CoreWrapper::Cow {
"into_owned().into_iter()"
} else {
"into_iter()"
};
let iter_expr = format!("{iter_method}.map(|(k, v)| (k.into(), {mirror_name}::from(v))).collect()");
if optional {
format!("v.{field_name}.map(|m| m.{iter_expr})")
} else {
format!("v.{field_name}.{iter_expr}")
}
}
fn sanitized_field_from_expr(field: &FieldDef) -> String {
let name = &field.name;
match &field.ty {
TypeRef::Primitive(_) => {
if field.optional {
format!("v.{name}.map(|x| x as _)")
} else {
format!("v.{name} as _")
}
}
TypeRef::String | TypeRef::Char if field.core_wrapper == CoreWrapper::Cow => {
if field.optional {
format!("v.{name}.map(|s| s.into_owned())")
} else {
format!("v.{name}.into_owned()")
}
}
_ => {
let _ = name;
String::from("Default::default()")
}
}
}
fn emit_from_impl_for_enum(out: &mut String, en: &EnumDef, source_crate_name: &str) {
let name = &en.name;
let core_ty = if en.rust_path.is_empty() {
format!("{source_crate_name}::{name}")
} else {
en.rust_path.replace('-', "_")
};
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_core_enum_open.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
name => name.as_str(),
},
));
for variant in &en.excluded_variants {
let vname = &variant.name;
let template = if variant.is_tuple || !variant.fields.is_empty() {
"rust_enum_excluded_variant_tuple_arm.jinja"
} else {
"rust_enum_excluded_variant_unit_arm.jinja"
};
out.push_str(&crate::backends::dart::template_env::render(
template,
minijinja::context! {
core_ty => core_ty.as_str(),
vname => vname.as_str(),
name => name.as_str(),
},
));
}
for variant in &en.variants {
let vname = &variant.name;
let visible_fields: Vec<&crate::core::ir::FieldDef> =
variant.fields.iter().filter(|f| !f.binding_excluded).collect();
if variant.originally_had_data_fields {
let template = if variant.is_tuple {
"rust_enum_tuple_stripped_from_core_arm.jinja"
} else {
"rust_enum_struct_stripped_from_core_arm.jinja"
};
out.push_str(&crate::backends::dart::template_env::render(
template,
minijinja::context! {
core_ty => core_ty.as_str(),
vname => vname.as_str(),
name => name.as_str(),
},
));
} else if visible_fields.is_empty() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_enum_unit_from_core_arm.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
vname => vname.as_str(),
name => name.as_str(),
},
));
} else if variant.is_tuple {
let field_patterns: Vec<String> = (0..visible_fields.len()).map(|i| format!("f{i}")).collect();
let mirror_fields: Vec<String> = visible_fields
.iter()
.enumerate()
.map(|(i, field)| {
let conv = enum_variant_field_conv(&format!("f{i}"), field, source_crate_name);
format!("field{i}: {conv}")
})
.collect();
out.push_str(&crate::backends::dart::template_env::render(
"rust_enum_tuple_from_core_arm.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
vname => vname.as_str(),
name => name.as_str(),
field_patterns => field_patterns.join(", "),
mirror_fields => mirror_fields.join(", "),
},
));
} else {
let field_names: Vec<&str> = visible_fields.iter().map(|f| f.name.as_str()).collect();
let field_convs: Vec<String> = visible_fields
.iter()
.map(|field| {
let fname = &field.name;
let conv = enum_variant_field_conv(fname, field, source_crate_name);
format!("{fname}: {conv}")
})
.collect();
out.push_str(&crate::backends::dart::template_env::render(
"rust_enum_struct_from_core_arm.jinja",
minijinja::context! {
core_ty => core_ty.as_str(),
vname => vname.as_str(),
name => name.as_str(),
field_names => field_names.join(", "),
field_convs => field_convs.join(", "),
},
));
}
}
out.push_str(&crate::backends::dart::template_env::render(
"rust_from_impl_close.jinja",
minijinja::context! {},
));
}
fn enum_variant_field_conv(binding: &str, field: &FieldDef, source_crate_name: &str) -> String {
let _ = source_crate_name;
if field.sanitized {
match &field.ty {
TypeRef::Primitive(_) => {
if field.optional {
return format!("{binding}.map(|x| x as _).unwrap_or_default()");
}
return format!("{binding} as _");
}
TypeRef::Vec(inner) => {
if matches!(inner.as_ref(), TypeRef::Vec(inner_inner) if matches!(inner_inner.as_ref(), TypeRef::String))
{
if field.optional {
return format!(
"{binding}.map(|v| v.into_iter().map(|(a, b)| vec![a.to_string(), b.to_string()]).collect()).unwrap_or_default()"
);
}
return format!("{binding}.into_iter().map(|(a, b)| vec![a.to_string(), b.to_string()]).collect()");
}
if field.optional {
return format!(
"{binding}.map(|v| v.into_iter().map(|e| serde_json::to_string(&e).unwrap_or_default()).collect()).unwrap_or_default()"
);
}
return format!(
"{binding}.into_iter().map(|e| serde_json::to_string(&e).unwrap_or_default()).collect()"
);
}
_ => {
if field.optional {
return format!(
"{binding}.map(|v| serde_json::to_string(&v).unwrap_or_default()).unwrap_or_default()"
);
}
return format!("serde_json::to_string(&{binding}).unwrap_or_default()");
}
}
}
match &field.ty {
TypeRef::Named(inner_name) => {
if field.is_boxed && field.optional {
format!("{binding}.map(|b| {inner_name}::from(*b)).unwrap_or_default()")
} else if field.is_boxed {
format!("{inner_name}::from(*{binding})")
} else if field.optional {
format!("{binding}.map({inner_name}::from).unwrap_or_default()")
} else {
format!("{inner_name}::from({binding})")
}
}
TypeRef::Vec(inner) => {
let item_conv = match inner.as_ref() {
TypeRef::Named(inner_name) => Some(format!("{inner_name}::from")),
TypeRef::Primitive(_) => Some("|x| x as _".to_string()),
TypeRef::String => None,
_ => Some("|s| s.into()".to_string()),
};
match (item_conv, field.optional) {
(None, true) => format!("{binding}.unwrap_or_default()"),
(None, false) => binding.to_string(),
(Some(conv), true) => {
format!("{binding}.map(|v| v.into_iter().map({conv}).collect()).unwrap_or_default()")
}
(Some(conv), false) => format!("{binding}.into_iter().map({conv}).collect()"),
}
}
TypeRef::String => {
if field.optional {
format!("{binding}.unwrap_or_default()")
} else if matches!(field.core_wrapper, CoreWrapper::Cow) {
format!("{binding}.into()")
} else {
binding.to_string()
}
}
TypeRef::Char => {
if field.optional {
format!("{binding}.map(|c| c.to_string()).unwrap_or_default()")
} else {
format!("{binding}.to_string()")
}
}
TypeRef::Path => {
if field.optional {
format!("{binding}.map(|p| p.to_string_lossy().into_owned()).unwrap_or_default()")
} else {
format!("{binding}.to_string_lossy().into_owned()")
}
}
TypeRef::Json => {
if field.optional {
format!("{binding}.map(|j| serde_json::to_string(&j).unwrap_or_default()).unwrap_or_default()")
} else {
format!("serde_json::to_string(&{binding}).unwrap_or_default()")
}
}
TypeRef::Primitive(_) => {
if let Some(_nw) = &field.newtype_wrapper {
if field.optional {
format!("{binding}.map(|x| x.0 as _).unwrap_or_default()")
} else {
format!("{binding}.0 as _")
}
} else if field.optional {
format!("{binding}.map(|x| x as _).unwrap_or_default()")
} else {
format!("{binding} as _")
}
}
TypeRef::Map(_, v_ty) => {
let needs_value_conv = matches!(v_ty.as_ref(), TypeRef::Json | TypeRef::Named(_));
if needs_value_conv {
format!(
"{binding}.into_iter().map(|(k, v)| (k.into(), serde_json::to_string(&v).unwrap_or_default())).collect()"
)
} else {
format!("{binding}.into_iter().map(|(k, v)| (k.into(), v.into())).collect()")
}
}
_ => binding.to_string(),
}
}
fn has_duration_or_path_field(ty: &TypeRef) -> bool {
use crate::core::ir::PrimitiveType;
match ty {
TypeRef::Duration | TypeRef::Path | TypeRef::Json => true,
TypeRef::Primitive(p) => !matches!(p, PrimitiveType::I64 | PrimitiveType::F64 | PrimitiveType::Bool),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => has_duration_or_path_field(inner),
_ => false,
}
}
fn has_unbridgeable_param(f: &crate::core::ir::FunctionDef) -> bool {
for p in &f.params {
let Some(orig) = p.original_type.as_deref() else {
continue;
};
let stripped_orig = orig.replace(' ', "");
if stripped_orig.starts_with("Vec(Named(\"(Vec<u8>,") {
return true;
}
}
false
}
#[allow(clippy::too_many_arguments)]
fn emit_opaque_impl_block(
out: &mut String,
ty: &TypeDef,
source_crate_name: &str,
stub_methods: &[String],
types_needing_from_conversion: &HashSet<String>,
opaque_type_names: &HashSet<String>,
streaming_adapters: &std::collections::HashMap<String, &AdapterConfig>,
config: &ResolvedCrateConfig,
type_paths: &std::collections::HashMap<String, String>,
mirror_type_names: &HashSet<String>,
) {
let type_name = &ty.name;
let mut trait_uses: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
fn collect_named(ty: &crate::core::ir::TypeRef, out: &mut std::collections::BTreeSet<String>) {
use crate::core::ir::TypeRef;
match ty {
TypeRef::Named(n) => {
out.insert(n.clone());
}
TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named(inner, out),
TypeRef::Map(k, v) => {
collect_named(k, out);
collect_named(v, out);
}
_ => {}
}
}
let mut named_refs: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for method in &ty.methods {
if stub_methods.contains(&method.name) {
continue;
}
if method.sanitized {
let adapter_key = format!("{type_name}.{}", method.name);
if !streaming_adapters.contains_key(&adapter_key) {
continue;
}
}
if let Some(path) = method.trait_source.as_deref() {
trait_uses.insert(path.to_string());
}
for p in &method.params {
collect_named(&p.ty, &mut named_refs);
}
collect_named(&method.return_type, &mut named_refs);
}
for name in &named_refs {
if name == type_name {
continue;
}
if mirror_type_names.contains(name) || opaque_type_names.contains(name) {
continue;
}
if let Some(path) = type_paths.get(name)
&& path.contains("::")
{
trait_uses.insert(path.clone());
}
}
for path in &trait_uses {
out.push_str(&format!("#[allow(unused_imports)]\nuse {path};\n"));
}
out.push_str(&format!("impl {type_name} {{\n"));
for method in &ty.methods {
let method_name = &method.name;
if stub_methods.contains(method_name) {
continue;
}
if method.sanitized {
let adapter_key = format!("{type_name}.{method_name}");
if let Some(adapter) = streaming_adapters.get(&adapter_key) {
emit_streaming_sink_method(out, method_name, adapter, types_needing_from_conversion, config);
} else {
out.push_str(&format!(
" // Method `{method_name}` has a sanitized return type that cannot be bridged through FRB — skipped.\n"
));
}
continue;
}
if method.is_static {
emit_static_opaque_method(
out,
ty,
method,
source_crate_name,
stub_methods,
types_needing_from_conversion,
opaque_type_names,
);
continue;
}
emit_opaque_method(
out,
ty,
method,
source_crate_name,
stub_methods,
types_needing_from_conversion,
opaque_type_names,
);
}
out.push_str("}\n");
}
fn emit_streaming_sink_method(
out: &mut String,
method_name: &str,
adapter: &AdapterConfig,
_types_needing_from_conversion: &HashSet<String>,
config: &ResolvedCrateConfig,
) {
let item_type = adapter.item_type.as_deref().unwrap_or("()");
let params: Vec<String> = adapter
.params
.iter()
.map(|p| {
let ty = if p.optional {
format!("Option<{}>", p.ty)
} else {
p.ty.clone()
};
format!("{}: {ty}", p.name)
})
.collect();
let params_str = if params.is_empty() {
String::new()
} else {
format!(", {}", params.join(", "))
};
let (body, _struct_def) =
crate::adapters::streaming::generate_body(adapter, crate::core::config::Language::Dart, config)
.unwrap_or_else(|_| {
(
String::from(
"compile_error!(\"alef cannot generate this Dart streaming adapter; configure a supported adapter body or exclude the method\")",
),
None,
)
});
out.push_str(&format!(
" #[frb]\n pub fn {method_name}(&self{params_str}, sink: crate::frb_generated::StreamSink<{item_type}>) {{\n {body}\n }}\n"
));
}
fn emit_opaque_method(
out: &mut String,
_ty: &TypeDef,
method: &MethodDef,
source_crate_name: &str,
_stub_methods: &[String],
types_needing_from_conversion: &HashSet<String>,
opaque_type_names: &HashSet<String>,
) {
use bridge_fn::frb_rust_type_mirror;
let method_name = &method.name;
let self_param = match &method.receiver {
Some(ReceiverKind::RefMut) => "&mut self",
Some(ReceiverKind::Owned) => "self",
_ => "&self",
};
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let rust_ty = frb_rust_type_mirror(&p.ty, p.optional);
format!("{}: {rust_ty}", p.name)
})
.collect();
let async_kw = if method.is_async { "async " } else { "" };
let has_error = method.error_type.is_some();
let ret_ty = if has_error {
let ok_ty = frb_rust_type_mirror(&method.return_type, false);
format!("Result<{ok_ty}, String>")
} else {
frb_rust_type_mirror(&method.return_type, false)
};
out.push_str(" #[frb]\n");
let params_str = params.join(", ");
out.push_str(&format!(
" pub {async_kw}fn {method_name}({self_param}, {params_str}) -> {ret_ty} {{\n"
));
emit_opaque_method_body(
out,
method,
source_crate_name,
types_needing_from_conversion,
opaque_type_names,
);
out.push_str(" }\n");
}
fn emit_static_opaque_method(
out: &mut String,
ty: &TypeDef,
method: &MethodDef,
source_crate_name: &str,
_stub_methods: &[String],
types_needing_from_conversion: &HashSet<String>,
opaque_type_names: &HashSet<String>,
) {
use bridge_fn::frb_rust_type_mirror;
let method_name = &method.name;
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let rust_ty = frb_rust_type_mirror(&p.ty, p.optional);
format!("{}: {rust_ty}", p.name)
})
.collect();
let async_kw = if method.is_async { "async " } else { "" };
let has_error = method.error_type.is_some();
let ret_ty = if has_error {
let ok_ty = frb_rust_type_mirror(&method.return_type, false);
format!("Result<{ok_ty}, String>")
} else {
frb_rust_type_mirror(&method.return_type, false)
};
out.push_str(" #[frb]\n");
let params_str = params.join(", ");
out.push_str(&format!(
" pub {async_kw}fn {method_name}({params_str}) -> {ret_ty} {{\n"
));
emit_static_opaque_method_body(
out,
ty,
method,
source_crate_name,
types_needing_from_conversion,
opaque_type_names,
);
out.push_str(" }\n");
}
fn emit_static_opaque_method_body(
out: &mut String,
ty: &TypeDef,
method: &MethodDef,
source_crate_name: &str,
types_needing_from_conversion: &HashSet<String>,
opaque_type_names: &HashSet<String>,
) {
use conversions::frb_rust_type_inner;
let method_name = &method.name;
let type_name = &ty.name;
let core_type_path = format!("{source_crate_name}::{type_name}");
let call_args: Vec<String> = method
.params
.iter()
.map(|p| {
let param_name = &p.name;
match &p.ty {
TypeRef::Named(mirror_name) => {
if opaque_type_names.contains(mirror_name.as_str()) {
if p.optional {
return format!("{param_name}.map(|h| h.inner)");
}
if p.is_ref {
return format!("&{param_name}.inner");
}
return format!("{param_name}.inner");
}
let core_ty = format!("{source_crate_name}::{mirror_name}");
if types_needing_from_conversion.contains(mirror_name.as_str()) {
if p.optional {
format!("{param_name}.map({core_ty}::from)")
} else if p.is_ref {
format!("&{core_ty}::from({param_name})")
} else {
format!("{core_ty}::from({param_name})")
}
} else {
if p.optional {
format!("{param_name}.map(|v| unsafe {{ ::std::mem::transmute::<{mirror_name}, {core_ty}>(v) }})")
} else if p.is_ref {
format!("unsafe {{ ::std::mem::transmute::<&{mirror_name}, &{core_ty}>(&{param_name}) }}")
} else {
format!("unsafe {{ ::std::mem::transmute::<{mirror_name}, {core_ty}>({param_name}) }}")
}
}
}
TypeRef::Vec(inner) => {
if let TypeRef::Named(mirror_name) = inner.as_ref() {
let core_ty = format!("{source_crate_name}::{mirror_name}");
if types_needing_from_conversion.contains(mirror_name.as_str()) {
if p.optional {
format!("{param_name}.map(|v| v.into_iter().map({core_ty}::from).collect())")
} else {
format!("{param_name}.into_iter().map({core_ty}::from).collect()")
}
} else {
if p.optional {
format!("{param_name}.map(|v| unsafe {{ ::std::mem::transmute::<Vec<{mirror_name}>, Vec<{core_ty}>>(v) }})")
} else {
format!("unsafe {{ ::std::mem::transmute::<Vec<{mirror_name}>, Vec<{core_ty}>>({param_name}) }}")
}
}
} else if matches!(inner.as_ref(), TypeRef::String) && p.is_ref && p.vec_inner_is_ref {
format!("&{param_name}.iter().map(|s| s.as_str()).collect::<Vec<_>>()")
} else if p.is_ref {
format!("&{param_name}")
} else {
param_name.clone()
}
}
TypeRef::Bytes => {
if p.is_ref {
format!("&{param_name}")
} else {
format!("::bytes::Bytes::from({param_name})")
}
}
TypeRef::Primitive(prim) => {
let native = conversions::primitive_name(prim);
let frb_ty = frb_rust_type_inner(&TypeRef::Primitive(prim.clone()));
if native == frb_ty {
param_name.clone()
} else if p.optional {
format!("{param_name}.map(|v| v as {native})")
} else {
format!("{param_name} as {native}")
}
}
TypeRef::String => {
if p.is_ref && p.optional {
format!("{param_name}.as_deref()")
} else if p.is_ref {
format!("&{param_name}")
} else {
param_name.clone()
}
}
_ => param_name.clone(),
}
})
.collect();
let call_args_str = call_args.join(", ");
let call = format!("{core_type_path}::{method_name}({call_args_str})");
let wrap_return = build_opaque_return_wrap(&method.return_type, method.returns_ref);
let has_error = method.error_type.is_some();
if method.is_async {
if has_error {
if wrap_return.is_empty() {
out.push_str(&format!(" {call}.await.map_err(|e| e.to_string())\n"));
} else {
out.push_str(&format!(
" {call}.await.map({wrap_return}).map_err(|e| e.to_string())\n"
));
}
} else if wrap_return.is_empty() {
out.push_str(&format!(" {call}.await\n"));
} else {
out.push_str(&format!(" ({wrap_return})({call}.await)\n"));
}
} else if has_error {
if wrap_return.is_empty() {
out.push_str(&format!(" {call}.map_err(|e| e.to_string())\n"));
} else {
out.push_str(&format!(
" {call}.map({wrap_return}).map_err(|e| e.to_string())\n"
));
}
} else if wrap_return.is_empty() {
out.push_str(&format!(" {call}\n"));
} else {
out.push_str(&format!(" ({wrap_return})({call})\n"));
}
}
fn emit_opaque_method_body(
out: &mut String,
method: &MethodDef,
source_crate_name: &str,
types_needing_from_conversion: &HashSet<String>,
opaque_type_names: &HashSet<String>,
) {
use conversions::frb_rust_type_inner;
let method_name = &method.name;
let has_error = method.error_type.is_some();
let call_args: Vec<String> = method
.params
.iter()
.map(|p| {
let param_name = &p.name;
match &p.ty {
TypeRef::Named(mirror_name) => {
if opaque_type_names.contains(mirror_name.as_str()) {
if p.optional {
return format!("{param_name}.map(|h| h.inner)");
}
if p.is_mut {
return format!("&mut {param_name}.inner");
}
if p.is_ref {
return format!("&{param_name}.inner");
}
return format!("{param_name}.inner");
}
let core_ty = format!("{source_crate_name}::{mirror_name}");
if types_needing_from_conversion.contains(mirror_name.as_str()) {
if p.optional {
format!("{param_name}.map({core_ty}::from)")
} else if p.is_mut {
format!("&mut {core_ty}::from({param_name})")
} else if p.is_ref {
format!("&{core_ty}::from({param_name})")
} else {
format!("{core_ty}::from({param_name})")
}
} else {
if p.optional {
format!("{param_name}.map(|v| unsafe {{ ::std::mem::transmute::<{mirror_name}, {core_ty}>(v) }})")
} else if p.is_mut {
format!("unsafe {{ ::std::mem::transmute::<&mut {mirror_name}, &mut {core_ty}>(&mut {param_name}) }}")
} else if p.is_ref {
format!("unsafe {{ ::std::mem::transmute::<&{mirror_name}, &{core_ty}>(&{param_name}) }}")
} else {
format!("unsafe {{ ::std::mem::transmute::<{mirror_name}, {core_ty}>({param_name}) }}")
}
}
}
TypeRef::Vec(inner) => {
if let TypeRef::Named(mirror_name) = inner.as_ref() {
let core_ty = format!("{source_crate_name}::{mirror_name}");
if types_needing_from_conversion.contains(mirror_name.as_str()) {
if p.optional {
format!("{param_name}.map(|v| v.into_iter().map({core_ty}::from).collect::<Vec<_>>())")
} else if p.is_ref {
format!("&{param_name}.iter().map(|x| {core_ty}::from(x.clone())).collect::<Vec<_>>()")
} else {
format!("{param_name}.into_iter().map({core_ty}::from).collect::<Vec<_>>()")
}
} else {
if p.optional {
format!("{param_name}.map(|v| unsafe {{ ::std::mem::transmute::<Vec<{mirror_name}>, Vec<{core_ty}>>(v) }})")
} else if p.is_mut {
format!(
"unsafe {{ ::std::slice::from_raw_parts_mut(\
::std::mem::transmute::<*mut {mirror_name}, *mut {core_ty}>({param_name}.as_mut_ptr()), \
{param_name}.len()) }}"
)
} else if p.is_ref {
format!(
"unsafe {{ ::std::slice::from_raw_parts(\
::std::mem::transmute::<*const {mirror_name}, *const {core_ty}>({param_name}.as_ptr()), \
{param_name}.len()) }}"
)
} else {
format!("unsafe {{ ::std::mem::transmute::<Vec<{mirror_name}>, Vec<{core_ty}>>({param_name}) }}")
}
}
} else if matches!(inner.as_ref(), TypeRef::String) && p.is_ref && p.vec_inner_is_ref {
format!("&{param_name}.iter().map(|s| s.as_str()).collect::<Vec<_>>()")
} else if p.is_ref {
format!("&{param_name}")
} else {
param_name.clone()
}
}
TypeRef::Bytes => {
if p.is_ref {
format!("&{param_name}")
} else {
format!("::bytes::Bytes::from({param_name})")
}
}
TypeRef::Primitive(prim) => {
let native = conversions::primitive_name(prim);
let frb_ty = frb_rust_type_inner(&TypeRef::Primitive(prim.clone()));
if native == frb_ty {
param_name.clone()
} else if p.optional {
format!("{param_name}.map(|v| v as {native})")
} else {
format!("{param_name} as {native}")
}
}
TypeRef::String => {
if p.is_ref && p.optional {
format!("{param_name}.as_deref()")
} else if p.is_ref {
format!("&{param_name}")
} else {
param_name.clone()
}
}
TypeRef::Json => {
if p.optional {
format!("{param_name}.as_deref().and_then(|s| serde_json::from_str(s).ok())")
} else {
format!("serde_json::from_str(&{param_name}).unwrap_or(serde_json::Value::Null)")
}
}
TypeRef::Path => {
if p.optional {
format!("{param_name}.map(::std::path::PathBuf::from)")
} else {
format!("::std::path::PathBuf::from({param_name})")
}
}
_ => param_name.clone(),
}
})
.collect();
let call = format!("self.inner.{method_name}({})", call_args.join(", "));
let wrap_return = build_opaque_return_wrap(&method.return_type, method.returns_ref);
if method.is_async {
if has_error {
if wrap_return.is_empty() {
out.push_str(&format!(" {call}.await.map_err(|e| e.to_string())\n"));
} else {
out.push_str(&format!(
" {call}.await.map({wrap_return}).map_err(|e| e.to_string())\n"
));
}
} else if wrap_return.is_empty() {
out.push_str(&format!(" {call}.await\n"));
} else {
out.push_str(&format!(" ({wrap_return})({call}.await)\n"));
}
} else if has_error {
if wrap_return.is_empty() {
out.push_str(&format!(" {call}.map_err(|e| e.to_string())\n"));
} else {
out.push_str(&format!(
" {call}.map({wrap_return}).map_err(|e| e.to_string())\n"
));
}
} else if wrap_return.is_empty() {
out.push_str(&format!(" {call}\n"));
} else {
out.push_str(&format!(" ({wrap_return})({call})\n"));
}
}
fn build_opaque_return_wrap(ty: &TypeRef, returns_ref: bool) -> String {
use crate::core::ir::PrimitiveType;
match ty {
TypeRef::Named(mirror_name) => {
format!("|v| {mirror_name}::from(v)")
}
TypeRef::Bytes => {
"|v| v.to_vec()".to_string()
}
TypeRef::String if returns_ref => {
"|v: &str| v.to_string()".to_string()
}
TypeRef::Path => {
if returns_ref {
"|v: &::std::path::Path| v.to_string_lossy().to_string()".to_string()
} else {
"|v: ::std::path::PathBuf| v.to_string_lossy().to_string()".to_string()
}
}
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(mirror_name) => {
format!("|v| v.into_iter().map({mirror_name}::from).collect()")
}
TypeRef::String if returns_ref => {
"|v: &[&str]| v.iter().map(|s| s.to_string()).collect()".to_string()
}
_ => String::new(),
},
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(mirror_name) => {
format!("|v: Option<_>| v.map({mirror_name}::from)")
}
TypeRef::String if returns_ref => "|v: Option<&str>| v.map(|s| s.to_string())".to_string(),
_ => String::new(),
},
TypeRef::Primitive(prim) => {
match prim {
PrimitiveType::I64 | PrimitiveType::F64 | PrimitiveType::Bool => String::new(),
PrimitiveType::F32 => "|v| v as f64".to_string(),
_ => "|v| v as i64".to_string(),
}
}
_ => String::new(),
}
}
fn emit_from_json_fn(out: &mut String, ty: &TypeDef, source_crate_name: &str) {
let type_name = &ty.name;
let snake = dart_rust_function_component(type_name);
let fn_name = format!("create_{snake}_from_json");
let core_ty_base = if ty.rust_path.is_empty() {
format!("{source_crate_name}::{type_name}")
} else {
ty.rust_path.replace('-', "_")
};
let core_ty = if ty.has_lifetime_params {
format!("{core_ty_base}<'static>")
} else {
core_ty_base
};
out.push_str("#[frb]\n");
out.push_str(&format!(
"pub fn {fn_name}(json: String) -> Result<{type_name}, String> {{\n"
));
out.push_str(&format!(" serde_json::from_str::<{core_ty}>(&json)\n"));
out.push_str(&format!(" .map({type_name}::from)\n"));
out.push_str(" .map_err(|e| e.to_string())\n");
out.push_str("}\n");
}
fn dart_rust_function_component(s: &str) -> String {
public_host_identifier(Language::Rust, PublicIdentifierKind::Function, s)
}