alef 0.107.9

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::codegen::error_gen::{LastErrorField, LastErrorFieldKind, last_error_field_kind, last_error_fields};
use crate::core::ir::{ApiSurface, ErrorDef, ErrorVariant, MethodDef, PrimitiveType, TypeRef};

#[derive(serde::Serialize)]
struct FfiErrorVariantCode {
    pattern: String,
    name: String,
    code_expression: String,
}

#[derive(serde::Serialize)]
struct FfiErrorFieldCapture {
    upper: String,
    expression: String,
}

#[derive(serde::Serialize)]
struct FfiErrorCodeImpl {
    error_path: String,
    variants: Vec<FfiErrorVariantCode>,
    fields: Vec<FfiErrorFieldCapture>,
}

#[derive(serde::Serialize)]
struct FfiLastErrorField {
    name: String,
    upper: String,
    accessor: String,
    kind: &'static str,
    storage_type: String,
    c_type: String,
    reset_expression: String,
    fallback_expression: String,
}

fn ffi_last_error_field(prefix: &str, field: &LastErrorField) -> FfiLastErrorField {
    let accessor = crate::codegen::c_consumer::last_error_field_symbol(prefix, &field.name);
    let (kind, storage_type, c_type, reset_expression, fallback_expression) = match &field.kind {
        LastErrorFieldKind::Scalar(primitive) => {
            let ty = primitive.rust_source_display();
            let zero = match primitive {
                PrimitiveType::Bool => "false",
                PrimitiveType::F32 | PrimitiveType::F64 => "0.0",
                _ => "0",
            };
            ("scalar", ty, ty, zero, zero)
        }
        LastErrorFieldKind::Text => ("text", "Option<CString>", "*const c_char", "None", "std::ptr::null()"),
        LastErrorFieldKind::DurationMillis => ("duration", "i64", "i64", "-1", "-1"),
    };
    FfiLastErrorField {
        name: field.name.clone(),
        upper: field.name.to_uppercase(),
        accessor,
        kind,
        storage_type: storage_type.to_string(),
        c_type: c_type.to_string(),
        reset_expression: reset_expression.to_string(),
        fallback_expression: fallback_expression.to_string(),
    }
}

fn ffi_error_field_capture(method: &MethodDef, kind: &LastErrorFieldKind) -> FfiErrorFieldCapture {
    let call = format!("error.{}()", method.name);
    let expression = match kind {
        LastErrorFieldKind::Scalar(_) => call,
        LastErrorFieldKind::Text => format!("CString::new({call}.to_string()).ok()"),
        LastErrorFieldKind::DurationMillis if matches!(method.return_type, TypeRef::Optional(_)) => {
            format!("{call}.map_or(-1, alef_ffi_duration_millis)")
        }
        LastErrorFieldKind::DurationMillis => format!("alef_ffi_duration_millis({call})"),
    };
    FfiErrorFieldCapture {
        upper: method.name.to_uppercase(),
        expression,
    }
}

fn ffi_error_variant_code(error: &ErrorDef, error_path: &str, variant: &ErrorVariant) -> FfiErrorVariantCode {
    let suffix = if variant.is_unit {
        String::new()
    } else if variant.is_tuple {
        "(..)".to_string()
    } else {
        " { .. }".to_string()
    };
    FfiErrorVariantCode {
        pattern: format!("{error_path}::{}{suffix}", variant.name),
        name: variant.name.clone(),
        code_expression: variant.error_code.map_or_else(
            || "ALEF_FFI_UNKNOWN_ERROR".to_string(),
            |_| {
                let variant_name = crate::codegen::naming::ffi_error_code_variant_name(&error.rust_path, &variant.name);
                format!("AlefFfiErrorCode::{variant_name} as i32")
            },
        ),
    }
}

fn ffi_error_code_impl(error: &ErrorDef, core_import: &str, last_error_fields: &[LastErrorField]) -> FfiErrorCodeImpl {
    let error_path = if error.rust_path.contains("::") {
        error.rust_path.replace('-', "_")
    } else {
        format!("{core_import}::{}", error.name)
    };
    let variants = error
        .variants
        .iter()
        .map(|variant| ffi_error_variant_code(error, &error_path, variant))
        .collect();
    let fields = error
        .methods
        .iter()
        .filter_map(|method| {
            let kind = last_error_field_kind(method)?;
            let declared = last_error_fields.iter().find(|field| field.name == method.name)?;
            (declared.kind == kind).then(|| ffi_error_field_capture(method, &kind))
        })
        .collect();
    FfiErrorCodeImpl {
        error_path,
        variants,
        fields,
    }
}

pub(super) fn gen_last_error(api: &ApiSurface, prefix: &str, core_import: &str) -> String {
    let taxonomy = api.error_taxonomy();
    let last_error_fields = last_error_fields(&api.errors);
    let error_code_impls: Vec<_> = api
        .errors
        .iter()
        .map(|error| ffi_error_code_impl(error, core_import, &last_error_fields))
        .collect();
    let has_error_code_impls = !error_code_impls.is_empty();
    let has_duration_fields = last_error_fields
        .iter()
        .any(|field| field.kind == LastErrorFieldKind::DurationMillis);
    let error_fields: Vec<FfiLastErrorField> = last_error_fields
        .iter()
        .map(|field| ffi_last_error_field(prefix, field))
        .collect();
    crate::backends::ffi::template_env::render(
        "last_error.jinja",
        minijinja::context! {
            prefix => prefix,
            builtin_prefix => crate::codegen::naming::ffi_builtin_error_code_prefix(prefix),
            error_code_impls => error_code_impls,
            has_error_code_impls => has_error_code_impls,
            error_fields => error_fields,
            has_duration_fields => has_duration_fields,
            taxonomy => taxonomy.iter().map(|entry| minijinja::context! {
                code => entry.code,
                enum_variant => crate::codegen::naming::ffi_error_code_variant_name(&entry.error_type, &entry.variant),
            }).collect::<Vec<_>>(),
            no_error_code => ApiSurface::FFI_ERROR_CODE_NONE,
            conversion_error_code => ApiSurface::FFI_ERROR_CODE_CONVERSION,
            unknown_error_code => ApiSurface::FFI_ERROR_CODE_UNKNOWN,
            panic_error_code => ApiSurface::FFI_ERROR_CODE_PANIC,
            invalid_handle_error_code => ApiSurface::FFI_ERROR_CODE_INVALID_HANDLE,
            cancelled_error_code => ApiSurface::FFI_ERROR_CODE_CANCELLED,
        },
    )
}