alef 0.103.2

Opinionated polyglot binding generator for Rust libraries
Documentation
use alef::backends::pyo3::Pyo3Backend;
use alef::core::backend::Backend;
use alef::core::config::NewAlefConfig;
use alef::core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};

fn generated(has_default: bool, method: MethodDef) -> String {
    let config: NewAlefConfig = toml::from_str(
        r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
[crates.python]
module_name = "_test_lib"
"#,
    )
    .unwrap();
    let config = config.resolve().unwrap().remove(0);
    let typ = TypeDef {
        name: "Handle".into(),
        rust_path: "test_lib::Handle".into(),
        is_opaque: true,
        is_clone: true,
        has_default,
        methods: vec![method],
        ..Default::default()
    };
    let api = ApiSurface {
        crate_name: "test_lib".into(),
        version: "0.1.0".into(),
        types: vec![typ],
        ..Default::default()
    };
    let files = Pyo3Backend.generate_bindings(&api, &config).unwrap();
    files
        .into_iter()
        .find(|file| file.path.ends_with("lib.rs"))
        .unwrap()
        .content
}

fn constructor() -> MethodDef {
    MethodDef {
        name: "new".into(),
        is_static: true,
        return_type: TypeRef::Named("Handle".into()),
        ..Default::default()
    }
}

#[test]
fn explicit_constructor_preserves_no_default_contract() {
    for has_default in [false, true] {
        let content = generated(has_default, constructor());
        assert_eq!(content.contains("impl Default for Handle"), has_default);
        assert_eq!(content.contains("#[allow(clippy::new_without_default)]"), !has_default);
        assert!(!content.contains("#![allow(clippy::new_without_default)]"));
        let syntax = syn::parse_file(&content).unwrap();
        let wrapper = syntax
            .items
            .iter()
            .find_map(|item| match item {
                syn::Item::Impl(item)
                    if item.trait_.is_none() && item.attrs.iter().any(|a| a.path().is_ident("pymethods")) =>
                {
                    Some(item)
                }
                _ => None,
            })
            .unwrap();
        assert_eq!(wrapper.attrs.iter().any(|a| a.path().is_ident("allow")), !has_default);
    }
}

#[test]
fn unrelated_factories_do_not_receive_the_allowance() {
    let mut renamed = constructor();
    renamed.name = "create".into();
    let mut unit = constructor();
    unit.return_type = TypeRef::Unit;
    let mut fallible = constructor();
    fallible.error_type = Some("String".into());
    for method in [renamed, unit, fallible] {
        let content = generated(false, method);
        assert!(!content.contains("clippy::new_without_default"));
        assert!(!content.contains("impl Default for Handle"));
    }
}