flutter_rust_bridge_codegen 2.12.0

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple
Documentation
use crate::codegen::dumper::Dumper;
use crate::codegen::ir::hir::flat::pack::HirFlatPack;
use crate::codegen::ir::mir::pack::MirPack;
use crate::codegen::misc::GeneratorProgressBarPack;
use crate::codegen::parser::internal_config::ParserInternalConfig;
use crate::codegen::ConfigDumpContent;
use crate::codegen::ConfigDumpContent::Hir;
use ConfigDumpContent::Mir;

pub(crate) mod early_generator;
pub(crate) mod hir;
pub(crate) mod internal_config;
pub(crate) mod mir;

pub(crate) fn parse(
    config: &ParserInternalConfig,
    dumper: &Dumper,
    progress_bar_pack: &GeneratorProgressBarPack,
) -> anyhow::Result<MirPack> {
    parse_inner(config, dumper, progress_bar_pack, |_| Ok(()))
}

fn parse_inner(
    config: &ParserInternalConfig,
    dumper: &Dumper,
    progress_bar_pack: &GeneratorProgressBarPack,
    on_hir_flat: impl FnOnce(&HirFlatPack) -> anyhow::Result<()>,
) -> anyhow::Result<MirPack> {
    let dumper_hir = dumper.with_content(Hir);
    let dumper_hir_tree = dumper_hir.with_add_name_prefix("hir_tree/");
    let dumper_hir_naive_flat = dumper_hir.with_add_name_prefix("hir_naive_flat/");
    let dumper_hir_flat = dumper_hir.with_add_name_prefix("hir_flat/");
    let dumper_early_generator = dumper_hir.with_add_name_prefix("early_generator/");
    let dumper_mir = dumper.with_content(Mir);

    let pb = progress_bar_pack.parse_hir_raw.start();
    let hir_raw = hir::raw::parse(&config.hir, dumper)?;
    drop(pb);

    let pb = progress_bar_pack.parse_hir_primary.start();
    let hir_tree = hir::tree::parse(&config.hir, hir_raw, &dumper_hir_tree)?;
    let hir_naive_flat = hir::naive_flat::parse(&config.hir, hir_tree, &dumper_hir_naive_flat)?;
    let hir_flat = hir::flat::parse(&config.hir, hir_naive_flat, &dumper_hir_flat)?;
    on_hir_flat(&hir_flat)?;
    let ir_early_generator =
        early_generator::execute(hir_flat, &config.mir, &dumper_early_generator)?;
    drop(pb);

    let pb = progress_bar_pack.parse_mir.start();
    let mir_pack = mir::parse(
        &config.mir,
        &ir_early_generator,
        &dumper_mir,
        mir::ParseMode::Normal,
    )?;
    drop(pb);

    Ok(mir_pack)
}

#[cfg(test)]
mod tests {
    use crate::codegen::config::internal_config_parser::compute_force_codec_mode_pack;
    use crate::codegen::dumper::Dumper;
    use crate::codegen::generator::codec::structs::CodecMode;
    use crate::codegen::ir::mir::ty::rust_opaque::RustOpaqueCodecMode;
    use crate::codegen::misc::GeneratorProgressBarPack;
    use crate::codegen::parser::hir::internal_config::ParserHirInternalConfig;
    use crate::codegen::parser::internal_config::ParserInternalConfig;
    use crate::codegen::parser::mir::internal_config::{
        ParserMirInternalConfig, RustInputNamespacePack,
    };
    use crate::codegen::parser::{parse_inner, MirPack};
    use crate::utils::logs::configure_opinionated_test_logging;
    use crate::utils::namespace::Namespace;
    use crate::utils::test_utils::{
        create_path_sanitizers, get_test_fixture_dir, json_golden_test,
    };
    use log::info;
    use serial_test::serial;
    use std::path::{Path, PathBuf};

