alef 0.107.6

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::core::ir::ApiSurface;

pub(crate) fn project(api: &ApiSurface) -> ApiSurface {
    project_owned(api.clone())
}

pub(crate) fn project_owned(mut api: ApiSurface) -> ApiSurface {
    api.functions.retain(|function| !function.binding_excluded);
    api.types.retain(|type_def| !type_def.binding_excluded);
    api.enums.retain(|enum_def| !enum_def.binding_excluded);
    api.errors.retain(|error_def| !error_def.binding_excluded);
    crate::cli::pipeline::sanitize_binding_projection(&mut api);
    normalize_tuple_param_metadata(&mut api);
    api
}

fn normalize_tuple_param_metadata(api: &mut ApiSurface) {
    for function in &mut api.functions {
        normalize_params(&mut function.params);
    }
    for type_def in &mut api.types {
        for method in &mut type_def.methods {
            normalize_params(&mut method.params);
        }
    }
    for enum_def in &mut api.enums {
        for method in &mut enum_def.methods {
            normalize_params(&mut method.params);
        }
    }
}

fn normalize_params(params: &mut [crate::core::ir::ParamDef]) {
    for param in params {
        let Some(original) = param.original_type.as_deref() else {
            continue;
        };
        let Ok(source_type) = syn::parse_str::<syn::Type>(original) else {
            continue;
        };
        let resolved = match crate::extract::type_resolver::resolve_type(&source_type) {
            crate::core::ir::TypeRef::Optional(inner) => *inner,
            resolved => resolved,
        };
        if contains_tuple(&resolved) {
            param.original_type = Some(format!("{resolved:?}"));
        }
    }
}

fn contains_tuple(ty: &crate::core::ir::TypeRef) -> bool {
    match ty {
        crate::core::ir::TypeRef::Named(name) => name.starts_with('('),
        crate::core::ir::TypeRef::Optional(inner) | crate::core::ir::TypeRef::Vec(inner) => contains_tuple(inner),
        crate::core::ir::TypeRef::Map(key, value) => contains_tuple(key) || contains_tuple(value),
        _ => false,
    }
}

#[cfg(test)]
mod tests {
    use super::project_owned;
    use crate::core::ir::{ApiSurface, FunctionDef, ParamDef, TypeRef};

    #[test]
    fn projects_source_tuple_syntax_to_binding_tuple_metadata() {
        let api = ApiSurface {
            functions: vec![FunctionDef {
                name: "record".to_string(),
                params: vec![
                    ParamDef {
                        name: "entries".to_string(),
                        ty: TypeRef::Vec(Box::new(TypeRef::String)),
                        sanitized: true,
                        is_ref: true,
                        original_type: Some("&[(PathBuf, Option<FileExtractionConfig>)]".to_string()),
                        ..ParamDef::default()
                    },
                    ParamDef {
                        name: "optional_entries".to_string(),
                        ty: TypeRef::Vec(Box::new(TypeRef::String)),
                        optional: true,
                        sanitized: true,
                        is_ref: true,
                        original_type: Some("Option<&[(PathBuf, Option<FileExtractionConfig>)]>".to_string()),
                        ..ParamDef::default()
                    },
                ],
                ..FunctionDef::default()
            }],
            ..ApiSurface::default()
        };

        let projected = project_owned(api);

        assert_eq!(
            projected.functions[0].params[0].original_type.as_deref(),
            Some("Vec(Named(\"(PathBuf, Option<FileExtractionConfig>)\"))")
        );
        assert_eq!(
            projected.functions[0].params[1].original_type.as_deref(),
            Some("Vec(Named(\"(PathBuf, Option<FileExtractionConfig>)\"))")
        );
    }
}