use crate::codegen::generators::type_paths::build_type_path_lookup;
use crate::core::backend::GeneratedFile;
use crate::core::config::{AdapterConfig, AdapterPattern, ResolvedCrateConfig, resolve_output_dir};
use crate::core::ir::{ApiSurface, EnumDef, TypeDef};
use std::collections::HashSet;
mod bridge_fn;
mod cargo;
mod conversions;
mod enum_conversions;
mod helpers;
mod mirror;
mod mirror_conversions;
#[cfg(test)]
mod mirror_conversions_tests;
mod opaque;
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 deduped_api = api.with_deduped_functions();
let api = &deduped_api;
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, unreachable_patterns)]\n");
content.push_str("#![allow(missing_docs)]\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::redundant_field_names,\n");
content.push_str(" clippy::useless_conversion,\n");
content.push_str(")]\n");
if let Some(extra_attr) = crate::codegen::shared::format_extra_clippy_allows(&config.extra_clippy_allows, &content)
{
content.push_str(&format!("#![{extra_attr}]\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;
}
let rendered_path = path.replace('-', "_");
content.push_str(&crate::backends::dart::template_env::render(
"rust_pub_use.rs.jinja",
minijinja::context! {
path => rendered_path.as_str(),
},
));
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 || opaque::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 || opaque::has_duration_or_path_field(&f.ty))
})
})
.map(|e| e.name.clone()),
)
.collect();
let types_needing_from_conversion: HashSet<String> =
mirror_conversions::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');
opaque::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(&crate::backends::dart::template_env::render(
"rust_client_constructor_impl.rs.jinja",
minijinja::context! {
type_name => ty.name.as_str(),
ctor_body => ctor_body.as_str(),
},
));
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');
mirror_conversions::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');
enum_conversions::emit_from_impl_for_enum(&mut content, en, source_crate_name);
}
let configurator_param_types: HashSet<String> = api
.services
.iter()
.flat_map(|s| s.configurators.iter())
.flat_map(|cfg| cfg.params.iter())
.flat_map(|p| mirror_conversions::collect_named_types_from_type_ref(&p.ty))
.filter(|name| !exclude_types.contains(name))
.collect();
let param_types_needing_from: HashSet<String> = api
.functions
.iter()
.filter(|f| !exclude_functions.contains(&f.name) && !opaque::has_unbridgeable_param(f))
.flat_map(|f| f.params.iter())
.flat_map(|p| mirror_conversions::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| mirror_conversions::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| mirror_conversions::collect_named_types_from_type_ref(&m.return_type)),
)
.collect();
let types_needing_from_impl =
mirror_conversions::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');
mirror_conversions::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');
enum_conversions::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');
mirror_conversions::emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
}
}
}
let configurator_param_closure =
mirror_conversions::compute_types_needing_from_impl(api, &configurator_param_types, exclude_types);
{
let configurator_param_mirror_types: Vec<&TypeDef> = api
.types
.iter()
.filter(|t| {
configurator_param_closure.contains(&t.name)
&& !types_needing_from_impl.contains(&t.name)
&& !t.is_trait
&& !t.is_opaque
&& !t.binding_excluded
})
.collect();
if !configurator_param_mirror_types.is_empty() {
content.push_str("\n// From<T> for SourceT conversions for service-configurator mirror parameter types.\n");
for ty in configurator_param_mirror_types {
content.push('\n');
mirror_conversions::emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
}
}
}
{
let configurator_param_mirror_enums: Vec<&EnumDef> = api
.enums
.iter()
.filter(|e| {
configurator_param_closure.contains(&e.name)
&& !types_needing_from_impl.contains(&e.name)
&& !e.binding_excluded
})
.collect();
if !configurator_param_mirror_enums.is_empty() {
for en in configurator_param_mirror_enums {
content.push('\n');
enum_conversions::emit_from_mirror_to_core_enum(&mut content, en, 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| !opaque::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');
opaque::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('-', "_")
}