use super::bridge_error::BRIDGE_ERROR_TYPE;
use super::extern_block::{BridgeFn, getter_bridge_type};
use super::wrappers::is_unbridgeable_getter;
use crate::backends::swift::gen_rust_crate::default_construction::EnumKinds;
use crate::core::backend::GeneratedFile;
use crate::core::ir::TypeDef;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::path::Path;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct OwnedVecElement {
pub swift_type: String,
pub optional: bool,
}
#[derive(Debug, Serialize)]
pub(crate) struct OwnedVecGetter {
pub swift_name: String,
pub raw_name: String,
pub element: String,
pub optional: bool,
}
pub(crate) fn bridge_getter_swift_name(field_name: &str) -> String {
crate::backends::swift::naming::owned_vector_raw_name(field_name)
}
pub(crate) fn raw_swift_name(public_name: &str) -> String {
crate::backends::swift::naming::owned_vector_raw_name(public_name)
}
pub(crate) fn owned_vec_element(bridge_ty: &str) -> Option<OwnedVecElement> {
let (inner, optional) = match bridge_ty.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
Some(inner) => (inner, true),
None => (bridge_ty, false),
};
let element = inner.strip_prefix("Vec<")?.strip_suffix('>')?;
let swift_type = if element == "String" {
"RustString".to_string()
} else if element.starts_with(|c: char| c.is_ascii_uppercase())
&& element.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
{
element.to_string()
} else {
return None;
};
Some(OwnedVecElement { swift_type, optional })
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct OwnedVecReturn {
pub element: OwnedVecElement,
pub throws: bool,
}
pub(crate) fn owned_vec_return(return_ty: &str) -> Option<OwnedVecReturn> {
let error_tail = format!(", {BRIDGE_ERROR_TYPE}>");
let (ok, throws) = match return_ty
.strip_prefix("Result<")
.and_then(|s| s.strip_suffix(error_tail.as_str()))
{
Some(ok) => (ok, true),
None => (return_ty, false),
};
Some(OwnedVecReturn {
element: owned_vec_element(ok)?,
throws,
})
}
#[derive(Debug, Serialize)]
pub(crate) struct OwnedVecFunction {
pub swift_name: String,
pub generics: String,
pub params: String,
pub is_async: bool,
pub throws: bool,
pub return_type: String,
pub body: String,
}
fn swift_param_type(bridge_ty: &str, uses_string: &mut bool) -> Option<String> {
if let Some(inner) = bridge_ty.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
return Some(format!("Optional<{}>", swift_param_type(inner, uses_string)?));
}
if let Some(inner) = bridge_ty.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
return Some(format!("RustVec<{}>", swift_param_type(inner, uses_string)?));
}
if let Some(inner) = bridge_ty.strip_prefix("&mut ") {
return Some(format!("{}RefMut", swift_param_type(inner, uses_string)?));
}
if let Some(inner) = bridge_ty.strip_prefix('&') {
return Some(format!("{}Ref", swift_param_type(inner, uses_string)?));
}
let primitive = match bridge_ty {
"bool" => "Bool",
"u8" => "UInt8",
"u16" => "UInt16",
"u32" => "UInt32",
"u64" => "UInt64",
"usize" => "UInt",
"i8" => "Int8",
"i16" => "Int16",
"i32" => "Int32",
"i64" => "Int64",
"isize" => "Int",
"f32" => "Float",
"f64" => "Double",
_ => "",
};
if !primitive.is_empty() {
return Some(primitive.to_string());
}
if bridge_ty == "String" {
*uses_string = true;
return Some("GenericIntoRustString".to_string());
}
let is_handle = bridge_ty.starts_with(|c: char| c.is_ascii_uppercase())
&& bridge_ty.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
is_handle.then(|| bridge_ty.to_string())
}
pub(crate) fn owned_vec_function(bridge_fn: &BridgeFn) -> anyhow::Result<Option<OwnedVecFunction>> {
let Some(ret) = owned_vec_return(&bridge_fn.return_ty) else {
return Ok(None);
};
let mut uses_string = false;
let mut decls = Vec::new();
let mut args = Vec::new();
for (name, ty) in &bridge_fn.params {
let swift_ty = swift_param_type(ty, &mut uses_string).ok_or_else(|| {
anyhow::anyhow!(
"Swift function {} returns a vector but its parameter {name}: {ty} has no owned-array wrapper",
bridge_fn.public_name
)
})?;
decls.push(format!("_ {name}: {swift_ty}"));
args.push(name.clone());
}
let call = format!(
"{}{}{}({})",
if ret.throws { "try " } else { "" },
if bridge_fn.is_async { "await " } else { "" },
bridge_fn.swift_name,
args.join(", ")
);
let (return_type, body) = if ret.element.optional {
(
format!("[{}]?", ret.element.swift_type),
format!("({call}).map {{ $0.alefIntoOwnedArray() }}"),
)
} else {
(
format!("[{}]", ret.element.swift_type),
format!("{call}.alefIntoOwnedArray()"),
)
};
Ok(Some(OwnedVecFunction {
swift_name: bridge_fn.public_name.clone(),
generics: if uses_string {
"<GenericIntoRustString: IntoRustString>".to_string()
} else {
String::new()
},
params: decls.join(", "),
is_async: bridge_fn.is_async,
throws: ret.throws,
return_type,
body,
}))
}
pub(crate) struct OwnedVecGetterFacts<'sets, 'names> {
pub exclude_fields: &'sets HashSet<String>,
pub type_paths: &'sets HashMap<String, String>,
pub no_serde_names: &'sets HashSet<&'names str>,
pub first_class_names: &'sets HashSet<&'names str>,
pub enum_kinds: EnumKinds<'sets, 'names>,
pub text_types: &'sets HashSet<&'names str>,
pub configured_features: &'sets HashSet<&'names str>,
}
pub(crate) fn owned_vec_getters(ty: &TypeDef, facts: &OwnedVecGetterFacts<'_, '_>) -> Vec<OwnedVecGetter> {
let parent_first_class = facts.first_class_names.contains(ty.name.as_str());
let visible_fields: Vec<_> = ty
.fields
.iter()
.filter(|field| {
!is_unbridgeable_getter(
ty,
field,
facts.exclude_fields,
facts.type_paths,
facts.no_serde_names,
facts.configured_features,
)
})
.collect();
let getters: Vec<_> = visible_fields
.iter()
.filter_map(|field| {
let bridge_ty = getter_bridge_type(
field,
facts.enum_kinds,
facts.no_serde_names,
parent_first_class,
facts.text_types,
);
let element = owned_vec_element(&bridge_ty)?;
Some(OwnedVecGetter {
swift_name: crate::backends::swift::naming::bridge_field_name(&field.name),
raw_name: bridge_getter_swift_name(&field.name),
element: element.swift_type,
optional: element.optional,
})
})
.collect();
getters
}
pub(crate) type TypeOwnedVecGetters = (String, Vec<OwnedVecGetter>);
#[derive(Debug, Default)]
pub(crate) struct OwnedVecAccessors {
pub types: Vec<TypeOwnedVecGetters>,
pub functions: Vec<OwnedVecFunction>,
}
impl OwnedVecAccessors {
pub(crate) fn add_function(&mut self, function: &BridgeFn) -> anyhow::Result<()> {
self.functions.extend(owned_vec_function(function)?);
Ok(())
}
}
#[derive(Serialize)]
struct TypeAccessors<'a> {
name: &'a str,
getters: &'a [OwnedVecGetter],
}
pub(crate) fn emit_swift_file(accessors: &OwnedVecAccessors, rust_bridge_sources: &Path) -> Option<GeneratedFile> {
let functions = &accessors.functions;
let entries: Vec<TypeAccessors<'_>> = accessors
.types
.iter()
.filter(|(_, getters)| !getters.is_empty())
.map(|(name, getters)| TypeAccessors { name, getters })
.collect();
if entries.is_empty() && functions.is_empty() {
return None;
}
let content = crate::backends::swift::template_env::render(
"swift_owned_vec_accessors.swift.jinja",
crate::alef_context! {
header => crate::core::hash::SELF_MARKING_HEADER_LINE,
types => &entries,
functions => functions,
},
);
Some(GeneratedFile {
path: rust_bridge_sources.join("ZSwiftOwnedVecAccessors.swift"),
content,
generated_header: false,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_and_handle_vectors_are_owned_and_primitives_are_not() {
let owned = |ty: &str| owned_vec_element(ty);
assert_eq!(
owned("Vec<Choice>"),
Some(OwnedVecElement {
swift_type: "Choice".into(),
optional: false
})
);
assert_eq!(
owned("Vec<String>"),
Some(OwnedVecElement {
swift_type: "RustString".into(),
optional: false
})
);
assert_eq!(
owned("Option<Vec<Choice>>"),
Some(OwnedVecElement {
swift_type: "Choice".into(),
optional: true
})
);
for ty in [
"Vec<u8>",
"Vec<f32>",
"Vec<bool>",
"String",
"Choice",
"Vec<Vec<String>>",
"Option<String>",
] {
assert_eq!(owned(ty), None, "{ty} must keep its bridge shape");
}
}
#[test]
fn raw_getter_name_uses_reserved_internal_namespace() {
assert_eq!(
bridge_getter_swift_name("model_prefixes"),
"__alef_owned_vec_model_prefixes"
);
}
}