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
use crate::core::ir::{MethodDef, ReceiverKind, TypeRef};
use super::excluded::{excluded_carrier_name, excluded_type_core_path};
use crate::backends::dart::gen_rust_crate::trait_types::{
trait_impl_param_conversion, trait_impl_param_type, trait_impl_return_conversion, trait_impl_return_type,
};
/// Emit one method implementation on the bridge struct.
///
/// The method signature must match the **original** trait signature (ref-aware,
/// original primitive widths). The closures stored in the struct hold
/// FRB-friendly widened types (e.g. `i64` for `u64`, `f64` for `f32`). The
/// impl body converts between the two representations.
///
/// For methods with an `error_type`, the return type is
/// `{source_crate}::Result<T>` — the Dart callback never fails, so the body
/// wraps the awaited value in `Ok(...)`.
pub(super) fn emit_trait_bridge_method(
out: &mut String,
method: &MethodDef,
source_crate_name: &str,
type_paths: &std::collections::HashMap<String, String>,
excluded_type_paths: &std::collections::HashMap<String, String>,
lifetime_type_names: &std::collections::HashSet<String>,
) {
let method_name = &method.name;
// Build the method signature matching the actual trait.
// - Reference params use `&` / `&mut` prefix.
// - Primitive params use their original width (not FRB-widened).
// Emit the self receiver matching the trait definition so rustc's E0053
// ("method has an incompatible type for trait") is not triggered for
// traits that use `&mut self` (e.g. `HtmlVisitor`).
let self_receiver = match method.receiver {
Some(ReceiverKind::RefMut) => "&mut self",
Some(ReceiverKind::Owned) => "self",
// Default: `&self` (covers `Some(ReceiverKind::Ref)` and `None`).
_ => "&self",
};
let params_sig: Vec<String> = std::iter::once(self_receiver.to_string())
.chain(method.params.iter().map(|p| {
let orig_ty = trait_impl_param_type(p, source_crate_name, type_paths, lifetime_type_names);
format!("{}: {orig_ty}", p.name)
}))
.collect();
// Detect the `&[&str]` (Vec<String> + returns_ref) special case — the trait method
// expects a borrowed static slice but the Dart-side closure produces owned
// `Vec<String>`. We materialise that into `&'static [&'static str]` via Box::leak
// (same pattern as the napi/pyo3 trait-bridges, see
// `alef-codegen::trait_bridge::gen_method`). The owned vector is leaked once per
// method invocation: acceptable for plugin metadata that's typically read at
// registration time.
let is_ref_slice_of_str = method.returns_ref
&& matches!(
&method.return_type,
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String)
);
// Return type: use original primitive/named type; wrap in source-crate Result when error_type set.
let ret = if is_ref_slice_of_str {
"&[&str]".to_string()
} else {
trait_impl_return_type(&method.return_type, source_crate_name, type_paths)
};
let return_sig = if method.error_type.is_some() {
if matches!(method.return_type, TypeRef::Unit) {
format!("{source_crate_name}::Result<()>")
} else {
format!("{source_crate_name}::Result<{ret}>")
}
} else {
ret.clone()
};
let async_kw = if method.is_async { "async " } else { "" };
out.push_str(&crate::backends::dart::template_env::render(
"rust_method_signature.jinja",
minijinja::context! {
async_kw => async_kw,
method_name => method_name.as_str(),
params => params_sig.join(", "),
return_sig => return_sig.as_str(),
},
));
// Emit owned-conversion let-bindings for each parameter before calling the closure.
// References become owned; primitives may be widened; mut refs are copied for the callback.
for p in &method.params {
let conv = trait_impl_param_conversion(p, excluded_type_paths);
if !conv.is_empty() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_param_conversion.jinja",
minijinja::context! {
conversion => conv,
},
));
}
}
// Build call-site arg list (use the local owned var names).
//
// For params whose original type was excluded from public bindings, the Dart-facing
// closure receives an opaque JSON carrier. The Rust trait method itself still
// receives the source-crate type, so serialize at the bridge edge explicitly.
let mut pre_bindings = String::new();
let call_args: Vec<String> = method
.params
.iter()
.map(|p| {
let carrier_type = match &p.ty {
TypeRef::Named(name) if excluded_type_paths.contains_key(name) => Some(excluded_carrier_name(name)),
_ => None,
};
if let Some(carrier_type) = carrier_type {
let local = format!("__{}_local", p.name);
let expr = if p.optional {
if method.error_type.is_some() {
format!(
"{name}.map(|v| serde_json::to_string(&v).map(|json| {carrier_type} {{ json }})).transpose()?",
name = p.name,
carrier_type = carrier_type,
)
} else {
format!(
"{name}.map(|v| {carrier_type} {{ json: serde_json::to_string(&v).expect(\"serialize excluded Dart trait bridge value\") }})",
name = p.name,
carrier_type = carrier_type,
)
}
} else if method.error_type.is_some() {
format!(
"{carrier_type} {{ json: serde_json::to_string(&{name})? }}",
name = p.name,
carrier_type = carrier_type,
)
} else {
format!(
"{carrier_type} {{ json: serde_json::to_string(&{name}).expect(\"serialize excluded Dart trait bridge value\") }}",
name = p.name,
carrier_type = carrier_type,
)
};
let _ = std::fmt::Write::write_fmt(
&mut pre_bindings,
format_args!(" let {local} = {expr};\n", local = local, expr = expr),
);
local
} else {
p.name.clone()
}
})
.collect();
if !pre_bindings.is_empty() {
out.push_str(&pre_bindings);
}
let call_expr = format!("(self.{method_name})({})", call_args.join(", "));
// Emit the body, adapting the return value from FRB-widened to original type.
let ret_conv = trait_impl_return_conversion(&method.return_type, source_crate_name);
// Special case: Named return type — the mirror type cannot be trivially converted
// back to the core type. Drop the result and return Default::default().
let named_return_default = ret_conv == "__NAMED_RETURN_DEFAULT__";
// Special case: the return type was excluded from public bindings, substituted
// to a JSON-backed carrier in the closure signature. Deserialize explicitly
// to the source trait's exact return type.
let excluded_return_name = match &method.return_type {
TypeRef::Named(name) if excluded_type_paths.contains_key(name) => Some(name.as_str()),
_ => None,
};
if let Some(excluded_return_name) = excluded_return_name {
let core_path = excluded_type_core_path(excluded_return_name, source_crate_name, excluded_type_paths);
let carrier_type = excluded_carrier_name(excluded_return_name);
if method.is_async {
if method.error_type.is_some() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_excluded_async_result_return.rs.jinja",
minijinja::context! {
carrier_type => carrier_type.as_str(),
call_expr => call_expr.as_str(),
core_path => core_path.as_str(),
},
));
} else {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_excluded_async_plain_return.rs.jinja",
minijinja::context! {
carrier_type => carrier_type.as_str(),
call_expr => call_expr.as_str(),
core_path => core_path.as_str(),
},
));
}
} else {
out.push_str(" let __ret_bridge = ::tokio::runtime::Builder::new_current_thread()\n .build()\n .expect(\"build alef visitor tokio runtime\")\n");
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_block_on.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
},
));
if method.error_type.is_some() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_excluded_block_on_result_return.rs.jinja",
minijinja::context! {
core_path => core_path.as_str(),
},
));
} else {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_excluded_block_on_plain_return.rs.jinja",
minijinja::context! {
core_path => core_path.as_str(),
},
));
}
}
if method.error_type.is_some() {
out.push_str(" Ok(__ret)\n");
} else {
out.push_str(" __ret\n");
}
out.push_str(" }\n");
return;
}
if method.error_type.is_some() {
// DartFnFuture never fails: wrap the awaited value in Ok(...).
if method.is_async {
if named_return_default {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_default_await.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
return_expr => "Ok(Default::default())",
},
));
} else if ret_conv.is_empty() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_ok_await.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
},
));
} else {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_await_result.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
ret_conv => ret_conv.as_str(),
},
));
}
} else {
// FRB workers don't have a tokio runtime installed; `Handle::current()` would
// panic. Build a fresh current-thread runtime per call to drive the DartFnFuture
// — overhead is acceptable since visitor callbacks already cross an FFI boundary
// and the runtime is cheap to construct (no I/O drivers needed).
out.push_str(" let __result = ::tokio::runtime::Builder::new_current_thread()\n .build()\n .expect(\"build alef visitor tokio runtime\")\n");
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_block_on.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
},
));
if named_return_default {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_default_from_result.jinja",
minijinja::context! {
return_expr => "Ok(Default::default())",
},
));
} else {
// error_type present: the Dart callback never fails, so wrap in Ok(...).
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_ok_block_on.jinja",
minijinja::context! {
ret_conv => ret_conv.as_str(),
},
));
}
}
} else if method.is_async {
if named_return_default {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_default_await.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
return_expr => "Default::default()",
},
));
} else if ret_conv.is_empty() {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_await_plain.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
},
));
} else {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_await_result.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
ret_conv => ret_conv.as_str(),
},
));
}
} else {
// FRB workers don't have a tokio runtime installed; `Handle::current()` would
// panic. Build a fresh current-thread runtime per call to drive the DartFnFuture
// — overhead is acceptable since visitor callbacks already cross an FFI boundary
// and the runtime is cheap to construct (no I/O drivers needed).
out.push_str(" let __result = ::tokio::runtime::Builder::new_current_thread()\n .build()\n .expect(\"build alef visitor tokio runtime\")\n");
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_block_on.jinja",
minijinja::context! {
call_expr => call_expr.as_str(),
},
));
if named_return_default {
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_default_from_result.jinja",
minijinja::context! {
return_expr => "Default::default()",
},
));
} else if is_ref_slice_of_str {
// Materialise `Vec<String>` into `&'static [&'static str]` so the trait
// method's `&[&str]` return type is satisfied. Each closure invocation
// leaks its strings — acceptable for plugin-metadata callsites.
out.push_str(
" ;\n \
let __strs: Vec<&'static str> = __result\n \
.into_iter()\n \
.map(|s| -> &'static str { Box::leak(s.into_boxed_str()) })\n \
.collect();\n \
Box::leak(__strs.into_boxed_slice())\n",
);
} else {
// No error_type: return the plain value (no Ok() wrapping).
out.push_str(&crate::backends::dart::template_env::render(
"rust_trait_method_plain_block_on_result.jinja",
minijinja::context! {
ret_conv => ret_conv.as_str(),
},
));
}
}
out.push_str(" }\n");
}