alef 0.115.10

Opinionated polyglot binding generator for Rust libraries
Documentation
use super::type_bridge::{bridge_type, needs_json_bridge};
use crate::backends::swift::naming::{constructor_history_symbol, swift_rust_shim_ident};
use crate::core::ir::{FieldDef, TypeDef};
use heck::ToSnakeCase;

pub(super) struct ConstructorSignature<'a> {
    pub rust_name: String,
    pub fields: Vec<&'a FieldDef>,
    is_initializer: bool,
    swift_name: Option<&'static str>,
}

pub(super) fn signatures<'a>(owner: &'a TypeDef, fields: &[&'a FieldDef]) -> Vec<ConstructorSignature<'a>> {
    let has_legacy = !owner.legacy_constructor_fields().is_empty();
    let mut signatures = Vec::new();
    if has_legacy {
        signatures.push(ConstructorSignature {
            rust_name: "new".into(),
            fields: recorded_fields(owner.legacy_constructor_fields(), fields),
            is_initializer: true,
            swift_name: None,
        });
    }
    let recorded = owner
        .constructor_history()
        .iter()
        .map(|signature| recorded_fields(signature, fields));
    for (index, fields) in recorded.chain(std::iter::once(fields.to_vec())).enumerate() {
        let base = if has_legacy { "new_with_fields" } else { "new" };
        signatures.push(ConstructorSignature {
            rust_name: if index == 0 {
                base.into()
            } else {
                constructor_history_symbol(index)
            },
            fields,
            is_initializer: !has_legacy,
            swift_name: (has_legacy && index != 0).then_some("new_with_fields"),
        });
    }
    signatures
}

pub(super) fn validate(owner: &TypeDef, fields: &[&FieldDef]) -> anyhow::Result<()> {
    for name in owner
        .legacy_constructor_fields()
        .iter()
        .chain(owner.constructor_history().iter().flatten())
    {
        anyhow::ensure!(
            fields.iter().any(|field| field.name == *name),
            "Swift legacy constructor field {}.{name} is not exposed",
            owner.name
        );
    }
    for index in 1..=owner.constructor_history().len() {
        let symbol = constructor_history_symbol(index);
        let method_collision = owner
            .methods
            .iter()
            .any(|method| swift_rust_shim_ident(&method.name.to_snake_case()) == symbol);
        let field_collision = owner
            .fields
            .iter()
            .any(|field| swift_rust_shim_ident(&field.name.to_snake_case()) == symbol);
        anyhow::ensure!(
            !method_collision && !field_collision,
            "Swift DTO {} constructor history symbol `{symbol}` conflicts with an emitted method or getter",
            owner.name
        );
    }
    Ok(())
}

fn recorded_fields<'a>(signature: &[String], fields: &[&'a FieldDef]) -> Vec<&'a FieldDef> {
    signature
        .iter()
        .map(|name| {
            *fields
                .iter()
                .find(|field| &field.name == name)
                .expect("validated Swift constructor history field")
        })
        .collect()
}

pub(super) fn emit_externs(owner: &TypeDef, fields: &[&FieldDef]) -> String {
    let mut output = String::new();
    for signature in signatures(owner, fields) {
        let parameters = signature
            .fields
            .iter()
            .map(|field| {
                let bridge = bridge_type(&field.ty);
                let ty = if field.optional && !needs_json_bridge(&field.ty) {
                    format!("Option<{bridge}>")
                } else {
                    bridge
                };
                let name = swift_rust_shim_ident(&field.name.to_snake_case());
                format!("{name}: {ty}")
            })
            .collect::<Vec<_>>();
        if signature.is_initializer {
            output.push_str(&crate::backends::swift::template_env::render(
                "extern_init_attr.jinja",
                crate::alef_context! {},
            ));
        }
        output.push_str(&crate::backends::swift::template_env::render(
            "extern_fn_new.jinja",
            crate::alef_context! {
                params => parameters.join(", "), return_type => &owner.name,
                constructor_name => signature.rust_name,
                associated_type => if signature.is_initializer { None } else { Some(&owner.name) },
                swift_name => signature.swift_name,
            },
        ));
    }
    output
}