use crate::backends::swift::naming::bridge_protocol_name;
use crate::core::config::TraitBridgeConfig;
use crate::core::ir::{TypeDef, TypeRef};
use heck::{ToLowerCamelCase, ToSnakeCase};
use std::collections::HashSet;
pub fn gen_trait_bridge_files(
bridges: &[(String, &TraitBridgeConfig, &TypeDef)],
exclude_types: &HashSet<String>,
first_class_types: &HashSet<String>,
) -> Vec<(String, String)> {
let mut files = Vec::new();
let function_param_bridges: Vec<_> = bridges
.iter()
.filter(|(_, bridge_cfg, _)| {
!bridge_cfg.exclude_languages.iter().any(|lang| lang == "swift")
&& matches!(bridge_cfg.bind_via, crate::core::config::BridgeBinding::FunctionParam)
})
.collect();
if !function_param_bridges.is_empty() {
let content = emit_swift_plugin_bridge_protocol();
files.push(("SwiftPluginBridge.swift".to_string(), content));
}
for (trait_name, bridge_cfg, trait_def) in bridges {
if bridge_cfg.exclude_languages.iter().any(|lang| lang == "swift") {
continue;
}
if !matches!(bridge_cfg.bind_via, crate::core::config::BridgeBinding::FunctionParam) {
continue;
}
let mut combined_exclude = exclude_types.clone();
for first_class in first_class_types {
combined_exclude.insert(first_class.clone());
}
let content = gen_single_trait_bridge_file(trait_name, bridge_cfg, trait_def, &combined_exclude);
let protocol = bridge_protocol_name(trait_name);
let filename = format!("{protocol}.swift");
files.push((filename, content));
}
files
}
fn emit_swift_plugin_bridge_protocol() -> String {
crate::backends::swift::template_env::render("swift_plugin_bridge_protocol.swift.jinja", minijinja::context! {})
}
pub fn collect_named_types(type_ref: &TypeRef, named_types: &mut HashSet<String>) {
match type_ref {
TypeRef::Named(name) => {
named_types.insert(name.clone());
}
TypeRef::Optional(inner) | TypeRef::Vec(inner) => {
collect_named_types(inner, named_types);
}
TypeRef::Map(key, val) => {
collect_named_types(key, named_types);
collect_named_types(val, named_types);
}
_ => {}
}
}
pub fn excluded_named_type_bridge_policy(trait_def: &TypeDef, excluded_types: &HashSet<String>) -> HashSet<String> {
let mut policy = excluded_types.clone();
for method in &trait_def.methods {
for param in &method.params {
collect_named_types(¶m.ty, &mut policy);
}
collect_named_types(&method.return_type, &mut policy);
}
policy
}
fn gen_single_trait_bridge_file(
trait_name: &str,
_bridge_cfg: &TraitBridgeConfig,
trait_def: &TypeDef,
exclude_types: &HashSet<String>,
) -> String {
let bridge_exclude_types = excluded_named_type_bridge_policy(trait_def, exclude_types);
let protocol = bridge_protocol_name(trait_name);
let mut protocol_methods = String::new();
for method in &trait_def.methods {
let method_camel = method.name.to_lower_camel_case();
let params_sig = swift_method_params(&method.params, &bridge_exclude_types);
let return_type = swift_return_type(&method.return_type, &bridge_exclude_types);
let throws = if method.error_type.is_some() { " throws" } else { "" };
protocol_methods.push_str(&crate::backends::swift::template_env::render(
"swift_trait_protocol_method.swift.jinja",
minijinja::context! {
method_name => method_camel,
params => params_sig,
throws_clause => throws,
return_type => return_type,
has_rust_default => method.has_default_impl,
},
));
}
let mut adapter_methods = String::new();
for method in &trait_def.methods {
let method_camel = method.name.to_lower_camel_case();
let params_sig = swift_method_params(&method.params, &bridge_exclude_types);
let return_type = if method.error_type.is_some() {
"String".to_string()
} else {
swift_return_type(&method.return_type, &bridge_exclude_types)
};
let throws_kw = if method.error_type.is_some() { " throws" } else { "" };
let call_args = build_adapter_call_args(method);
let call_args_str = call_args.join(", ");
let method_body = if method.error_type.is_some() {
let success_body = trait_adapter_success_body(&method.return_type, &bridge_exclude_types);
crate::backends::swift::template_env::render(
"swift_trait_adapter_error_body.swift.jinja",
minijinja::context! {
method_name => &method_camel,
call_args => &call_args_str,
success_body => success_body,
binds_result => adapter_binds_result(&method.return_type),
},
)
} else {
crate::backends::swift::template_env::render(
"swift_trait_adapter_direct_body.swift.jinja",
minijinja::context! {
method_name => &method_camel,
call_args => &call_args_str,
binds_result => adapter_binds_result(&method.return_type),
},
)
};
adapter_methods.push_str(&crate::backends::swift::template_env::render(
"swift_trait_adapter_method.swift.jinja",
minijinja::context! {
method_name => method_camel,
params => params_sig,
throws_clause => throws_kw,
return_type => return_type,
body => method_body,
},
));
}
crate::backends::swift::template_env::render(
"swift_trait_bridge_file.swift.jinja",
minijinja::context! {
trait_name => trait_name,
adapter_class => format!("Swift{trait_name}Adapter"),
protocol => protocol,
protocol_methods => protocol_methods,
adapter_methods => adapter_methods,
},
)
}
fn adapter_binds_result(return_type: &TypeRef) -> bool {
!matches!(return_type, TypeRef::Unit)
}
fn trait_adapter_success_body(return_type: &TypeRef, bridge_exclude_types: &HashSet<String>) -> String {
let expression = match return_type {
TypeRef::Unit => Some("marshal_ok_result(Empty())"),
TypeRef::String => Some("marshal_ok_result(String(result))"),
TypeRef::Primitive(_) | TypeRef::Bytes | TypeRef::Char => Some("marshal_ok_result(result)"),
TypeRef::Vec(inner) => match **inner {
TypeRef::String => Some("marshal_ok_result(result.map { String($0) })"),
_ => Some("marshal_ok_result(try JSONEncoder().encode(result))"),
},
TypeRef::Named(name) if bridge_exclude_types.contains(name) => None,
_ => Some("marshal_ok_result(try JSONEncoder().encode(result))"),
};
if let Some(expression) = expression {
crate::backends::swift::template_env::render(
"swift_trait_adapter_success.swift.jinja",
minijinja::context! {
expression => expression,
},
)
} else {
crate::backends::swift::template_env::render(
"swift_trait_adapter_excluded_success.swift.jinja",
minijinja::context! {},
)
}
}
#[allow(dead_code)]
fn swift_method_params_native(params: &[crate::core::ir::ParamDef], exclude_types: &HashSet<String>) -> String {
if params.is_empty() {
return String::new();
}
params
.iter()
.map(|p| {
let name = p.name.to_snake_case();
let ty = swift_type_name_native(&p.ty, exclude_types);
format!("{}: {}", name, ty)
})
.collect::<Vec<_>>()
.join(", ")
}
fn swift_method_params(params: &[crate::core::ir::ParamDef], exclude_types: &HashSet<String>) -> String {
if params.is_empty() {
return String::new();
}
params
.iter()
.map(|p| {
let name = p.name.to_lower_camel_case();
let ty = swift_type_name(&p.ty, exclude_types);
format!("{}: {}", name, ty)
})
.collect::<Vec<_>>()
.join(", ")
}
#[allow(dead_code)]
fn swift_type_name_native(ty: &TypeRef, _exclude_types: &HashSet<String>) -> String {
match ty {
TypeRef::Primitive(p) => match p {
crate::core::ir::PrimitiveType::Bool => "Bool".to_string(),
crate::core::ir::PrimitiveType::I8 => "Int8".to_string(),
crate::core::ir::PrimitiveType::I16 => "Int16".to_string(),
crate::core::ir::PrimitiveType::I32 => "Int32".to_string(),
crate::core::ir::PrimitiveType::I64 => "Int64".to_string(),
crate::core::ir::PrimitiveType::U8 => "UInt8".to_string(),
crate::core::ir::PrimitiveType::U16 => "UInt16".to_string(),
crate::core::ir::PrimitiveType::U32 => "UInt32".to_string(),
crate::core::ir::PrimitiveType::U64 => "UInt64".to_string(),
crate::core::ir::PrimitiveType::Usize => "UInt".to_string(),
crate::core::ir::PrimitiveType::Isize => "Int".to_string(),
crate::core::ir::PrimitiveType::F32 => "Float".to_string(),
crate::core::ir::PrimitiveType::F64 => "Double".to_string(),
},
TypeRef::String => "String".to_string(),
TypeRef::Bytes => "Data".to_string(),
TypeRef::Path => "URL".to_string(),
TypeRef::Char => "Character".to_string(),
TypeRef::Named(name) => name.clone(),
TypeRef::Vec(inner) => format!("[{}]", swift_type_name_native(inner, _exclude_types)),
TypeRef::Map(k, v) => format!(
"[{}: {}]",
swift_type_name_native(k, _exclude_types),
swift_type_name_native(v, _exclude_types)
),
TypeRef::Optional(inner) => format!("{}?", swift_type_name_native(inner, _exclude_types)),
TypeRef::Unit => "Void".to_string(),
TypeRef::Json => "String".to_string(),
TypeRef::Duration => "TimeInterval".to_string(),
}
}
fn swift_type_name(ty: &TypeRef, exclude_types: &HashSet<String>) -> String {
match ty {
TypeRef::Primitive(p) => match p {
crate::core::ir::PrimitiveType::Bool => "Bool".to_string(),
crate::core::ir::PrimitiveType::I8 => "Int8".to_string(),
crate::core::ir::PrimitiveType::I16 => "Int16".to_string(),
crate::core::ir::PrimitiveType::I32 => "Int32".to_string(),
crate::core::ir::PrimitiveType::I64 => "Int64".to_string(),
crate::core::ir::PrimitiveType::U8 => "UInt8".to_string(),
crate::core::ir::PrimitiveType::U16 => "UInt16".to_string(),
crate::core::ir::PrimitiveType::U32 => "UInt32".to_string(),
crate::core::ir::PrimitiveType::U64 => "UInt64".to_string(),
crate::core::ir::PrimitiveType::Usize => "UInt".to_string(),
crate::core::ir::PrimitiveType::Isize => "Int".to_string(),
crate::core::ir::PrimitiveType::F32 => "Float".to_string(),
crate::core::ir::PrimitiveType::F64 => "Double".to_string(),
},
TypeRef::String => "String".to_string(),
TypeRef::Bytes => "Data".to_string(),
TypeRef::Path => "URL".to_string(),
TypeRef::Char => "Character".to_string(),
TypeRef::Named(name) => {
if exclude_types.contains(name) {
"String".to_string()
} else {
name.clone()
}
}
TypeRef::Vec(inner) => format!("[{}]", swift_type_name(inner, exclude_types)),
TypeRef::Map(_, _) => "String".to_string(),
TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(elem) if matches!(elem.as_ref(), TypeRef::Named(name) if exclude_types.contains(name))) => {
"String?".to_string()
}
TypeRef::Optional(inner) => format!("{}?", swift_type_name(inner, exclude_types)),
TypeRef::Unit => "Void".to_string(),
TypeRef::Json => "String".to_string(),
TypeRef::Duration => "TimeInterval".to_string(),
}
}
fn swift_return_type(ty: &TypeRef, exclude_types: &HashSet<String>) -> String {
swift_type_name(ty, exclude_types)
}
fn build_adapter_call_args(method: &crate::core::ir::MethodDef) -> Vec<String> {
method
.params
.iter()
.map(|p| {
let camel = p.name.to_lower_camel_case();
format!("{}: {}", camel, camel)
})
.collect()
}
pub fn gen_bridge_registration_overloads_file(
bridges: &[(String, &TraitBridgeConfig, &TypeDef)],
) -> Option<(String, String)> {
let trait_bridges: Vec<_> = bridges
.iter()
.filter(|(_, bridge_cfg, _)| {
!bridge_cfg.exclude_languages.iter().any(|lang| lang == "swift")
&& matches!(bridge_cfg.bind_via, crate::core::config::BridgeBinding::FunctionParam)
})
.collect();
if trait_bridges.is_empty() {
return None;
}
let mut protocol_aliases = String::new();
for (trait_name, _, _) in &trait_bridges {
let protocol = bridge_protocol_name(trait_name);
protocol_aliases.push_str(&crate::backends::swift::template_env::render(
"typealias.jinja",
minijinja::context! { name => &protocol },
));
}
let mut unregister_overloads = String::new();
for (trait_name, _, _) in &trait_bridges {
let pascal_name = trait_bridge_pascal_name(trait_name);
unregister_overloads.push_str(&crate::backends::swift::template_env::render(
"swift_trait_unregister_overload.swift.jinja",
minijinja::context! {
pascal_name => &pascal_name,
},
));
}
let mut register_overloads = String::new();
for (trait_name, _, _) in &trait_bridges {
let pascal_name = trait_bridge_pascal_name(trait_name);
register_overloads.push_str(&crate::backends::swift::template_env::render(
"swift_trait_register_overload.swift.jinja",
minijinja::context! {
pascal_name => &pascal_name,
},
));
}
let content = crate::backends::swift::template_env::render(
"swift_trait_bridge_overloads.swift.jinja",
minijinja::context! {
protocol_aliases => protocol_aliases,
unregister_overloads => unregister_overloads,
register_overloads => register_overloads,
},
);
Some(("BridgeRegistrationOverloads.swift".to_string(), content))
}
fn trait_bridge_pascal_name(s: &str) -> String {
crate::codegen::naming::to_class_name(s)
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod void_binding_tests;