alef 0.115.2

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::backends::dart::type_map::DartMapper;
use crate::codegen::naming::{PublicIdentifierKind, public_host_identifier};
use crate::codegen::type_mapper::TypeMapper;
use crate::core::config::Language;
use crate::core::config::{AdapterConfig, ResolvedCrateConfig};
use crate::core::ir::{ErrorDef, PrimitiveType, TypeDef, TypeRef};
use std::collections::HashMap;

pub(super) fn emit_streaming_sink_method(
    out: &mut String,
    method_name: &str,
    adapter: &AdapterConfig,
    errors: &[ErrorDef],
    config: &ResolvedCrateConfig,
) -> anyhow::Result<()> {
    let params = adapter
        .params
        .iter()
        .map(|param| {
            let ty = if param.optional {
                format!("Option<{}>", param.ty)
            } else {
                param.ty.clone()
            };
            format!(", {}: {ty}", param.name)
        })
        .collect::<String>();
    let typed_error = error_definition(adapter, config, errors).is_some();
    let (body, _) = crate::adapters::streaming::dart_body(adapter, config, typed_error)?;
    let item_type = if typed_error {
        crate::adapters::streaming::dart_transport_names(adapter).carrier
    } else {
        adapter.item_type.clone().unwrap_or("()".into())
    };
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_streaming_sink_method.rs.jinja",
        crate::alef_context! {
            method_name => method_name,
            params_str => params,
            item_type => item_type,
            body => body,
        },
    ));
    Ok(())
}

fn error_definition<'a>(
    adapter: &AdapterConfig,
    config: &ResolvedCrateConfig,
    errors: &'a [ErrorDef],
) -> Option<&'a ErrorDef> {
    let error_type = adapter.error_type.as_deref();
    super::error_bridge::typed_error_definition(error_type, errors).or_else(|| {
        let prefix = format!("{}::", config.core_import_name());
        let local = error_type.and_then(|name| name.strip_prefix(&prefix));
        super::error_bridge::typed_error_definition(local, errors)
    })
}

pub(super) fn emit_transports(
    out: &mut String,
    ty: &TypeDef,
    stub_methods: &[String],
    adapters: &HashMap<String, &AdapterConfig>,
    config: &ResolvedCrateConfig,
    errors: &[ErrorDef],
) {
    for method in &ty.methods {
        if !method.sanitized || stub_methods.contains(&method.name) {
            continue;
        }
        let Some(adapter) = adapters.get(&format!("{}.{}", ty.name, method.name)) else {
            continue;
        };
        let Some(error) = error_definition(adapter, config, errors) else {
            continue;
        };
        emit_transport(out, adapter, error, config, ty.cfg.as_deref().unwrap_or(""));
    }
}

fn emit_transport(
    out: &mut String,
    adapter: &AdapterConfig,
    error: &ErrorDef,
    config: &ResolvedCrateConfig,
    owner_cfg: &str,
) {
    let transport = crate::adapters::streaming::dart_transport_names(adapter);
    let dart_wire = public_host_identifier(Language::Dart, PublicIdentifierKind::Type, &transport.wire);
    let arms = [
        if transport.unit_item {
            format!("{dart_wire}_Item() => null")
        } else {
            format!("{dart_wire}_Item(:final value) => value")
        },
        format!("{dart_wire}_Error(:final error) => throw error"),
        format!("{dart_wire}_NativeFailure(:final message) => throw StateError(message)"),
    ];
    let decode = format!("switch ({{}}) {{ {} }}", arms.join(", "));
    let factory = public_host_identifier(Language::Dart, PublicIdentifierKind::Method, "Item");
    let encode = if transport.unit_item {
        format!("{dart_wire}.{factory}()")
    } else {
        format!("{dart_wire}.{factory}(value: {{}})")
    };
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_streaming_transport.rs.jinja",
        crate::alef_context! {
            transport => &transport,
            source_cfg => super::helpers::widen_opaque_wrapper_cfg(owner_cfg),
            item_type => adapter.item_type.as_deref().unwrap_or("()"),
            error_type => &error.name,
            core_error_type => super::error_bridge::core_error_path(error, &config.core_import_name()),
            dart_type => dart_item_type(adapter.item_type.as_deref().unwrap_or("()")),
            decode_code => format!("{decode:?}"),
            encode_code => format!("{encode:?}"),
        },
    ));
}

fn dart_item_type(item_type: &str) -> String {
    let ty = syn::parse_str::<syn::Type>(item_type)
        .map(|ty| crate::extract::type_resolver::resolve_type(&ty))
        .unwrap_or_else(|_| TypeRef::Named(item_type.into()));
    native_dart_type(&ty)
}

fn native_dart_type(ty: &TypeRef) -> String {
    match ty {
        TypeRef::Primitive(PrimitiveType::U64 | PrimitiveType::Usize) => "BigInt".into(),
        TypeRef::Primitive(PrimitiveType::I64 | PrimitiveType::Isize) => "PlatformInt64".into(),
        TypeRef::Optional(inner) => format!("{}?", native_dart_type(inner)),
        TypeRef::Vec(inner) => native_typed_list(inner)
            .map(str::to_owned)
            .unwrap_or_else(|| format!("List<{}>", native_dart_type(inner))),
        TypeRef::Map(key, value) => format!("Map<{}, {}>", native_dart_type(key), native_dart_type(value)),
        _ => DartMapper.map_type(ty),
    }
}

fn native_typed_list(ty: &TypeRef) -> Option<&'static str> {
    Some(match ty {
        TypeRef::Primitive(PrimitiveType::U8) => "Uint8List",
        TypeRef::Primitive(PrimitiveType::I8) => "Int8List",
        TypeRef::Primitive(PrimitiveType::U16) => "Uint16List",
        TypeRef::Primitive(PrimitiveType::I16) => "Int16List",
        TypeRef::Primitive(PrimitiveType::U32) => "Uint32List",
        TypeRef::Primitive(PrimitiveType::I32) => "Int32List",
        TypeRef::Primitive(PrimitiveType::U64 | PrimitiveType::Usize) => "Uint64List",
        TypeRef::Primitive(PrimitiveType::I64 | PrimitiveType::Isize) => "Int64List",
        TypeRef::Primitive(PrimitiveType::F32) => "Float32List",
        TypeRef::Primitive(PrimitiveType::F64) => "Float64List",
        _ => return None,
    })
}

#[cfg(test)]
mod tests {
    #[test]
    fn stream_codec_uses_dart_types_for_native_items() {
        for (rust, dart) in [
            ("Chunk", "Chunk"),
            ("String", "String"),
            ("u32", "int"),
            ("f64", "double"),
            ("bool", "bool"),
            ("u64", "BigInt"),
            ("i64", "PlatformInt64"),
            ("Option<u64>", "BigInt?"),
            ("Vec<u32>", "Uint32List"),
            ("Vec<f32>", "Float32List"),
            ("Vec<Option<u64>>", "List<BigInt?>"),
            ("()", "void"),
        ] {
            assert_eq!(super::dart_item_type(rust), dart, "{rust}");
        }
    }
}

#[cfg(test)]
#[path = "streaming_layout_tests.rs"]
mod layout_tests;