use crate::backends::swift::gen_rust_crate::type_bridge::{
bridge_type, bridge_type_enum_aware, bridge_type_enum_aware_ref, bridge_type_with_handles, is_vec_of_enum,
needs_json_bridge,
};
use crate::backends::swift::gen_rust_crate::wrappers::is_unbridgeable_getter;
use crate::core::config::AdapterConfig;
use crate::core::ir::{EnumDef, FieldDef, FunctionDef, TypeDef, TypeRef};
use crate::core::keywords::swift_ident;
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use std::collections::{HashMap, HashSet};
pub(crate) fn constructor_fields<'a>(ty: &'a TypeDef, exclude_fields: &HashSet<String>) -> Vec<&'a FieldDef> {
ty.fields
.iter()
.filter(|f| {
let field_key = format!("{}.{}", ty.name, f.name.to_snake_case());
!f.binding_excluded && !exclude_fields.contains(&field_key)
})
.collect()
}
pub(crate) fn has_constructor_extern(ty: &TypeDef, exclude_fields: &HashSet<String>) -> bool {
let fields = constructor_fields(ty, exclude_fields);
if fields.is_empty() {
return false;
}
let all_primitive_fields = fields.iter().all(|f| matches!(f.ty, TypeRef::Primitive(_)));
if all_primitive_fields {
return true;
}
let has_vec_non_primitive = fields.iter().any(
|f| matches!(&f.ty, TypeRef::Vec(inner) if !matches!(inner.as_ref(), TypeRef::Primitive(_) | TypeRef::Bytes)),
);
let has_non_serde_string_field = !ty.has_serde
&& fields
.iter()
.any(|f| matches!(f.ty, TypeRef::String | TypeRef::Path | TypeRef::Json | TypeRef::Char));
let needs_default_construction = ty.has_serde
|| has_vec_non_primitive
|| has_non_serde_string_field
|| ty.has_stripped_cfg_fields
|| fields
.iter()
.any(|f| needs_json_bridge(&f.ty) || matches!(f.ty, TypeRef::Named(_)));
!needs_default_construction || ty.has_default
}
pub(crate) fn emit_extern_block_for_type(
ty: &TypeDef,
exclude_fields: &HashSet<String>,
type_paths: &HashMap<String, String>,
no_serde_names: &HashSet<&str>,
enum_names: &HashSet<String>,
) -> String {
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
block.push_str(&crate::backends::swift::template_env::render(
"extern_type_decl.jinja",
minijinja::context! {
name => &ty.name,
},
));
let constructor_fields = constructor_fields(ty, exclude_fields);
let emit_constructor = has_constructor_extern(ty, exclude_fields);
if emit_constructor {
let params: Vec<String> = constructor_fields
.iter()
.map(|f| {
let bridge_ty = bridge_type(&f.ty);
let bridge_ty = if f.optional && !needs_json_bridge(&f.ty) {
format!("Option<{bridge_ty}>")
} else {
bridge_ty
};
let name = swift_ident(&f.name.to_snake_case());
format!("{name}: {bridge_ty}")
})
.collect();
block.push_str(&crate::backends::swift::template_env::render(
"extern_init_attr.jinja",
minijinja::context! {},
));
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_new.jinja",
minijinja::context! {
params => params.join(", "),
return_type => &ty.name,
},
));
}
for field in &ty.fields {
if is_unbridgeable_getter(ty, field, exclude_fields, type_paths, no_serde_names) {
continue;
}
let bridge_ty = bridge_type_enum_aware(&field.ty, enum_names);
let bridge_ty = if field.optional && !needs_json_bridge(&field.ty) {
if is_vec_of_enum(&field.ty, &enum_names.iter().map(|s| s.as_str()).collect()) {
"String".to_string()
} else {
format!("Option<{bridge_ty}>")
}
} else {
bridge_ty
};
let name = swift_ident(&field.name.to_snake_case());
let swift_name = swift_ident(&field.name.to_lower_camel_case());
if swift_name != name {
block.push_str(&crate::backends::swift::template_env::render(
"extern_swift_name_attr.jinja",
minijinja::context! {
swift_name => &swift_name,
},
));
}
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_getter.jinja",
minijinja::context! {
name => &name,
return_type => &bridge_ty,
},
));
}
block.push_str(" }\n\n");
block
}
pub(crate) fn emit_extern_block_for_enum(en: &EnumDef) -> String {
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
block.push_str(&crate::backends::swift::template_env::render(
"extern_enum_type.jinja",
minijinja::context! {
name => &en.name,
},
));
block.push_str(" fn to_string(&self) -> String;\n");
block.push_str(" }\n\n");
block
}
pub(crate) fn emit_extern_block_for_type_methods(
ty: &TypeDef,
handle_returned_types: &std::collections::HashSet<String>,
enum_names: &std::collections::HashSet<&str>,
) -> Option<String> {
let bridgeable: Vec<_> = ty.methods.iter().filter(|m| !m.sanitized && !m.is_static).collect();
if bridgeable.is_empty() {
return None;
}
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
for method in &bridgeable {
let type_snake = ty.name.to_snake_case();
let method_snake = method.name.to_snake_case();
let fn_name = format!("{type_snake}_{method_snake}");
let swift_name = swift_ident(&fn_name.to_lower_camel_case());
let client_receiver = if matches!(method.receiver, Some(crate::core::ir::ReceiverKind::RefMut)) {
format!("client: &mut {}", ty.name)
} else {
format!("client: &{}", ty.name)
};
let mut params: Vec<String> = vec![client_receiver];
for p in &method.params {
let bridge_ty = bridge_type_enum_aware_ref(&p.ty, enum_names);
let bridge_ty = if p.optional && !needs_json_bridge(&p.ty) {
format!("Option<{bridge_ty}>")
} else {
bridge_ty
};
let name = swift_ident(&p.name.to_snake_case());
params.push(format!("{name}: {bridge_ty}"));
}
let params_str = params.join(", ");
let return_ty = if method.error_type.is_some() {
let ok_ty = bridge_type_with_handles(&method.return_type, handle_returned_types);
if matches!(method.return_type, TypeRef::Unit) {
"Result<(), String>".to_string()
} else {
format!("Result<{ok_ty}, String>")
}
} else {
bridge_type_with_handles(&method.return_type, handle_returned_types)
};
if swift_name != fn_name {
block.push_str(&crate::backends::swift::template_env::render(
"extern_swift_name_attr.jinja",
minijinja::context! {
swift_name => &swift_name,
},
));
}
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_decl.jinja",
minijinja::context! {
fn_name => &fn_name,
params => ¶ms_str,
return_type => &return_ty,
},
));
}
block.push_str(" }\n\n");
Some(block)
}
pub(crate) fn emit_extern_block_for_type_constructor(ty: &TypeDef) -> Option<String> {
if ty.methods.iter().all(|m| m.sanitized) {
return None;
}
let type_snake = ty.name.to_snake_case();
let fn_name = format!("create_{type_snake}");
let swift_name = swift_ident(&fn_name.to_lower_camel_case());
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
if swift_name != fn_name {
block.push_str(&crate::backends::swift::template_env::render(
"extern_swift_name_attr.jinja",
minijinja::context! {
swift_name => &swift_name,
},
));
}
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_decl.jinja",
minijinja::context! {
fn_name => &fn_name,
params => "api_key: String, base_url: Option<String>",
return_type => format!("Result<{}, String>", ty.name),
},
));
block.push_str(" }\n\n");
Some(block)
}
pub(crate) fn emit_extern_block_for_functions(
functions: &[FunctionDef],
handle_returned_types: &HashSet<String>,
enum_names: &HashSet<String>,
) -> String {
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
for f in functions {
let fn_name = swift_ident(&f.name.to_snake_case());
let params: Vec<String> = f
.params
.iter()
.map(|p| {
let bridge_ty = bridge_type_enum_aware(&p.ty, enum_names);
let bridge_ty = if p.optional {
format!("Option<{bridge_ty}>")
} else {
bridge_ty
};
let name = swift_ident(&p.name.to_snake_case());
format!("{name}: {bridge_ty}")
})
.collect();
let params_str = params.join(", ");
let return_ty = if f.error_type.is_some() {
let ok_ty = bridge_type_with_handles(&f.return_type, handle_returned_types);
if matches!(f.return_type, TypeRef::Unit) {
"Result<(), String>".to_string()
} else {
format!("Result<{ok_ty}, String>")
}
} else {
bridge_type_with_handles(&f.return_type, handle_returned_types)
};
let swift_name = swift_ident(&f.name.to_lower_camel_case());
if swift_name != fn_name {
block.push_str(&crate::backends::swift::template_env::render(
"extern_swift_name_attr.jinja",
minijinja::context! {
swift_name => &swift_name,
},
));
}
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_decl.jinja",
minijinja::context! {
fn_name => &fn_name,
params => ¶ms_str,
return_type => &return_ty,
},
));
}
block.push_str(" }\n\n");
block
}
pub(crate) fn emit_extern_block_for_vec_accessors(visible_types: &[&TypeDef], visible_enums: &[&EnumDef]) -> String {
if visible_types.is_empty() && visible_enums.is_empty() {
return String::new();
}
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
block.push_str(" // Phantom Vec<T> functions: swift-bridge-build must emit the full Vec support\n");
block.push_str(
" // C ABI symbols (__swift_bridge__$Vec_T$new, drop, push, pop, get, get_mut, as_ptr, len)\n",
);
block.push_str(" // which the auto-generated Swift Vec<T> conformances reference.\n");
block.push_str(" //\n");
block.push_str(" // swift-bridge 0.1.59 only emits these when Vec<T> appears as a return type\n");
block.push_str(" // in an extern block. Without these phantom functions, Swift linker fails when\n");
block.push_str(" // trying to construct or manipulate Vec<T> of opaque types.\n");
block.push_str(" //\n");
block.push_str(" // These declarations are paired with phantom_impl functions below the bridge module.\n");
for ty in visible_types {
let type_snake = ty.name.to_snake_case();
block.push_str(&crate::backends::swift::template_env::render(
"rust_phantom_vec_decl.rs.jinja",
minijinja::context! {
type_snake => &type_snake,
type_name => &ty.name,
},
));
}
for en in visible_enums {
let enum_snake = en.name.to_snake_case();
block.push_str(&crate::backends::swift::template_env::render(
"rust_phantom_vec_decl.rs.jinja",
minijinja::context! {
type_snake => &enum_snake,
type_name => &en.name,
},
));
}
block.push_str(" }\n\n");
block
}
pub(crate) fn emit_phantom_vec_impl(visible_types: &[&TypeDef], visible_enums: &[&EnumDef]) -> String {
if visible_types.is_empty() && visible_enums.is_empty() {
return String::new();
}
let mut out = String::new();
for ty in visible_types {
let type_snake = ty.name.to_snake_case();
out.push_str(&crate::backends::swift::template_env::render(
"rust_phantom_vec_impl.rs.jinja",
minijinja::context! {
type_snake => &type_snake,
type_name => &ty.name,
},
));
}
for en in visible_enums {
let enum_snake = en.name.to_snake_case();
out.push_str(&crate::backends::swift::template_env::render(
"rust_phantom_vec_impl.rs.jinja",
minijinja::context! {
type_snake => &enum_snake,
type_name => &en.name,
},
));
}
out
}
pub(crate) fn emit_extern_block_for_streaming_adapters(adapters: &[AdapterConfig]) -> Option<String> {
use crate::core::config::AdapterPattern;
let streaming: Vec<&AdapterConfig> = adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
.filter(|a| a.owner_type.is_some())
.collect();
if streaming.is_empty() {
return None;
}
let mut block = String::new();
block.push_str(" extern \"Rust\" {\n");
for adapter in &streaming {
let owner_type = adapter.owner_type.as_deref().unwrap_or("");
let owner_pascal = owner_type.to_pascal_case();
let adapter_pascal = adapter.name.to_pascal_case();
let handle_name = format!("{owner_pascal}{adapter_pascal}StreamHandle");
block.push_str(&crate::backends::swift::template_env::render(
"extern_type_decl.jinja",
minijinja::context! {
name => &handle_name,
},
));
}
block.push('\n');
for adapter in &streaming {
let owner_type = adapter.owner_type.as_deref().unwrap_or("");
let owner_snake = owner_type.to_snake_case();
let owner_pascal = owner_type.to_pascal_case();
let adapter_pascal = adapter.name.to_pascal_case();
let handle_name = format!("{owner_pascal}{adapter_pascal}StreamHandle");
let fn_start = format!("{owner_snake}_{}_start", adapter.name);
let swift_start = swift_ident(&fn_start.to_lower_camel_case());
let mut start_params: Vec<String> = vec![format!("client: &{owner_type}")];
for p in &adapter.params {
let simple_ty = p.ty.rsplit("::").next().unwrap_or(&p.ty);
let param_name = swift_ident(&p.name.to_snake_case());
start_params.push(format!("{param_name}: &{simple_ty}"));
}
let start_params_str = start_params.join(", ");
if swift_start != fn_start {
block.push_str(&crate::backends::swift::template_env::render(
"extern_swift_name_attr.jinja",
minijinja::context! { swift_name => &swift_start },
));
}
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_decl.jinja",
minijinja::context! {
fn_name => &fn_start,
params => &start_params_str,
return_type => format!("Result<{handle_name}, String>"),
},
));
block.push_str(&crate::backends::swift::template_env::render(
"extern_fn_decl.jinja",
minijinja::context! {
fn_name => "next",
params => format!("self: &mut {handle_name}"),
return_type => "Result<String, String>",
},
));
}
block.push_str(" }\n\n");
Some(block)
}