alef 0.79.5

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::core::ir::{ParamDef, TypeRef};
use ahash::{AHashMap, AHashSet};

use super::orchestration::gen_function_wrapper_footer;
use super::params::{ParamConversionContext, gen_param_conversion_with_enums};
use super::return_handling::return_type_needs_non_serde_named;

#[test]
fn return_type_needs_non_serde_named_vec_non_serde() {
    let mut serde_names: AHashSet<String> = AHashSet::new();
    serde_names.insert("ExtractionResult".to_string());

    let vec_non_serde = TypeRef::Vec(Box::new(TypeRef::Named("PatternMatch".to_string())));
    assert!(
        return_type_needs_non_serde_named(&vec_non_serde, &serde_names),
        "Vec<PatternMatch> without Serialize must be detected as needing stub"
    );
}

#[test]
fn return_type_needs_non_serde_named_vec_serde_ok() {
    let mut serde_names: AHashSet<String> = AHashSet::new();
    serde_names.insert("ExtractionResult".to_string());

    let vec_serde = TypeRef::Vec(Box::new(TypeRef::Named("ExtractionResult".to_string())));
    assert!(
        !return_type_needs_non_serde_named(&vec_serde, &serde_names),
        "Vec<ExtractionResult> with Serialize must NOT be detected as needing stub"
    );
}

#[test]
fn return_type_needs_non_serde_named_primitive_vec_not_affected() {
    let serde_names: AHashSet<String> = AHashSet::new();
    assert!(!return_type_needs_non_serde_named(
        &TypeRef::Vec(Box::new(TypeRef::String)),
        &serde_names
    ));
    assert!(!return_type_needs_non_serde_named(
        &TypeRef::Vec(Box::new(TypeRef::Primitive(crate::core::ir::PrimitiveType::U64))),
        &serde_names
    ));
}

#[test]
fn named_param_is_mut_call_site_passes_local_directly() {
    let p = ParamDef {
        name: "result".to_string(),
        ty: TypeRef::Named("ExtractionResult".to_string()),
        optional: false,
        default: None,
        sanitized: false,
        typed_default: None,
        is_ref: false,
        is_mut: true,
        newtype_wrapper: None,
        original_type: None,
        map_is_ahash: false,
        map_key_is_cow: false,
        vec_inner_is_ref: false,
        map_is_btree: false,
        core_wrapper: crate::core::ir::CoreWrapper::None,
    };
    let rs = format!("{}_rs", p.name);
    let result = if p.is_mut {
        rs.clone()
    } else if p.is_ref {
        format!("&{rs}")
    } else {
        rs.clone()
    };
    assert_eq!(
        result, "result_rs",
        "is_mut Named param must pass local directly (already &mut T)"
    );
}

#[test]
fn enum_param_local_name_uses_param_name_not_type_name() {
    let mut enum_names: AHashSet<String> = AHashSet::new();
    enum_names.insert("RedactionStrategy".to_string());

    let p = ParamDef {
        name: "strategy".to_string(),
        ty: TypeRef::Named("RedactionStrategy".to_string()),
        optional: false,
        default: None,
        sanitized: false,
        typed_default: None,
        is_ref: false,
        is_mut: false,
        newtype_wrapper: None,
        original_type: None,
        map_is_ahash: false,
        map_key_is_cow: false,
        vec_inner_is_ref: false,
        map_is_btree: false,
        core_wrapper: crate::core::ir::CoreWrapper::None,
    };

    let output = gen_param_conversion_with_enums(
        &p,
        &ParamConversionContext {
            has_error: false,
            is_bytes_result: false,
            return_type: &TypeRef::Unit,
            ffi_return_type: None,
            core_import: "sample_crate",
            path_map: &AHashMap::new(),
            enum_names: &enum_names,
        },
    );

    assert!(
        output.contains("let strategy_rs ="),
        "enum local must be named after param (strategy_rs), got:\n{output}"
    );
    assert!(
        output.contains("redaction_strategy_from_i32_rs(strategy)"),
        "enum helper must receive the FFI param name (strategy), got:\n{output}"
    );
}

#[test]
fn panic_footer_uses_existing_failure_sentinel() {
    let pointer_footer = gen_function_wrapper_footer(
        &Some("*mut std::ffi::c_char".to_string()),
        &TypeRef::String,
        false,
        false,
    );
    assert!(pointer_footer.contains("AssertUnwindSafe(set_panic_error)"));
    assert!(pointer_footer.contains("std::ptr::null_mut()"));

    let const_pointer_footer = gen_function_wrapper_footer(
        &Some("*const sample_runtime::RawValue".to_string()),
        &TypeRef::Named("Value".to_string()),
        false,
        false,
    );
    assert!(const_pointer_footer.contains("std::ptr::null()"));
    assert!(!const_pointer_footer.contains("std::ptr::null_mut()"));

    let status_footer = gen_function_wrapper_footer(&Some("i32".to_string()), &TypeRef::Unit, true, false);
    assert!(status_footer.contains("-1"));
}

/// `trivial_call` skips the footer's leading `})) {` — the header already closed
/// `AssertUnwindSafe(inline_callee))` and opened the `match` arms itself when the closure was
/// replaced by a bare callee path (see `can_inline_trivially` in `orchestration.rs`).
#[test]
fn panic_footer_skips_closure_close_for_trivial_call() {
    let footer = gen_function_wrapper_footer(&None, &TypeRef::Unit, false, true);
    assert!(
        !footer.contains("})) {"),
        "trivial_call footer must not re-close a closure the header never opened:\n{footer}"
    );
    assert!(
        footer.trim_start().starts_with("Ok(value) => value,"),
        "trivial_call footer must start directly at the match arms:\n{footer}"
    );
}

#[test]
fn scalar_handle_override_controls_parameter_failure_sentinel() {
    let parameter = ParamDef {
        name: "source".to_string(),
        ty: TypeRef::String,
        ..ParamDef::default()
    };
    let output = gen_param_conversion_with_enums(
        &parameter,
        &ParamConversionContext {
            has_error: false,
            is_bytes_result: false,
            return_type: &TypeRef::String,
            ffi_return_type: Some("AlefHandle"),
            core_import: "sample_lib",
            path_map: &AHashMap::new(),
            enum_names: &AHashSet::new(),
        },
    );

    assert!(output.contains("return 0;"), "{output}");
    assert!(!output.contains("return std::ptr::null_mut();"), "{output}");
}