alef 0.23.37

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::core::config::TraitBridgeConfig;
use crate::core::ir::{ApiSurface, EnumDef, TypeRef};

#[derive(Debug, Clone)]
pub(crate) struct VisitorResultVariant {
    pub name: String,
    pub wire_name: String,
    pub code: usize,
}

#[derive(Debug, Clone)]
pub(crate) struct VisitorResultMetadata {
    pub default_variant: VisitorResultVariant,
    pub unit_variants: Vec<VisitorResultVariant>,
    pub string_payload_variants: Vec<VisitorResultVariant>,
}

pub(crate) fn visitor_result_metadata(
    api: &ApiSurface,
    bridge_cfg: &TraitBridgeConfig,
) -> Option<VisitorResultMetadata> {
    let result_type = bridge_cfg.result_type.as_deref()?;
    let enum_def = api.enums.iter().find(|enum_def| enum_def.name == result_type)?;
    visitor_result_metadata_from_enum_checked(enum_def, &bridge_cfg.trait_name).ok()
}

pub(crate) fn required_visitor_result_metadata(
    api: &ApiSurface,
    bridge_cfg: &TraitBridgeConfig,
) -> anyhow::Result<VisitorResultMetadata> {
    let result_type = bridge_cfg.result_type.as_deref().ok_or_else(|| {
        anyhow::anyhow!(
            "trait bridge `{}` must configure result_type for visitor result conversion",
            bridge_cfg.trait_name
        )
    })?;
    let enum_def = api.enums.iter().find(|enum_def| enum_def.name == result_type).ok_or_else(|| {
        anyhow::anyhow!(
            "trait bridge `{}` configures result_type `{result_type}`, but no matching enum exists in the API surface",
            bridge_cfg.trait_name
        )
    })?;
    visitor_result_metadata_from_enum_checked(enum_def, &bridge_cfg.trait_name)
}

pub(crate) fn visitor_result_metadata_from_enum_checked(
    enum_def: &EnumDef,
    trait_name: &str,
) -> anyhow::Result<VisitorResultMetadata> {
    let unit_variants = enum_def
        .variants
        .iter()
        .enumerate()
        .filter(|(_, variant)| variant.fields.is_empty() && !variant.originally_had_data_fields)
        .map(|(code, variant)| VisitorResultVariant {
            name: variant.name.clone(),
            wire_name: crate::codegen::naming::wire_variant_value(
                &variant.name,
                variant.serde_rename.as_deref(),
                enum_def.serde_rename_all.as_deref(),
            ),
            code,
        })
        .collect::<Vec<_>>();

    let default_unit_variants = enum_def
        .variants
        .iter()
        .filter(|variant| variant.is_default && variant.fields.is_empty() && !variant.originally_had_data_fields)
        .collect::<Vec<_>>();

    let default_variant = match default_unit_variants.as_slice() {
        [variant] => VisitorResultVariant {
            name: variant.name.clone(),
            wire_name: crate::codegen::naming::wire_variant_value(
                &variant.name,
                variant.serde_rename.as_deref(),
                enum_def.serde_rename_all.as_deref(),
            ),
            code: enum_def
                .variants
                .iter()
                .position(|candidate| candidate.name == variant.name)
                .unwrap_or_default(),
        },
        [] if unit_variants.len() == 1 => unit_variants[0].clone(),
        [] => anyhow::bail!(
            "trait bridge `{trait_name}` result_type `{}` must have exactly one #[default] unit variant, \
             or exactly one unit variant, to derive the default callback result",
            enum_def.name
        ),
        _ => anyhow::bail!(
            "trait bridge `{trait_name}` result_type `{}` has multiple #[default] unit variants; expected exactly one",
            enum_def.name
        ),
    };

    let string_payload_variants = enum_def
        .variants
        .iter()
        .enumerate()
        .filter(|(_, variant)| variant.fields.len() == 1 && matches!(variant.fields[0].ty, TypeRef::String))
        .map(|(code, variant)| VisitorResultVariant {
            name: variant.name.clone(),
            wire_name: crate::codegen::naming::wire_variant_value(
                &variant.name,
                variant.serde_rename.as_deref(),
                enum_def.serde_rename_all.as_deref(),
            ),
            code,
        })
        .collect();

    Ok(VisitorResultMetadata {
        default_variant,
        unit_variants,
        string_payload_variants,
    })
}

pub(crate) fn default_result_expr(return_type: &str, metadata: &VisitorResultMetadata) -> String {
    format!("{return_type}::{}", metadata.default_variant.name)
}

pub(crate) fn unknown_string_result_expr(
    return_type: &str,
    metadata: &VisitorResultMetadata,
    value_expr: &str,
) -> String {
    match metadata.string_payload_variants.as_slice() {
        [variant] => format!("{return_type}::{}({value_expr})", variant.name),
        _ => default_result_expr(return_type, metadata),
    }
}

pub(crate) fn variant_contexts(variants: &[VisitorResultVariant]) -> Vec<minijinja::Value> {
    variants
        .iter()
        .map(|variant| {
            minijinja::context! {
                name => variant.name.clone(),
                wire_name => variant.wire_name.clone(),
                code => variant.code,
            }
        })
        .collect()
}