use crate::backends::swift::gen_rust_crate::type_bridge::{bridge_type, needs_json_bridge};
use crate::codegen::generators::trait_bridge::{TraitBridgeGenerator, TraitBridgeSpec};
use crate::core::ir::{MethodDef, TypeDef, TypeRef};
use heck::ToSnakeCase;
use std::collections::HashSet;
pub struct SwiftBridgeGenerator;
impl TraitBridgeGenerator for SwiftBridgeGenerator {
fn foreign_object_type(&self) -> &str {
"swift_bridge::opaque"
}
fn bridge_imports(&self) -> Vec<String> {
vec![]
}
fn gen_sync_method_body(&self, _method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
String::new()
}
fn gen_async_method_body(&self, _method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
String::new()
}
fn gen_constructor(&self, _spec: &TraitBridgeSpec) -> String {
String::new()
}
fn gen_registration_fn(&self, _spec: &TraitBridgeSpec) -> String {
String::new()
}
fn gen_unregistration_fn(&self, spec: &TraitBridgeSpec) -> String {
let Some(unregister_fn) = spec.bridge_config.unregister_fn.as_deref() else {
return String::new();
};
let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
return String::new();
};
let trait_name = &spec.trait_def.name;
format!(
"/// Unregister a previously-registered `{trait_name}` plugin by name.\n\
pub fn {unregister_fn}(name: String) -> Result<(), String> {{\n\
\x20\x20\x20\x20let registry = {registry_getter}();\n\
\x20\x20\x20\x20let mut guard = registry.write();\n\
\x20\x20\x20\x20guard.remove(&name).map_err(|e| e.to_string())\n\
}}\n"
)
}
fn gen_clear_fn(&self, spec: &TraitBridgeSpec) -> String {
let Some(clear_fn) = spec.bridge_config.clear_fn.as_deref() else {
return String::new();
};
let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
return String::new();
};
let trait_name = &spec.trait_def.name;
format!(
"/// Clear all registered `{trait_name}` plugins.\n\
pub fn {clear_fn}() -> Result<(), String> {{\n\
\x20\x20\x20\x20let registry = {registry_getter}();\n\
\x20\x20\x20\x20let mut guard = registry.write();\n\
\x20\x20\x20\x20guard.clear().map_err(|e| e.to_string())\n\
}}\n"
)
}
}
pub fn emit_extern_block_for_trait_bridge(trait_def: &TypeDef, visible_type_names: &HashSet<&str>) -> String {
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
block.push_str(&crate::backends::swift::template_env::render(
"trait_extern_type.jinja",
minijinja::context! {
trait_name => &trait_def.name,
},
));
let trait_snake = heck::AsSnakeCase(trait_def.name.as_str()).to_string();
block.push_str(&crate::backends::swift::template_env::render(
"trait_phantom_fn.jinja",
minijinja::context! {
trait_name => &trait_def.name,
trait_snake => &trait_snake,
},
));
for method in &trait_def.methods {
if method.has_default_impl {
continue;
}
let method_name = method.name.to_snake_case();
let fn_name = format!("{trait_snake}_call_{method_name}");
let mut params = vec!["this: &".to_string() + &format!("{}Box", trait_def.name)];
for p in &method.params {
let bridge_ty = bridge_type_for_trait_method(&p.ty, visible_type_names);
let name = p.name.to_snake_case();
params.push(format!("{name}: {bridge_ty}"));
}
let return_ty = if method.error_type.is_some() {
"String".to_string()
} else {
bridge_type_for_trait_method(&method.return_type, visible_type_names)
};
let params_str = params.join(", ");
block.push_str(&crate::backends::swift::template_env::render(
"trait_method_fn.jinja",
minijinja::context! {
fn_name => &fn_name,
params => ¶ms_str,
return_type => &return_ty,
},
));
}
block.push_str(" }\n\n");
block
}
pub fn emit_trait_bridge_wrapper(
trait_def: &TypeDef,
source_crate: &str,
enum_names: &HashSet<&str>,
visible_type_names: &HashSet<&str>,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
let mut out = String::new();
let trait_name = &trait_def.name;
let trait_snake = heck::AsSnakeCase(trait_name.as_str()).to_string();
let trait_path = if trait_def.rust_path.is_empty() {
format!("{source_crate}::{trait_name}")
} else {
trait_def.rust_path.replace('-', "_")
};
out.push_str(&crate::backends::swift::template_env::render(
"trait_struct.jinja",
minijinja::context! {
trait_name => trait_name,
trait_path => &trait_path,
},
));
out.push_str(&crate::backends::swift::template_env::render(
"trait_phantom_impl.jinja",
minijinja::context! {
trait_name => trait_name,
trait_snake => &trait_snake,
},
));
for method in &trait_def.methods {
if method.has_default_impl {
continue;
}
let method_name = method.name.to_snake_case();
let fn_name = format!("{trait_snake}_call_{method_name}");
let mut sig_params = vec![format!("this: &{trait_name}Box")];
for p in &method.params {
let bridge_ty = bridge_type_for_trait_method(&p.ty, visible_type_names);
let name = p.name.to_snake_case();
let needs_mut = p.is_mut;
if needs_mut {
sig_params.push(format!("mut {name}: {bridge_ty}"));
} else {
sig_params.push(format!("{name}: {bridge_ty}"));
}
}
let sig_params_str = sig_params.join(", ");
let return_ty = if method.error_type.is_some() {
"String".to_string()
} else {
bridge_type_for_trait_method(&method.return_type, visible_type_names)
};
let call_args: Vec<String> = method
.params
.iter()
.map(|p| trait_call_arg(p, visible_type_names, type_paths))
.collect();
let call_args_str = call_args.join(", ");
let source_call = format!("this.0.{method_name}({call_args_str})");
let body = emit_trait_method_body(method, &source_call, &return_ty, enum_names, visible_type_names);
out.push_str(&crate::backends::swift::template_env::render(
"trait_method_impl.jinja",
minijinja::context! {
fn_name => &fn_name,
params => &sig_params_str,
return_type => &return_ty,
body => &body,
},
));
}
out
}
fn bridge_type_for_trait_method(ty: &TypeRef, visible_type_names: &HashSet<&str>) -> String {
match ty {
TypeRef::Named(name) if !visible_type_names.contains(name.as_str()) => "String".to_string(),
TypeRef::Optional(inner) => format!("Option<{}>", bridge_type_for_trait_method(inner, visible_type_names)),
TypeRef::Vec(inner) => format!("Vec<{}>", bridge_type_for_trait_method(inner, visible_type_names)),
_ => bridge_type(ty),
}
}
pub(crate) fn trait_call_arg(
p: &crate::core::ir::ParamDef,
visible_type_names: &HashSet<&str>,
type_paths: &std::collections::HashMap<String, String>,
) -> String {
let name = p.name.to_snake_case();
if needs_json_bridge(&p.ty) {
let native_ty = crate::backends::swift::gen_rust_crate::type_bridge::swift_bridge_rust_type(&p.ty);
let deser = format!("serde_json::from_str::<{native_ty}>(&{name}).expect(\"valid JSON for {name}\")");
if p.is_mut {
return format!("&mut {deser}");
}
if p.is_ref {
return format!("&{deser}");
}
return deser;
}
if matches!(p.ty, TypeRef::Path) {
if p.optional {
if p.is_ref {
return format!("{name}.as_ref().map(std::path::Path::new)");
}
return format!("{name}.map(std::path::PathBuf::from)");
}
if p.is_ref {
return format!("std::path::Path::new(&{name})");
}
return format!("std::path::PathBuf::from({name})");
}
if let TypeRef::Named(named) = &p.ty {
if !visible_type_names.contains(named.as_str()) {
let qualified = type_paths
.get(named.as_str())
.map(|p| p.replace('-', "_"))
.unwrap_or_else(|| named.clone());
let deser = format!("serde_json::from_str::<{qualified}>(&{name}).expect(\"valid JSON for {name}\")");
if p.is_ref {
return format!("&{deser}");
}
return deser;
}
}
if matches!(p.ty, TypeRef::Named(_)) {
if p.optional {
if p.is_ref {
return format!("{name}.as_ref().map(|w| &w.0)");
}
return format!("{name}.map(|w| w.0)");
}
if p.is_mut {
return format!("&mut {name}.0");
}
if p.is_ref {
return format!("&{name}.0");
}
return format!("{name}.0");
}
if p.is_ref {
match &p.ty {
TypeRef::Bytes | TypeRef::String | TypeRef::Char => return format!("&{name}"),
TypeRef::Vec(_) if p.optional => return format!("{name}.as_deref()"),
TypeRef::Vec(_) => return format!("{name}.as_slice()"),
_ => return format!("&{name}"),
}
}
name
}
pub(crate) fn emit_trait_method_body(
method: &MethodDef,
source_call: &str,
_return_ty: &str,
enum_names: &HashSet<&str>,
visible_type_names: &HashSet<&str>,
) -> String {
let wrap_return = |expr: String| -> String {
if needs_json_bridge(&method.return_type) {
format!("serde_json::to_string(&({expr})).expect(\"serializable return\")")
} else {
match &method.return_type {
TypeRef::String => format!("{expr}.to_string()"),
TypeRef::Path => format!("{expr}.display().to_string()"),
TypeRef::Named(name) => {
if !visible_type_names.contains(name.as_str()) {
format!("serde_json::to_string(&({expr})).expect(\"serializable return\")")
} else if enum_names.contains(name.as_str()) {
format!("{name}::from({expr})")
} else {
format!("{name}({expr})")
}
}
_ => expr,
}
}
};
let envelope_result_expr = |base: String| -> String {
let ok_fragment = if matches!(method.return_type, TypeRef::Unit) {
"\"null\"".to_string()
} else {
"serde_json::to_string(&v).expect(\"serializable return\")".to_string()
};
format!(
"match {base} {{\n\
\x20\x20\x20\x20Ok(v) => format!(\"{{{{\\\"ok\\\": {{}}}}}}\", {ok_fragment}),\n\
\x20\x20\x20\x20Err(e) => format!(\"{{{{\\\"err\\\": {{}}}}}}\", serde_json::to_string(&e.to_string()).expect(\"serializable error\")),\n\
}}"
)
};
if method.is_async {
let await_expr = format!("{source_call}.await");
if method.error_type.is_some() {
let enveloped = envelope_result_expr(await_expr);
format!(" crate::__alef_tokio_runtime().block_on(async {{ {enveloped} }})\n")
} else {
let inner = wrap_return(await_expr);
format!(" crate::__alef_tokio_runtime().block_on(async {{ {inner} }})\n")
}
} else if method.error_type.is_some() {
let enveloped = envelope_result_expr(source_call.to_string());
format!(" {enveloped}\n")
} else if method.returns_ref
&& matches!(&method.return_type, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String))
{
format!(" {source_call}.iter().map(|s| s.to_string()).collect()\n")
} else {
let wrapped = wrap_return(source_call.to_string());
format!(" {wrapped}\n")
}
}