alef 0.72.0

Opinionated polyglot binding generator for Rust libraries
Documentation
use alef::backends::swift::SwiftBackend;
use alef::core::backend::Backend;
use alef::core::config::new_config::NewAlefConfig;
use alef::core::ir::{ApiSurface, FunctionDef, TypeRef};

const REPEATED_GENERATION_COUNT: usize = 32;

fn make_config() -> alef::core::config::ResolvedCrateConfig {
    let config: NewAlefConfig = toml::from_str(
        r#"
[workspace]
languages = ["swift"]

[[crates]]
name = "sample-core"
sources = ["src/lib.rs"]
"#,
    )
    .expect("test config must parse");
    config.resolve().expect("test config must resolve").remove(0)
}

fn async_vec_function(name: &str, element_name: &str) -> FunctionDef {
    FunctionDef {
        name: name.to_string(),
        rust_path: format!("sample_core::{name}"),
        params: Vec::new(),
        return_type: TypeRef::Vec(Box::new(TypeRef::Named(element_name.to_string()))),
        is_async: true,
        doc: String::new(),
        error_type: None,
        binding_excluded: false,
        binding_exclusion_reason: None,
        cfg: None,
        sanitized: false,
        returns_ref: false,
        returns_cow: false,
        return_newtype_wrapper: None,
        original_rust_path: String::new(),
        return_sanitized: false,
        version: Default::default(),
    }
}

fn generated_swift(api: &ApiSurface) -> String {
    SwiftBackend
        .generate_bindings(api, &make_config())
        .expect("Swift generation must succeed")
        .into_iter()
        .find(|file| {
            file.content
                .contains("extension RustBridge.FirstRecord: @unchecked Sendable")
        })
        .expect("Swift bindings with async return Sendable extensions must be generated")
        .content
}

#[test]
fn async_return_sendable_extensions_are_stable_across_repeated_generation() {
    let api = ApiSurface {
        crate_name: "sample_core".to_string(),
        version: "0.1.0".to_string(),
        functions: vec![
            async_vec_function("load_second_records", "SecondRecord"),
            async_vec_function("load_first_records", "FirstRecord"),
        ],
        ..ApiSurface::default()
    };

    let expected = generated_swift(&api);
    let first_position = expected
        .find("extension RustBridge.FirstRecord: @unchecked Sendable")
        .expect("FirstRecord Sendable extension must be generated");
    let second_position = expected
        .find("extension RustBridge.SecondRecord: @unchecked Sendable")
        .expect("SecondRecord Sendable extension must be generated");
    assert!(
        first_position < second_position,
        "Sendable extensions must use canonical name order"
    );

    for _ in 1..REPEATED_GENERATION_COUNT {
        assert_eq!(generated_swift(&api), expected);
    }
}