    #[test]
    #[serial]
    fn test_simple() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/simple", None)
    }

    #[test]
    #[serial]
    fn test_methods() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/methods", None)
    }

    #[test]
    #[serial]
    fn test_multi_input_file() -> anyhow::Result<()> {
        body(
            "library/codegen/parser/mod/multi_input_file",
            Some(Box::new(|_rust_crate_dir| RustInputNamespacePack {
                rust_input_namespace_prefixes: vec![
                    Namespace::new_self_crate("api_one".to_owned()),
                    Namespace::new_self_crate("api_two".to_owned()),
                ],
                rust_output_path_namespace: Namespace::new_self_crate("frb_generated".to_owned()),
            })),
        )
    }

    #[test]
    #[serial]
    fn test_use_type_in_another_file() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/use_type_in_another_file", None)
    }

    #[test]
    #[serial]
    fn test_qualified_names() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/qualified_names", None)
    }

    #[test]
    #[serial]
    fn test_non_qualified_names() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/non_qualified_names", None)
    }

    #[test]
    #[serial]
    fn test_generics() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/generics", None)
    }

    #[test]
    #[serial]
    fn test_unused_struct_enum() -> anyhow::Result<()> {
        body("library/codegen/parser/mod/unused_struct_enum", None)
    }

    #[allow(clippy::type_complexity)]
    fn body(
        fixture_name: &str,
        rust_input_namespace_pack: Option<Box<dyn Fn(&Path) -> RustInputNamespacePack>>,
    ) -> anyhow::Result<()> {
        let (actual_ir, rust_crate_dir) = execute_parse(fixture_name, rust_input_namespace_pack)?;
        json_golden_test(
            &serde_json::to_value(actual_ir)?,
            &rust_crate_dir.join("expect_mir.json"),
            &[],
        )?;

        Ok(())
    }

    #[allow(clippy::type_complexity)]
    fn execute_parse(
        fixture_name: &str,
        rust_input_namespace_pack: Option<Box<dyn Fn(&Path) -> RustInputNamespacePack>>,
    ) -> anyhow::Result<(MirPack, PathBuf)> {
        configure_opinionated_test_logging();
        let test_fixture_dir = get_test_fixture_dir(fixture_name);
        let rust_crate_dir = test_fixture_dir.clone();
        info!("test_fixture_dir={test_fixture_dir:?}");

        let rust_input_namespace_pack = rust_input_namespace_pack
            .map(|f| f(&rust_crate_dir))
            .unwrap_or(RustInputNamespacePack {
                rust_input_namespace_prefixes: vec![Namespace::new_self_crate("api".to_owned())],
                rust_output_path_namespace: Namespace::new_self_crate("frb_generated".to_owned()),
            });

        let config = ParserInternalConfig {
            hir: ParserHirInternalConfig {
                rust_input_namespace_pack: rust_input_namespace_pack.clone(),
                rust_crate_dir: rust_crate_dir.clone(),
                third_party_crate_names: vec![],
                rust_features: None,
                parse_const: false,
            },
            mir: ParserMirInternalConfig {
                rust_input_namespace_pack: rust_input_namespace_pack.clone(),
                force_codec_mode_pack: compute_force_codec_mode_pack(true),
                default_stream_sink_codec: CodecMode::Dco,
                default_rust_opaque_codec: RustOpaqueCodecMode::Nom,
                stop_on_error: true,
                enable_lifetime: false,
                type_64bit_int: false,
                default_dart_async: true,
            },
        };

        let pack = parse_inner(
            &config,
            &Dumper::new(&Default::default()),
            &GeneratorProgressBarPack::new(),
            |hir_flat| {
                json_golden_test(
                    &serde_json::to_value(hir_flat).unwrap(),
                    &rust_crate_dir.join("expect_hir_flat.json"),
                    &create_path_sanitizers(&test_fixture_dir),
                )
            },
        )?;

        Ok((pack, rust_crate_dir))
    }
}