1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
//! Emits the swift-bridge wrapper newtype structs for IR struct types.
//!
//! `emit_type_wrapper` produces:
//! - `pub struct T(pub SourceT)` newtype
//! - `impl T { pub fn new(…) → T }` constructor
//! - `impl T { pub fn field(&self) → BridgeType }` getters
//!
//! Enum wrappers live in `enums.rs`.
use crate::backends::swift::gen_rust_crate::type_bridge::{
bridge_type_enum_aware_ref, needs_json_bridge, needs_json_bridge_with_handles, swift_bridge_rust_type,
};
use crate::core::ir::{ReceiverKind, TypeDef, TypeRef};
use crate::core::keywords::swift_ident;
use heck::ToSnakeCase;
use std::collections::{HashMap, HashSet};
/// Emit free function shims for each method on `ty`.
///
/// Each method `fn method_name(&self, param: T) -> Result<R, E>` becomes
/// `pub fn type_name_method_name(client: &TypeName, param: BridgeT) -> Result<BridgeR, String>`.
/// Async methods are blocked on a Tokio current-thread runtime (same pattern as function shims).
pub(crate) fn emit_type_method_shims(
ty: &TypeDef,
_source_crate: &str,
type_paths: &HashMap<String, String>,
handle_returned_types: &std::collections::HashSet<String>,
unit_enum_names: &HashSet<&str>,
) -> String {
let type_snake = ty.name.to_snake_case();
let type_name = &ty.name;
let mut out = String::new();
// Bring trait providers into scope so trait methods on `client.0` resolve.
// Methods from inherent impls have `trait_source: None`; methods from trait
// impls record the fully qualified trait path.
// Without these `use` statements rustc emits `no method named X found` for every
// trait-provided method.
let mut trait_uses: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for method in &ty.methods {
if method.sanitized {
continue;
}
if let Some(path) = method.trait_source.as_deref() {
trait_uses.insert(path.to_string());
}
}
for path in &trait_uses {
out.push_str(&crate::backends::swift::template_env::render(
"rust_trait_use.rs.jinja",
minijinja::context! {
path => path,
},
));
}
if !trait_uses.is_empty() {
out.push('\n');
}
for method in &ty.methods {
if method.sanitized {
continue;
}
// Skip static/associated functions: the shim is `pub fn type_method(client: &T)`
// and the body uses `client.0.method()`. Static methods like `T::default()`
// need to be called as associated functions (`T::default()`), not via the
// receiver — calling `client.0.default()` trips E0599. We skip them rather
// than emitting a separate constructor surface; static constructors are
// exposed via `create_<T>` shims when an explicit client_constructor_body is
// configured, not via method shims.
if method.is_static {
continue;
}
let method_snake = method.name.to_snake_case();
let fn_name = format!("{type_snake}_{method_snake}");
// Build param list: first param is `client: &TypeName` (or `&mut` for
// RefMut receivers so the inner `client.0.method()` borrow compiles), then
// method params.
let client_receiver = if matches!(method.receiver, Some(ReceiverKind::RefMut)) {
format!("client: &mut {type_name}")
} else {
format!("client: &{type_name}")
};
let mut params_vec: Vec<String> = vec![client_receiver];
for p in &method.params {
let bridge_ty = bridge_type_enum_aware_ref(&p.ty, unit_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_vec.push(format!("{name}: {bridge_ty}"));
}
let params_str = params_vec.join(", ");
let return_ty = if method.error_type.is_some() {
let ok_ty = crate::backends::swift::gen_rust_crate::type_bridge::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 {
crate::backends::swift::gen_rust_crate::type_bridge::bridge_type_with_handles(
&method.return_type,
handle_returned_types,
)
};
// Build call args for each method param (excluding the receiver).
//
// - Enum → deserialize from String (bridged as String)
// - Named newtype → `arg.0` (unwrap to inner source-crate type)
// - Optional<Named> → `arg.map(|v| v.0)` (preserve None, unwrap Some) [for structs only]
// - String → `&arg` (the underlying trait method usually takes `&str`)
// - Path → `PathBuf::from(arg)` (bridge delivers String; core takes PathBuf)
// - Bytes+is_ref → `&arg` (bridge delivers Vec<u8>; core takes &[u8])
// - Vec<String>+is_ref → `&arg.iter().map(|s| s.as_str()).collect::<Vec<_>>()` (→ &[&str])
// - Named+is_ref → `&arg.0` (borrow the unwrapped inner value) [for structs only]
// - JSON-bridged → deserialize from the bridge String
// - Other primitives → pass through verbatim
let call_args: Vec<String> = method
.params
.iter()
.map(|p| {
let name = p.name.to_snake_case();
// Special case: TypeRef::Json params are bridged as String but the
// core method expects serde_json::Value. Convert here.
if matches!(&p.ty, TypeRef::Json) {
return format!(
"serde_json::from_str::<serde_json::Value>(&{name}).unwrap_or(serde_json::Value::Null)"
);
}
// Enum parameters: bridged as plain wire strings (e.g. "person"), NOT as
// JSON-encoded strings (e.g. "\"person\""). serde_json::from_str fails on
// unquoted strings; use `From<String>` instead which every alef unit enum
// implements. Vec<EnumType> params with is_ref=true must also be converted
// element-wise and then sliced to &[T].
if let TypeRef::Vec(vec_inner) = &p.ty {
if let TypeRef::Named(n) = vec_inner.as_ref() {
if unit_enum_names.contains(n.as_str()) {
let source_enum_ty = type_paths
.get(n.as_str())
.map(|p| p.replace('-', "_"))
.unwrap_or_else(|| n.clone());
let map_expr = format!(
concat!(
"{name}.into_iter().map(|s| ",
"<{source_enum_ty} as ::std::convert::From<String>>::from(s))",
".collect::<Vec<_>>()"
),
name = name,
source_enum_ty = source_enum_ty,
);
if p.is_ref {
// Core expects `&[EnumType]`; coerce `&Vec<T>` to `&[T]`. The
// temporary Vec lives for the enclosing call statement.
return format!("&{map_expr}");
}
if p.optional {
let opt_map = format!(
concat!(
"{name}.map(|values| values.into_iter().map(|s| ",
"<{source_enum_ty} as ::std::convert::From<String>>::from(s))",
".collect::<Vec<_>>())"
),
name = name,
source_enum_ty = source_enum_ty,
);
return opt_map;
}
return map_expr;
}
}
}
if let TypeRef::Named(n) = &p.ty {
if unit_enum_names.contains(n.as_str()) {
// Single enum parameter: swift delivers a plain wire string.
let source_enum_ty = type_paths
.get(n.as_str())
.map(|p| p.replace('-', "_"))
.unwrap_or_else(|| n.clone());
let from_expr = format!("<{source_enum_ty} as ::std::convert::From<String>>::from({name})");
if p.optional {
return format!(
"{name}.map(|s| <{source_enum_ty} as ::std::convert::From<String>>::from(s))"
);
}
if p.is_ref {
// Core takes `&EnumType`; borrow the converted value (lifetime
// covers the surrounding call expression).
return format!("&{from_expr}");
}
return from_expr;
}
}
if needs_json_bridge(&p.ty) {
let native_ty = swift_bridge_rust_type(&p.ty);
return format!("serde_json::from_str::<{native_ty}>(&{name}).expect(\"valid JSON for {name}\")");
}
if p.optional {
if let TypeRef::Named(n) = &p.ty {
// Skip .0 access for enums (they're already JSON-deserialized above).
// For struct wrappers, unwrap to inner type via .0.
if !unit_enum_names.contains(n.as_str()) {
return format!("{name}.map(|v| v.0)");
}
}
}
match &p.ty {
TypeRef::Named(n) if p.is_ref && !unit_enum_names.contains(n.as_str()) => format!("&{name}.0"),
TypeRef::Named(n) if p.is_ref && unit_enum_names.contains(n.as_str()) => format!("&{name}"),
TypeRef::Named(n) if !unit_enum_names.contains(n.as_str()) => format!("{name}.0"),
TypeRef::Named(n) if unit_enum_names.contains(n.as_str()) => name,
TypeRef::String => format!("&{name}"),
TypeRef::Path => format!("::std::path::PathBuf::from({name})"),
TypeRef::Bytes if p.is_ref => format!("&{name}"),
TypeRef::Vec(_)
if p.is_ref
&& p.vec_inner_is_ref
&& matches!(&p.ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String)) =>
{
// Core takes `&[&str]`; swift-bridge delivers `Vec<String>`.
// Borrow the temporary Vec<&str> into &[&str].
format!("&{name}.iter().map(|s| s.as_str()).collect::<Vec<_>>()")
}
TypeRef::Vec(_) if p.is_ref => {
// Core takes `&[T]`; swift-bridge delivers `Vec<T>`.
// Coerce `&Vec<T>` to `&[T]`.
format!("&{name}")
}
_ => name,
}
})
.collect();
let call_args_str = call_args.join(", ");
// Resolve the method call on the inner type.
// S5: when the inner method takes owned `self` (e.g. builder `build(self)`),
// we must clone because `client` is `&TypeName` — swift-bridge opaque types
// cannot be owned in the extern "Rust" declaration. Clone is cheap for builders
// (they hold plain config fields, no heap-heavy state).
let is_owned_receiver = matches!(method.receiver.as_ref(), Some(ReceiverKind::Owned));
let inner_access = if is_owned_receiver {
"client.0.clone()"
} else {
"client.0"
};
let method_call = format!("{inner_access}.{method_snake}({call_args_str})");
// Determine return wrapping: Named return types get wrapped in their newtype.
// Use the handle-aware variant so that Option<Named(T)> where T is an opaque
// handle type does NOT collapse to JSON String — the handle is returned directly
// as a swift-bridge opaque and requires no serde::Serialize impl.
let json_wrap_ok = needs_json_bridge_with_handles(&method.return_type, handle_returned_types);
let wrap_return = |source: String| -> String {
if json_wrap_ok {
return format!("serde_json::to_string(&({source})).expect(\"serializable return\")");
}
match &method.return_type {
TypeRef::Named(t) => format!("{t}({source})"),
TypeRef::Optional(inner) => {
if let TypeRef::Named(t) = inner.as_ref() {
format!("({source}).map({t})")
} else {
source
}
}
TypeRef::String => format!("{source}.to_string()"),
TypeRef::Path => format!("{source}.to_string_lossy().into_owned()"),
// Trait methods that return `&[&str]` (Vec<String> + returns_ref)
// can't be bridged to swift's `Vec<String>` without copying each
// element to owned `String`.
TypeRef::Vec(inner) if method.returns_ref && matches!(inner.as_ref(), TypeRef::String) => {
format!("{source}.iter().map(|s| s.to_string()).collect()")
}
_ => source,
}
};
let body = if method.is_async {
let chain = if method.error_type.is_some() {
let ok_wrap = if json_wrap_ok {
".map(|v| serde_json::to_string(&v).expect(\"serializable return\"))".to_string()
} else {
match &method.return_type {
TypeRef::Named(t) => format!(".map({t})"),
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
// Vec<Named> → Vec<Wrapper> via .map(|v| Wrapper(v))
if let TypeRef::Named(t) = inner.as_ref() {
format!(".map(|vec| vec.into_iter().map({t}).collect())")
} else {
String::new()
}
}
TypeRef::String | TypeRef::Path => ".map(|s| s.to_string())".to_string(),
// `bytes::Bytes` is bridged as `Vec<u8>` in the swift-bridge surface.
// The trait method returns `Bytes`; convert via `.to_vec()`.
TypeRef::Bytes => ".map(|b| b.to_vec())".to_string(),
_ => String::new(),
}
};
format!("{method_call}.await.map_err(|e| e.to_string()){ok_wrap}")
} else {
wrap_return(format!("{method_call}.await"))
};
// Share the process-wide tokio runtime — see shims.rs for the
// rationale (orphaned reqwest connection pools).
format!(" crate::__alef_tokio_runtime().block_on(async {{ {chain} }})")
} else if method.error_type.is_some() {
let ok_wrap = if json_wrap_ok {
".map(|v| serde_json::to_string(&v).expect(\"serializable return\"))".to_string()
} else {
match &method.return_type {
TypeRef::Named(t) => format!(".map({t})"),
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
// Vec<Named> → Vec<Wrapper> via .map(|v| Wrapper(v))
if let TypeRef::Named(t) = inner.as_ref() {
format!(".map(|vec| vec.into_iter().map({t}).collect())")
} else {
String::new()
}
}
TypeRef::String | TypeRef::Path => ".map(|s| s.to_string())".to_string(),
TypeRef::Bytes => ".map(|b| b.to_vec())".to_string(),
_ => String::new(),
}
};
format!(" {method_call}.map_err(|e| e.to_string()){ok_wrap}")
} else {
format!(" {}", wrap_return(method_call))
};
let return_clause = if return_ty == "()" {
String::new()
} else {
format!(" -> {return_ty}")
};
out.push_str(&crate::backends::swift::template_env::render(
"rust_wrapper_free_fn.rs.jinja",
minijinja::context! {
fn_name => fn_name,
params => params_str,
return_clause => return_clause,
body => body,
},
));
}
out
}