alef 0.23.33

Opinionated polyglot binding generator for Rust libraries
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
// Generated by alef. Do not edit by hand — this file is the codegen for codegen.
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;
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};

/// Emit the Rust-side flutter_rust_bridge bridge crate for the given API surface.
///
/// Returns four files that together form the `packages/dart/rust/` crate:
/// - `packages/dart/rust/Cargo.toml`
/// - `packages/dart/rust/src/lib.rs`
/// - `packages/dart/rust/build.rs`
/// - `packages/dart/rust/flutter_rust_bridge.yaml`
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();
    // Inner crate-level attributes must appear before any items (including `mod frb_generated`).
    // We also declare `mod frb_generated` explicitly so FRB codegen doesn't prepend it
    // BEFORE the #![allow] attrs (which would make those attrs invalid per E0753).
    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");
    // `From<Box<str>> for String` and `From<String> for Box<str>` are needed when
    // core uses `HashMap<Box<str>, _>` and the mirror uses `HashMap<String, _>` (or
    // vice versa). When both sides are plain `String`, the emitted `(k.into(),
    // v.into())` collapses to identity — silenced here so the same codegen path
    // works for both wrapped and unwrapped string keys/values.
    content.push_str("    clippy::useless_conversion,\n");
    content.push_str(")]\n");
    // Declare frb_generated after the crate-level attrs so FRB doesn't inject it at line 1.
    content.push_str("mod frb_generated;\n");
    content.push_str("use flutter_rust_bridge::frb;\n");
    // DartFnFuture is re-exported so frb_generated.rs (which does `use crate::*`)
    // can reference it by bare name in the generated closure types.
    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);
    }

    // FRB strips module paths from `frb_generated.rs` when it serializes closure
    // signatures, leaving bare type names that resolve against `use crate::*`.
    // Re-export every excluded type referenced by trait-bridge method signatures so
    // those bare names resolve. Without this, excluded carrier types can show up
    // as E0425 in the generated bridge file.
    {
        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;
            }
            // Match the filter applied by `emit_trait_bridge`: skip methods inherited
            // from super-traits, AND skip methods whose return type references another
            // trait (these are emitted as `Option<&dyn Trait>` in the source IR and the
            // bridge cannot dispatch them — see `return_type_references_trait`).
            // Default-impl methods that DO participate in the bridge still need their
            // referenced types re-exported.
            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);
            }
        }
        // D2: pre-compute the set of opaque type names that will be emitted as
        // `#[frb(opaque)] pub struct {Name}` by `emit_mirror_struct`. A `pub use` re-export
        // of the same short name would cause E0255 "defined multiple times".
        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 {
            // D2: skip names that are already emitted as an opaque wrapper struct; a `pub use`
            // for the same short name would redefine the identifier and cause E0255.
            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());
            }
        }
    }

    // Compute the set of types that have DIRECT sanitized fields (or Duration/Path fields that
    // cause layout mismatches) BEFORE emitting impl blocks so opaque method bodies can use
    // `From` conversion instead of `transmute` for these types.
    // Also include types with Duration or Path fields: the FRB mirror maps Duration→i64 (8B vs 16B)
    // and Path→String, creating layout mismatches that make transmute unsound for these types.
    // Both structs and enums are checked — sanitized enum variant fields (e.g.
    // `Scroll { selector: Option<String> }` sanitized to `Scroll { selector: String }`)
    // change the enum's discriminant payload size and make Vec/single transmute UB.
    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| {
                            // FRB mirror enum variants emit `frb_rust_type_inner(field.ty)`
                            // which strips Option wrappers — `Option<String>` becomes bare
                            // `String` in the mirror. That makes the mirror enum's variant
                            // payload smaller than core's, so transmute is UB. Treat any
                            // variant field that's optional in core (or otherwise layout-
                            // shifting like Duration/Path) as evidence the enum needs
                            // `From<Mirror> for Core` instead of a transmute.
                            f.sanitized || f.optional || opaque::has_duration_or_path_field(&f.ty)
                        })
                    })
                })
                .map(|e| e.name.clone()),
        )
        .collect();

    // Compute the transitive closure: types that DIRECTLY or INDIRECTLY contain a type
    // with sanitized fields. These cannot be safely transmuted because the inner types
    // have different memory layouts (e.g. a batch item contains a config DTO
    // which has Option<String> where core has Option<HtmlOutputConfig>).
    // Bridge functions use `From<MirrorT> for SourceT` for all of these.
    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);
    }

    // Build a lookup from "OwnerType.method_name" → AdapterConfig for streaming adapters.
    // This lets emit_opaque_impl_block emit StreamSink<T> methods instead of skipping them.
    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();

    // Pre-build the type-path lookup so impl-block emission can resolve Named types
    // referenced in method signatures and emit the corresponding `use` statements.
    let type_paths_for_impls = build_type_path_lookup_for_source(api, source_crate_name);

    // Build the set of mirror type names: non-opaque, non-trait types AND enums that
    // receive a `#[frb(mirror(TypeName))]` declaration above. These types are already
    // in scope under their short name; emitting a `use source_crate::TypeName;` for
    // them inside impl blocks would cause E0255 "defined multiple times" errors. The
    // enum branch also avoids the orphan-rule trap where `use source_crate::Method;` at
    // module scope shadows the local mirror enum, causing `impl From<source_crate::Method>
    // for Method` to be interpreted as an impl for the source crate's `Method` (both
    // foreign → E0117).
    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();

    // Collect opaque type names (is_opaque = true, not traits) — these use a wrapper struct
    // in the generated bridge crate and must be accessed via .inner, not transmuted.
    // Computed here (before emit_opaque_impl_block) so the impl bodies can use it for D3.
    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();

    // Emit impl blocks for opaque types that expose methods.
    // FRB generates Dart-side methods on opaque handles only when the bridge crate
    // contains `impl TypeName { #[frb] pub fn method(...) }` blocks. Without these
    // blocks FRB emits an empty `abstract class TypeName implements RustOpaqueInterface {}`
    // with no methods, causing method-not-found errors in Dart callers.
    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,
        );
    }

    // Client constructors for opaque handles (emit a separate impl block with #[frb])
    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);
    }

    // Emit mirror enums for error types so flutter_rust_bridge generates a Dart sealed
    // class for each error. The `impl` block with `#[frb]` methods surfaces introspection
    // methods (e.g. `status_code`, `is_transient`, `error_type`) as Dart instance methods.
    for error in api.errors.iter().filter(|e| !e.binding_excluded) {
        content.push('\n');
        emit_mirror_error(&mut content, error, source_crate_name);
    }

    // Emit From<SourceT> for T conversions for all struct and enum types.
    // These are required because the bridge functions use local mirror types but the core
    // functions return source-crate types that may differ in layout (e.g. Cow vs String).
    // transmute cannot be used when sizes differ, so explicit field-by-field From impls
    // are generated instead.
    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);
    }

    // Collect service configurator-param types that need From<Mirror> for Source impls.
    // These types don't necessarily have sanitized fields but are passed as mirror types
    // from Dart to the core service and need explicit From conversions.
    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();

    // Collect only the types transitively containing sanitized fields that appear as
    // function input parameters. Output-only types (result structs) are excluded —
    // they never flow Dart→Rust and must not get From<Mirror> for Core impls.
    // Also include method parameters from opaque types (e.g. DefaultClient::chat takes
    // ChatCompletionRequest) — these methods also pass mirror types to core and need
    // From<Mirror> for Core impls to convert safely without transmute.
    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(
            // Method parameters from opaque types that are not stub methods.
            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))
        // Trait-bridge method return types: when a trait is bridged (e.g. HtmlVisitor),
        // each Dart callback returns a mirror value, and the bridge impl converts it back
        // to the core type via `.into()`. Include those return types so a
        // `From<Mirror> for Core` impl is emitted. Chained AFTER the
        // `types_needing_from_conversion` filter because these return types are not
        // necessarily reachable via the sanitized-field transitive closure (e.g.
        // `VisitResult` is a simple enum with no sanitized fields, but the trait bridge
        // still needs a `From<Mirror> for Core` impl for it).
        .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();

    // Compute the transitive closure of all struct/enum types reachable from function-
    // parameter types that need From conversion, via non-sanitized field references.
    let types_needing_from_impl =
        mirror_conversions::compute_types_needing_from_impl(api, &param_types_needing_from, exclude_types);

    // Emit From<T> for SourceT impls (mirror-to-core direction) for types in the
    // transitive closure. Only those types (not all types) get this impl, avoiding
    // Default::default() issues for output-only types that don't implement Default.
    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");
    // Filter out trait and opaque types: trait types are not constructible with `{}` literals,
    // and opaque types are handled separately by their own bridge impl block. Both can land in
    // `types_needing_from_impl` via trait-bridge return types (e.g. `Option<&dyn SyncExtractor>`
    // contributes `SyncExtractor` to the seed via `collect_named_types_from_type_ref`).
    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);
    }
    // Emit From<MirrorEnum> for SourceEnum so that enum-typed struct fields
    // can use `.into()` in the mirror-to-core From impls above.
    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);
    }

    // Streaming adapter method bodies use `let core_req: crate::T = req.into()` which
    // requires `From<LocalMirrorT> for crate::T`. Simple request types (e.g.
    // `CrawlStreamRequest { url: String }`) have no sanitized fields, so they never enter
    // `types_needing_from_impl` via the sanitized-field transitive closure above. Emit the
    // mirror-to-core `From` impl for any streaming adapter param type that is a declared
    // mirror type and has not already been covered above.
    {
        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);
            }
        }
    }

    // Service configurator parameters are passed as mirror types from Dart to the core
    // service and need From<Mirror> for Source impls, even if they don't have sanitized
    // fields. Compute the transitive closure of configurator-param types and emit From
    // impls for those that haven't already been covered above.
    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);
            }
        }
    }
    // Also emit From<MirrorEnum> for enums in the configurator-param closure
    {
        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);
    // opaque_type_names already computed above (before emit_opaque_impl_block).

    for f in api
        .functions
        .iter()
        .filter(|f| !exclude_functions.contains(&f.name))
        .filter(|f| !opaque::has_unbridgeable_param(f))
        // Skip functions whose name matches a trait_bridge.clear_fn — the trait-bridge
        // emission path emits its own forwarder; a duplicate `pub fn clear_*` here
        // would either fail to compile or be silently de-duped by frb_codegen
        // (which logs noisy warnings on every regen).
        .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,
        );
    }

    // Emit `create_<snake_name>_from_json` free functions for all non-opaque, non-trait
    // mirror struct types. These allow e2e tests (and other callers) to construct typed
    // request objects from a JSON string without manually filling every field — important
    // for FRB's named-parameter calling convention where passing a plain JSON map is not
    // possible. Each function deserializes via `serde_json::from_str` into the core type,
    // then converts to the local mirror type using the already-emitted `From<CoreT> for T`
    // impl. FRB generates the corresponding `BridgeClass.createTypeNameFromJson(json)` Dart
    // helper automatically from the `#[frb]`-annotated Rust function.
    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)
        // Only types that derive serde::Deserialize on the core side can be deserialized
        // from a JSON string. Internal types like `MergedChunk`, `ResolvedStyle`,
        // `CharShape`, etc. exist in the binding surface as From-converted mirrors but
        // aren't serde-Deserialize on the core side — emitting `serde_json::from_str::<T>`
        // for them produces an E0277 trait-bound error at compile time.
        .filter(|t| t.has_serde)
    {
        content.push('\n');
        opaque::emit_from_json_fn(&mut content, ty, source_crate_name);
    }

    // Emit FRB trait bridge wrappers for each configured [[trait_bridges]] entry.
    // The Dart-side wiring (implementing the abstract class in Dart) is post-FRB-codegen-runtime
    // work — only the Rust side is generated here. The generated Rust code uses
    // DartFnFuture<T> callbacks from flutter_rust_bridge for async-to-sync bridging.
    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,
            );
        }
    }

    // Include service-API module if services are present.
    // The service_api.rs file is generated separately and contains FRB opaque owners and handler bridges.
    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('-', "_")
}