alef 0.104.4

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, 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,
    }
}

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| {
            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| {
                    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")
                            },
                        ),
                    }
                })
                .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,
            }
        })
        .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,
        },
    )
}