alef_codegen/generators/binding_helpers.rs
1use crate::generators::{AsyncPattern, RustBindingConfig};
2use ahash::AHashSet;
3use alef_core::ir::{CoreWrapper, ParamDef, TypeDef, TypeRef};
4use std::fmt::Write;
5
6/// Helper: wrap an opaque inner value in the correct smart pointer expression.
7///
8/// - Plain opaque types use `Arc::new(val)`.
9/// - Mutex-wrapped opaque types use `Arc::new(std::sync::Mutex::new(val))`.
10fn arc_wrap(val: &str, name: &str, mutex_types: &AHashSet<String>) -> String {
11 if mutex_types.contains(name) {
12 format!("Arc::new(std::sync::Mutex::new({val}))")
13 } else {
14 format!("Arc::new({val})")
15 }
16}
17
18/// Wrap a core-call result for opaque delegation methods.
19///
20/// - `TypeRef::Named(n)` where `n == type_name` → re-wrap in `Self { inner: Arc::new(...) }`
21/// - `TypeRef::Named(n)` where `n` is another opaque type → wrap in `{n} { inner: Arc::new(...) }`
22/// - `TypeRef::Named(n)` where `n` is a non-opaque type → `todo!()` placeholder (From may not exist)
23/// - Everything else (primitives, String, Vec, etc.) → pass through unchanged
24/// - `TypeRef::Unit` → pass through unchanged
25///
26/// When `returns_cow` is true the core method returns `Cow<'_, T>`. `.into_owned()` is emitted
27/// before any further type conversion to obtain an owned `T`.
28///
29/// `mutex_types` identifies opaque types that use `Arc<Mutex<T>>` instead of `Arc<T>`, so
30/// constructor expressions use `Arc::new(Mutex::new(...))` where needed.
31#[allow(clippy::too_many_arguments)]
32pub fn wrap_return_with_mutex(
33 expr: &str,
34 return_type: &TypeRef,
35 type_name: &str,
36 opaque_types: &AHashSet<String>,
37 mutex_types: &AHashSet<String>,
38 self_is_opaque: bool,
39 returns_ref: bool,
40 returns_cow: bool,
41) -> String {
42 let self_arc = arc_wrap("", type_name, mutex_types); // used for pattern matching only
43 let _ = self_arc; // just to reference mutex_types in context
44 match return_type {
45 TypeRef::Named(n) if n == type_name && self_is_opaque => {
46 let inner = if returns_cow {
47 format!("{expr}.into_owned()")
48 } else if returns_ref {
49 format!("{expr}.clone()")
50 } else {
51 expr.to_string()
52 };
53 format!("Self {{ inner: {} }}", arc_wrap(&inner, type_name, mutex_types))
54 }
55 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
56 let inner = if returns_cow {
57 format!("{expr}.into_owned()")
58 } else if returns_ref {
59 format!("{expr}.clone()")
60 } else {
61 expr.to_string()
62 };
63 format!("{n} {{ inner: {} }}", arc_wrap(&inner, n, mutex_types))
64 }
65 TypeRef::Named(_) => {
66 // Non-opaque Named return type — use .into() for core→binding From conversion.
67 // When the core returns a Cow, call .into_owned() first to get an owned T.
68 // When the core returns a reference, clone first since From<&T> typically doesn't exist.
69 // NOTE: If this type was sanitized to String in the binding, From won't exist.
70 // The calling backend should check method.sanitized before delegating.
71 // This code assumes non-sanitized Named types have From impls.
72 if returns_cow {
73 format!("{expr}.into_owned().into()")
74 } else if returns_ref {
75 format!("{expr}.clone().into()")
76 } else {
77 format!("{expr}.into()")
78 }
79 }
80 // String/Bytes: only convert when the core returns a reference (&str→String, &[u8]→Vec<u8>).
81 // When owned (returns_ref=false), both sides are already String/Vec<u8> — skip .into().
82 TypeRef::String | TypeRef::Bytes => {
83 if returns_ref {
84 format!("{expr}.into()")
85 } else {
86 expr.to_string()
87 }
88 }
89 // Path: PathBuf→String needs to_string_lossy, &Path→String too
90 TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
91 // Duration: core returns std::time::Duration, binding uses u64 (millis)
92 TypeRef::Duration => format!("{expr}.as_millis() as u64"),
93 // Json: serde_json::Value needs serialization to string
94 TypeRef::Json => format!("{expr}.to_string()"),
95 // Optional: wrap inner conversion in .map(...)
96 TypeRef::Optional(inner) => match inner.as_ref() {
97 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
98 let wrap = arc_wrap("v", n, mutex_types);
99 if returns_ref {
100 format!(
101 "{expr}.map(|v| {n} {{ inner: {} }})",
102 arc_wrap("v.clone()", n, mutex_types)
103 )
104 } else {
105 format!("{expr}.map(|v| {n} {{ inner: {wrap} }})")
106 }
107 }
108 TypeRef::Named(_) => {
109 if returns_ref {
110 format!("{expr}.map(|v| v.clone().into())")
111 } else {
112 format!("{expr}.map(Into::into)")
113 }
114 }
115 TypeRef::Path => {
116 format!("{expr}.map(Into::into)")
117 }
118 TypeRef::String | TypeRef::Bytes => {
119 if returns_ref {
120 format!("{expr}.map(Into::into)")
121 } else {
122 expr.to_string()
123 }
124 }
125 TypeRef::Duration => format!("{expr}.map(|d| d.as_millis() as u64)"),
126 TypeRef::Json => format!("{expr}.map(ToString::to_string)"),
127 // Optional<Vec<Named>>: convert each element in the inner Vec
128 TypeRef::Vec(vec_inner) => match vec_inner.as_ref() {
129 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
130 if returns_ref {
131 let wrap = arc_wrap("x.clone()", n, mutex_types);
132 format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: {wrap} }}).collect())")
133 } else {
134 let wrap = arc_wrap("x", n, mutex_types);
135 format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: {wrap} }}).collect())")
136 }
137 }
138 TypeRef::Named(_) => {
139 if returns_ref {
140 format!("{expr}.map(|v| v.into_iter().map(|x| x.clone().into()).collect())")
141 } else {
142 format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
143 }
144 }
145 _ => expr.to_string(),
146 },
147 _ => expr.to_string(),
148 },
149 // Vec: map each element through the appropriate conversion
150 TypeRef::Vec(inner) => match inner.as_ref() {
151 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
152 if returns_ref {
153 let wrap = arc_wrap("v.clone()", n, mutex_types);
154 format!("{expr}.into_iter().map(|v| {n} {{ inner: {wrap} }}).collect()")
155 } else {
156 let wrap = arc_wrap("v", n, mutex_types);
157 format!("{expr}.into_iter().map(|v| {n} {{ inner: {wrap} }}).collect()")
158 }
159 }
160 TypeRef::Named(_) => {
161 if returns_ref {
162 format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
163 } else {
164 format!("{expr}.into_iter().map(Into::into).collect()")
165 }
166 }
167 TypeRef::Path => {
168 format!("{expr}.into_iter().map(Into::into).collect()")
169 }
170 TypeRef::String | TypeRef::Bytes => {
171 if returns_ref {
172 format!("{expr}.into_iter().map(Into::into).collect()")
173 } else {
174 expr.to_string()
175 }
176 }
177 _ => expr.to_string(),
178 },
179 _ => expr.to_string(),
180 }
181}
182
183/// Wrap a core-call result for opaque delegation methods.
184///
185/// This is the backward-compatible wrapper that passes an empty `mutex_types` set.
186/// Use `wrap_return_with_mutex` when the type set contains mutex-wrapped opaque types.
187pub fn wrap_return(
188 expr: &str,
189 return_type: &TypeRef,
190 type_name: &str,
191 opaque_types: &AHashSet<String>,
192 self_is_opaque: bool,
193 returns_ref: bool,
194 returns_cow: bool,
195) -> String {
196 wrap_return_with_mutex(
197 expr,
198 return_type,
199 type_name,
200 opaque_types,
201 &AHashSet::new(),
202 self_is_opaque,
203 returns_ref,
204 returns_cow,
205 )
206}
207
208/// Unwrap a newtype return value when `return_newtype_wrapper` is set.
209///
210/// Core function returns a newtype (e.g. `NodeIndex(u32)`), but the binding return type
211/// is the inner type (e.g. `u32`). Access `.0` to unwrap the newtype.
212pub fn apply_return_newtype_unwrap(expr: &str, return_newtype_wrapper: &Option<String>) -> String {
213 match return_newtype_wrapper {
214 Some(_) => format!("({expr}).0"),
215 None => expr.to_string(),
216 }
217}
218
219/// Build call argument expressions from parameters.
220/// - Opaque Named types: unwrap Arc wrapper via `(*param.inner).clone()`
221/// - Non-opaque Named types: `.into()` for From conversion
222/// - String/Path/Bytes: `¶m` since core functions typically take `&str`/`&Path`/`&[u8]`
223/// - Params with `newtype_wrapper` set: re-wrap the raw value in the newtype constructor
224/// (e.g., `NodeIndex(parent)`) since the binding resolved `NodeIndex(u32)` → `u32`.
225///
226/// NOTE: This function does not perform serde-based conversion. For Named params that lack
227/// From impls (e.g., due to sanitized fields), use `gen_serde_let_bindings` instead when
228/// `cfg.has_serde` is true, or fall back to `gen_unimplemented_body`.
229pub fn gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
230 params
231 .iter()
232 .enumerate()
233 .map(|(idx, p)| {
234 let promoted = crate::shared::is_promoted_optional(params, idx);
235 // If a required param was promoted to optional, unwrap it before use.
236 // Note: promoted params that are not Optional<T> will NOT call .expect() because
237 // promoted refers to the PyO3 signature constraint, not the actual Rust type.
238 // The function_params logic wraps promoted params in Option<T>, making them truly optional.
239 let unwrap_suffix = if promoted && p.optional {
240 format!(".expect(\"'{}' is required\")", p.name)
241 } else {
242 String::new()
243 };
244 // If this param's type was resolved from a newtype (e.g. NodeIndex(u32) → u32),
245 // re-wrap the raw value back into the newtype when calling core.
246 if let Some(newtype_path) = &p.newtype_wrapper {
247 return if p.optional {
248 format!("{}.map({newtype_path})", p.name)
249 } else if promoted {
250 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
251 } else {
252 format!("{newtype_path}({})", p.name)
253 };
254 }
255 match &p.ty {
256 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
257 // Opaque type: borrow through Arc to get &CoreType
258 if p.optional {
259 format!("{}.as_ref().map(|v| &v.inner)", p.name)
260 } else if promoted {
261 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
262 } else {
263 format!("&{}.inner", p.name)
264 }
265 }
266 TypeRef::Named(_) => {
267 if p.optional {
268 if p.is_ref {
269 // Option<T> (binding) -> Option<&CoreT>: use as_ref() only
270 // The Into conversion must happen in a let binding to avoid E0716
271 format!("{}.as_ref()", p.name)
272 } else {
273 format!("{}.map(Into::into)", p.name)
274 }
275 } else if promoted {
276 format!("{}{}.into()", p.name, unwrap_suffix)
277 } else {
278 format!("{}.into()", p.name)
279 }
280 }
281 // String → &str for core function calls when is_ref=true,
282 // or pass owned when is_ref=false (core takes String/impl Into<String>).
283 // For optional params: as_deref() when is_ref=true, pass owned when is_ref=false.
284 TypeRef::String | TypeRef::Char => {
285 if p.optional {
286 if p.is_ref {
287 format!("{}.as_deref()", p.name)
288 } else {
289 p.name.clone()
290 }
291 } else if promoted {
292 if p.is_ref {
293 format!("&{}{}", p.name, unwrap_suffix)
294 } else {
295 format!("{}{}", p.name, unwrap_suffix)
296 }
297 } else if p.is_ref {
298 format!("&{}", p.name)
299 } else {
300 p.name.clone()
301 }
302 }
303 // Path → PathBuf/&Path for core function calls
304 TypeRef::Path => {
305 if p.optional && p.is_ref {
306 format!("{}.as_deref().map(std::path::Path::new)", p.name)
307 } else if p.optional {
308 format!("{}.map(std::path::PathBuf::from)", p.name)
309 } else if promoted {
310 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
311 } else if p.is_ref {
312 format!("std::path::Path::new(&{})", p.name)
313 } else {
314 format!("std::path::PathBuf::from({})", p.name)
315 }
316 }
317 TypeRef::Bytes => {
318 if p.optional {
319 if p.is_ref {
320 format!("{}.as_deref()", p.name)
321 } else {
322 p.name.clone()
323 }
324 } else if promoted {
325 // is_ref=true: pass &Vec<u8> (core takes &[u8])
326 // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
327 if p.is_ref {
328 format!("&{}{}", p.name, unwrap_suffix)
329 } else {
330 format!("{}{}", p.name, unwrap_suffix)
331 }
332 } else {
333 // is_ref=true: pass &Vec<u8> (core takes &[u8])
334 // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
335 if p.is_ref {
336 format!("&{}", p.name)
337 } else {
338 p.name.clone()
339 }
340 }
341 }
342 // Duration: binding uses u64 (millis), core uses std::time::Duration
343 TypeRef::Duration => {
344 if p.optional {
345 format!("{}.map(std::time::Duration::from_millis)", p.name)
346 } else if promoted {
347 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
348 } else {
349 format!("std::time::Duration::from_millis({})", p.name)
350 }
351 }
352 TypeRef::Json => {
353 // JSON params: binding has String, core expects serde_json::Value
354 if p.optional {
355 format!("{}.as_ref().and_then(|s| serde_json::from_str(s).ok())", p.name)
356 } else if promoted {
357 format!("serde_json::from_str(&{}{}).unwrap_or_default()", p.name, unwrap_suffix)
358 } else {
359 format!("serde_json::from_str(&{}).unwrap_or_default()", p.name)
360 }
361 }
362 TypeRef::Vec(inner) => {
363 // Vec<Named>: convert each element via Into::into when used with let bindings
364 if matches!(inner.as_ref(), TypeRef::Named(_)) {
365 if p.optional {
366 if p.is_ref {
367 format!("{}.as_deref()", p.name)
368 } else {
369 p.name.clone()
370 }
371 } else if promoted {
372 if p.is_ref {
373 format!("&{}{}", p.name, unwrap_suffix)
374 } else {
375 format!("{}{}", p.name, unwrap_suffix)
376 }
377 } else if p.is_ref {
378 format!("&{}", p.name)
379 } else {
380 p.name.clone()
381 }
382 } else if promoted {
383 format!("{}{}", p.name, unwrap_suffix)
384 } else if p.is_mut && p.optional {
385 format!("{}.as_deref_mut()", p.name)
386 } else if p.is_mut {
387 format!("&mut {}", p.name)
388 } else if p.is_ref && p.optional {
389 format!("{}.as_deref()", p.name)
390 } else if p.is_ref {
391 format!("&{}", p.name)
392 } else {
393 p.name.clone()
394 }
395 }
396 _ => {
397 if promoted {
398 format!("{}{}", p.name, unwrap_suffix)
399 } else if p.is_mut && p.optional {
400 format!("{}.as_deref_mut()", p.name)
401 } else if p.is_mut {
402 format!("&mut {}", p.name)
403 } else if p.is_ref && p.optional {
404 // Optional ref params: use as_deref() for slice/str coercion
405 // Option<Vec<T>> -> Option<&[T]>, Option<String> -> Option<&str>
406 format!("{}.as_deref()", p.name)
407 } else if p.is_ref {
408 format!("&{}", p.name)
409 } else {
410 p.name.clone()
411 }
412 }
413 }
414 })
415 .collect::<Vec<_>>()
416 .join(", ")
417}
418
419/// Build call argument expressions using pre-bound let bindings for non-opaque Named params.
420/// Non-opaque Named params use `&{name}_core` references instead of `.into()`.
421pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
422 params
423 .iter()
424 .enumerate()
425 .map(|(idx, p)| {
426 let promoted = crate::shared::is_promoted_optional(params, idx);
427 let unwrap_suffix = if promoted {
428 format!(".expect(\"'{}' is required\")", p.name)
429 } else {
430 String::new()
431 };
432 // If this param's type was resolved from a newtype, re-wrap when calling core.
433 if let Some(newtype_path) = &p.newtype_wrapper {
434 return if p.optional {
435 format!("{}.map({newtype_path})", p.name)
436 } else if promoted {
437 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
438 } else {
439 format!("{newtype_path}({})", p.name)
440 };
441 }
442 match &p.ty {
443 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
444 if p.optional {
445 format!("{}.as_ref().map(|v| &v.inner)", p.name)
446 } else if promoted {
447 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
448 } else {
449 format!("&{}.inner", p.name)
450 }
451 }
452 TypeRef::Named(_) => {
453 if p.optional && p.is_ref {
454 // Let binding already created Option<&T> via .as_ref()
455 format!("{}_core", p.name)
456 } else if p.is_ref {
457 // Let binding created T, need reference for call
458 format!("&{}_core", p.name)
459 } else {
460 format!("{}_core", p.name)
461 }
462 }
463 TypeRef::String | TypeRef::Char => {
464 if p.optional {
465 if p.is_ref {
466 format!("{}.as_deref()", p.name)
467 } else {
468 p.name.clone()
469 }
470 } else if promoted {
471 if p.is_ref {
472 format!("&{}{}", p.name, unwrap_suffix)
473 } else {
474 format!("{}{}", p.name, unwrap_suffix)
475 }
476 } else if p.is_ref {
477 format!("&{}", p.name)
478 } else {
479 p.name.clone()
480 }
481 }
482 TypeRef::Path => {
483 if promoted {
484 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
485 } else if p.optional && p.is_ref {
486 format!("{}.as_deref().map(std::path::Path::new)", p.name)
487 } else if p.optional {
488 format!("{}.map(std::path::PathBuf::from)", p.name)
489 } else if p.is_ref {
490 format!("std::path::Path::new(&{})", p.name)
491 } else {
492 format!("std::path::PathBuf::from({})", p.name)
493 }
494 }
495 TypeRef::Bytes => {
496 if p.optional {
497 if p.is_ref {
498 format!("{}.as_deref()", p.name)
499 } else {
500 p.name.clone()
501 }
502 } else if promoted {
503 // is_ref=true: pass &Vec<u8> (core takes &[u8])
504 // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
505 if p.is_ref {
506 format!("&{}{}", p.name, unwrap_suffix)
507 } else {
508 format!("{}{}", p.name, unwrap_suffix)
509 }
510 } else {
511 // is_ref=true: pass &Vec<u8> (core takes &[u8])
512 // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
513 if p.is_ref {
514 format!("&{}", p.name)
515 } else {
516 p.name.clone()
517 }
518 }
519 }
520 TypeRef::Duration => {
521 if p.optional {
522 format!("{}.map(std::time::Duration::from_millis)", p.name)
523 } else if promoted {
524 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
525 } else {
526 format!("std::time::Duration::from_millis({})", p.name)
527 }
528 }
529 TypeRef::Vec(inner) => {
530 // Sanitized Vec<tuple>: binding accepts Vec<String> (JSON-encoded tuples).
531 // Let binding created {name}_core via JSON deserialization.
532 if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
533 if p.optional && p.is_ref {
534 format!("{}_core.as_deref()", p.name)
535 } else if p.optional {
536 format!("{}_core", p.name)
537 } else if p.is_ref {
538 format!("&{}_core", p.name)
539 } else {
540 format!("{}_core", p.name)
541 }
542 } else if matches!(inner.as_ref(), TypeRef::Named(_)) {
543 // Vec<Named>: use let binding that converts each element
544 if p.optional && p.is_ref {
545 // Let binding creates Option<Vec<CoreType>>, use as_deref() to get Option<&[CoreType]>
546 format!("{}_core.as_deref()", p.name)
547 } else if p.optional {
548 // Let binding creates Option<Vec<CoreType>>, no ref needed
549 format!("{}_core", p.name)
550 } else if p.is_ref {
551 format!("&{}_core", p.name)
552 } else {
553 format!("{}_core", p.name)
554 }
555 } else if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref {
556 // Vec<String> with is_ref=true: core expects &[&str].
557 // Convert via _refs intermediate binding.
558 if p.optional {
559 format!("{}.as_deref()", p.name)
560 } else {
561 format!("&{}_refs", p.name)
562 }
563 } else if promoted {
564 format!("{}{}", p.name, unwrap_suffix)
565 } else if p.is_ref && p.optional {
566 format!("{}.as_deref()", p.name)
567 } else if p.is_ref {
568 format!("&{}", p.name)
569 } else {
570 p.name.clone()
571 }
572 }
573 _ => {
574 if promoted {
575 format!("{}{}", p.name, unwrap_suffix)
576 } else if p.is_ref && p.optional {
577 format!("{}.as_deref()", p.name)
578 } else if p.is_ref {
579 format!("&{}", p.name)
580 } else {
581 p.name.clone()
582 }
583 }
584 }
585 })
586 .collect::<Vec<_>>()
587 .join(", ")
588}
589
590/// Generate let bindings for non-opaque Named params, converting them to core types.
591pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>, core_import: &str) -> String {
592 gen_named_let_bindings(params, opaque_types, core_import)
593}
594
595/// Like `gen_named_let_bindings_pub` but without optional-promotion semantics.
596/// Use this for backends (e.g. WASM) that do not promote non-optional params to `Option<T>`.
597pub fn gen_named_let_bindings_no_promote(
598 params: &[ParamDef],
599 opaque_types: &AHashSet<String>,
600 core_import: &str,
601) -> String {
602 gen_named_let_bindings_inner(params, opaque_types, core_import, false)
603}
604
605pub(super) fn gen_named_let_bindings(
606 params: &[ParamDef],
607 opaque_types: &AHashSet<String>,
608 core_import: &str,
609) -> String {
610 gen_named_let_bindings_inner(params, opaque_types, core_import, true)
611}
612
613fn gen_named_let_bindings_inner(
614 params: &[ParamDef],
615 opaque_types: &AHashSet<String>,
616 core_import: &str,
617 promote: bool,
618) -> String {
619 let mut bindings = String::new();
620 for (idx, p) in params.iter().enumerate() {
621 match &p.ty {
622 TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
623 let promoted = promote && crate::shared::is_promoted_optional(params, idx);
624 let core_type_path = format!("{}::{}", core_import, name);
625 if p.optional {
626 if p.is_ref {
627 // Option<T> (binding) -> Option<&CoreT> (core expects reference to core type)
628 // Split into two bindings to avoid temporary value dropped while borrowed (E0716)
629 write!(
630 bindings,
631 "let {name}_owned: Option<{core_type_path}> = {name}.map(Into::into);\n let {name}_core = {name}_owned.as_ref();\n ",
632 name = p.name
633 )
634 .ok();
635 } else {
636 write!(
637 bindings,
638 "let {}_core: Option<{core_type_path}> = {}.map(Into::into);\n ",
639 p.name, p.name
640 )
641 .ok();
642 }
643 } else if promoted {
644 // Promoted-optional: unwrap then convert. Add explicit type annotation to help type inference.
645 write!(
646 bindings,
647 "let {}_core: {core_type_path} = {}.expect(\"'{}' is required\").into();\n ",
648 p.name, p.name, p.name
649 )
650 .ok();
651 } else {
652 // Non-optional: add explicit type annotation to help type inference
653 write!(
654 bindings,
655 "let {}_core: {core_type_path} = {}.into();\n ",
656 p.name, p.name
657 )
658 .ok();
659 }
660 }
661 TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if !opaque_types.contains(n.as_str())) => {
662 let promoted = promote && crate::shared::is_promoted_optional(params, idx);
663 if p.optional && p.is_ref {
664 // Option<Vec<Named>> with is_ref: convert to Option<Vec<CoreType>>, then use as_deref()
665 // This ensures elements are converted from binding to core type.
666 write!(
667 bindings,
668 "let {}_core: Option<Vec<_>> = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect());\n ",
669 p.name, p.name
670 )
671 .ok();
672 } else if p.optional {
673 // Option<Vec<Named>> without is_ref: convert to concrete Vec
674 write!(
675 bindings,
676 "let {}_core = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect()).unwrap_or_default();\n ",
677 p.name, p.name
678 )
679 .ok();
680 } else if promoted {
681 // Promoted-optional: unwrap then convert
682 write!(
683 bindings,
684 "let {}_core: Vec<_> = {}.expect(\"'{}' is required\").into_iter().map(Into::into).collect();\n ",
685 p.name, p.name, p.name
686 )
687 .ok();
688 } else if p.is_ref {
689 // Non-optional Vec<Named> with is_ref=true: generate let binding for conversion
690 write!(
691 bindings,
692 "let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n ",
693 p.name, p.name
694 )
695 .ok();
696 } else {
697 // Vec<Named>: convert each element
698 write!(
699 bindings,
700 "let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n ",
701 p.name, p.name
702 )
703 .ok();
704 }
705 }
706 // Vec<String> with is_ref=true: core expects &[&str].
707 // Convert Vec<String> to Vec<&str> via intermediate binding.
708 TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref => {
709 write!(
710 bindings,
711 "let {n}_refs: Vec<&str> = {n}.iter().map(|s| s.as_str()).collect();\n ",
712 n = p.name,
713 )
714 .ok();
715 }
716 // Sanitized Vec<String> (originally Vec<tuple>): deserialize each JSON string.
717 TypeRef::Vec(inner)
718 if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() =>
719 {
720 if p.optional {
721 write!(
722 bindings,
723 "let {n}_core: Option<Vec<_>> = {n}.map(|strs| \
724 strs.into_iter()\n \
725 .filter_map(|s| serde_json::from_str(&s).ok())\n \
726 .collect()\n \
727 );\n ",
728 n = p.name,
729 )
730 .ok();
731 } else {
732 write!(
733 bindings,
734 "let {n}_core: Vec<_> = {n}.into_iter()\n \
735 .filter_map(|s| serde_json::from_str(&s).ok())\n \
736 .collect();\n ",
737 n = p.name,
738 )
739 .ok();
740 }
741 }
742 _ => {}
743 }
744 }
745 bindings
746}
747
748/// Generate serde-based let bindings for non-opaque Named params.
749/// Serializes binding types to JSON and deserializes to core types.
750/// Used when From impls don't exist (e.g., types with sanitized fields).
751/// `indent` is the whitespace prefix for each generated line (e.g., " " for functions, " " for methods).
752/// NOTE: This function should only be called when `cfg.has_serde` is true.
753/// The caller (functions.rs, methods.rs) must gate the call behind a `has_serde` check.
754pub fn gen_serde_let_bindings(
755 params: &[ParamDef],
756 opaque_types: &AHashSet<String>,
757 core_import: &str,
758 err_conv: &str,
759 indent: &str,
760) -> String {
761 let mut bindings = String::new();
762 for p in params {
763 match &p.ty {
764 TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
765 let core_path = format!("{}::{}", core_import, name);
766 if p.optional {
767 write!(
768 bindings,
769 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
770 {indent} let json = serde_json::to_string(&v){err_conv}?;\n\
771 {indent} serde_json::from_str(&json){err_conv}\n\
772 {indent}}}).transpose()?;\n{indent}",
773 name = p.name,
774 core_path = core_path,
775 err_conv = err_conv,
776 indent = indent,
777 )
778 .ok();
779 } else {
780 write!(
781 bindings,
782 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
783 {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
784 name = p.name,
785 core_path = core_path,
786 err_conv = err_conv,
787 indent = indent,
788 )
789 .ok();
790 }
791 }
792 TypeRef::Vec(inner) => {
793 if let TypeRef::Named(name) = inner.as_ref() {
794 if !opaque_types.contains(name.as_str()) {
795 let core_path = format!("{}::{}", core_import, name);
796 if p.optional {
797 write!(
798 bindings,
799 "let {name}_core: Option<Vec<{core_path}>> = {name}.map(|v| {{\n\
800 {indent} let json = serde_json::to_string(&v){err_conv}?;\n\
801 {indent} serde_json::from_str(&json){err_conv}\n\
802 {indent}}}).transpose()?;\n{indent}",
803 name = p.name,
804 core_path = core_path,
805 err_conv = err_conv,
806 indent = indent,
807 )
808 .ok();
809 } else {
810 write!(
811 bindings,
812 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
813 {indent}let {name}_core: Vec<{core_path}> = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
814 name = p.name,
815 core_path = core_path,
816 err_conv = err_conv,
817 indent = indent,
818 )
819 .ok();
820 }
821 }
822 } else if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
823 // Sanitized Vec<tuple>: binding accepts Vec<String> (JSON-encoded tuple items).
824 // Deserialize each JSON string as a tuple using serde_json.
825 if p.optional {
826 write!(
827 bindings,
828 "let {n}_core: Option<Vec<_>> = {n}.map(|strs| {{\n\
829 {indent} strs.into_iter()\n\
830 {indent} .map(|s| serde_json::from_str::<_>(&s){err_conv})\n\
831 {indent} .collect::<Result<Vec<_>, _>>()\n\
832 {indent}}}).transpose()?;\n{indent}",
833 n = p.name,
834 err_conv = err_conv,
835 indent = indent,
836 )
837 .ok();
838 } else {
839 write!(
840 bindings,
841 "let {n}_core: Vec<_> = {n}.into_iter()\n\
842 {indent}.map(|s| serde_json::from_str::<_>(&s){err_conv})\n\
843 {indent}.collect::<Result<Vec<_>, _>>()?;\n{indent}",
844 n = p.name,
845 err_conv = err_conv,
846 indent = indent,
847 )
848 .ok();
849 }
850 }
851 }
852 _ => {}
853 }
854 }
855 bindings
856}
857
858/// Check if params contain any non-opaque Named types that need let bindings.
859/// This includes direct Named types, Vec<Named> types, Vec<String> params
860/// with is_ref=true (which need a Vec<&str> intermediate to pass as &[&str]),
861/// and sanitized Vec<String> params (which are JSON-deserialized to tuples).
862pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
863 params.iter().any(|p| match &p.ty {
864 TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => true,
865 TypeRef::Vec(inner) => {
866 // Vec<Named> always needs a conversion let binding.
867 // Vec<String> with is_ref=true needs a Vec<&str> intermediate for &[&str] coercion.
868 // Sanitized Vec<String> needs JSON deserialization via let binding.
869 matches!(inner.as_ref(), TypeRef::Named(name) if !opaque_types.contains(name.as_str()))
870 || (matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref)
871 || (matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some())
872 }
873 _ => false,
874 })
875}
876
877/// Check if a param type is safe for non-opaque delegation (no complex conversions needed).
878/// Vec and Map params can cause type mismatches (e.g. Vec<String> vs &[&str]).
879pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
880 match ty {
881 TypeRef::Primitive(_)
882 | TypeRef::String
883 | TypeRef::Char
884 | TypeRef::Bytes
885 | TypeRef::Path
886 | TypeRef::Unit
887 | TypeRef::Duration => true,
888 TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
889 _ => false,
890 }
891}
892
893/// Generate a lossy binding→core struct literal for non-opaque delegation.
894/// Sanitized fields use `Default::default()`, non-sanitized fields are cloned and converted.
895/// Fields are accessed via `self.` (behind &self), so all non-Copy types need `.clone()`.
896///
897/// NOTE: This assumes all binding struct fields implement Clone. If a field type does not
898/// implement Clone (e.g., `Mutex<T>`), it should be marked as `sanitized=true` so that
899/// `Default::default()` is used instead of calling `.clone()`. Backends that exclude types
900/// should mark such fields appropriately.
901pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str, option_duration_on_defaults: bool) -> String {
902 gen_lossy_binding_to_core_fields_inner(typ, core_import, false, option_duration_on_defaults)
903}
904
905/// Same as `gen_lossy_binding_to_core_fields` but declares `core_self` as mutable.
906pub fn gen_lossy_binding_to_core_fields_mut(
907 typ: &TypeDef,
908 core_import: &str,
909 option_duration_on_defaults: bool,
910) -> String {
911 gen_lossy_binding_to_core_fields_inner(typ, core_import, true, option_duration_on_defaults)
912}
913
914fn gen_lossy_binding_to_core_fields_inner(
915 typ: &TypeDef,
916 core_import: &str,
917 needs_mut: bool,
918 option_duration_on_defaults: bool,
919) -> String {
920 let core_path = crate::conversions::core_type_path(typ, core_import);
921 let mut_kw = if needs_mut { "mut " } else { "" };
922 // When has_stripped_cfg_fields is true we emit ..Default::default() at the end of the
923 // struct literal to fill cfg-gated fields that were stripped from the binding IR.
924 // Suppress clippy::needless_update because the fields only exist when the corresponding
925 // feature is enabled — without the feature, clippy thinks the spread is redundant.
926 let allow = if typ.has_stripped_cfg_fields {
927 "#[allow(clippy::needless_update)]\n "
928 } else {
929 ""
930 };
931 let mut out = format!("{allow}let {mut_kw}core_self = {core_path} {{\n");
932 for field in &typ.fields {
933 let name = &field.name;
934 if field.sanitized {
935 writeln!(out, " {name}: Default::default(),").ok();
936 } else {
937 let expr = match &field.ty {
938 TypeRef::Primitive(_) => format!("self.{name}"),
939 TypeRef::Duration => {
940 if field.optional {
941 format!("self.{name}.map(std::time::Duration::from_millis)")
942 } else if option_duration_on_defaults && typ.has_default {
943 // When option_duration_on_defaults is true, non-optional Duration fields
944 // on has_default types are stored as Option<u64> in the binding struct.
945 // Use .map(...).unwrap_or_default() so that None falls back to the core
946 // type's Default (e.g. Duration::from_secs(30)) rather than Duration::ZERO.
947 format!("self.{name}.map(std::time::Duration::from_millis).unwrap_or_default()")
948 } else {
949 format!("std::time::Duration::from_millis(self.{name})")
950 }
951 }
952 TypeRef::String => format!("self.{name}.clone()"),
953 // Bytes: binding stores Vec<u8>. When core_wrapper == Bytes, core expects
954 // bytes::Bytes so we must call .into() to convert Vec<u8> → Bytes.
955 // When core_wrapper == None, the core field is also Vec<u8> (plain clone).
956 TypeRef::Bytes => {
957 if field.core_wrapper == CoreWrapper::Bytes {
958 format!("self.{name}.clone().into()")
959 } else {
960 format!("self.{name}.clone()")
961 }
962 }
963 TypeRef::Char => {
964 if field.optional {
965 format!("self.{name}.as_ref().and_then(|s| s.chars().next())")
966 } else {
967 format!("self.{name}.chars().next().unwrap_or('*')")
968 }
969 }
970 TypeRef::Path => {
971 if field.optional {
972 format!("self.{name}.clone().map(Into::into)")
973 } else {
974 format!("self.{name}.clone().into()")
975 }
976 }
977 TypeRef::Named(_) => {
978 if field.optional {
979 format!("self.{name}.clone().map(Into::into)")
980 } else {
981 format!("self.{name}.clone().into()")
982 }
983 }
984 TypeRef::Vec(inner) => match inner.as_ref() {
985 TypeRef::Named(_) => {
986 if field.optional {
987 // Option<Vec<Named(T)>>: map over the Option, then convert each element
988 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
989 } else {
990 format!("self.{name}.clone().into_iter().map(Into::into).collect()")
991 }
992 }
993 _ => format!("self.{name}.clone()"),
994 },
995 TypeRef::Optional(inner) => {
996 // When field.optional is also true, the binding field was flattened from
997 // Option<Option<T>> to Option<T>. Core expects Option<Option<T>>, so wrap
998 // with .map(Some) to reconstruct the double-optional.
999 let base = match inner.as_ref() {
1000 TypeRef::Named(_) => {
1001 format!("self.{name}.clone().map(Into::into)")
1002 }
1003 TypeRef::Duration => {
1004 format!("self.{name}.map(|v| std::time::Duration::from_millis(v as u64))")
1005 }
1006 TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
1007 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
1008 }
1009 _ => format!("self.{name}.clone()"),
1010 };
1011 if field.optional {
1012 format!("({base}).map(Some)")
1013 } else {
1014 base
1015 }
1016 }
1017 TypeRef::Map(_, v) => match v.as_ref() {
1018 TypeRef::Json => {
1019 // HashMap<String, String> (binding) → HashMap<String, Value> (core)
1020 if field.optional {
1021 format!(
1022 "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
1023 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
1024 )
1025 } else {
1026 format!(
1027 "self.{name}.clone().into_iter().map(|(k, v)| \
1028 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
1029 )
1030 }
1031 }
1032 // Collect to handle HashMap↔BTreeMap conversion
1033 _ => {
1034 if field.optional {
1035 format!("self.{name}.clone().map(|m| m.into_iter().collect())")
1036 } else {
1037 format!("self.{name}.clone().into_iter().collect()")
1038 }
1039 }
1040 },
1041 TypeRef::Unit => format!("self.{name}.clone()"),
1042 TypeRef::Json => {
1043 // String (binding) → serde_json::Value (core)
1044 if field.optional {
1045 format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
1046 } else {
1047 format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
1048 }
1049 }
1050 };
1051 // Newtype wrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
1052 // re-wrap the binding value into the newtype for the core struct literal.
1053 // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
1054 // field is actually `Option<NewtypeT>`, so we must use `.map(NewtypeT)` not `NewtypeT(...)`.
1055 let expr = if let Some(newtype_path) = &field.newtype_wrapper {
1056 match &field.ty {
1057 TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
1058 TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
1059 _ if field.optional => format!("({expr}).map({newtype_path})"),
1060 _ => format!("{newtype_path}({expr})"),
1061 }
1062 } else {
1063 expr
1064 };
1065 writeln!(out, " {name}: {expr},").ok();
1066 }
1067 }
1068 // Use ..Default::default() to fill cfg-gated fields stripped from the IR
1069 if typ.has_stripped_cfg_fields {
1070 out.push_str(" ..Default::default()\n");
1071 }
1072 out.push_str(" };\n ");
1073 out
1074}
1075
1076/// Generate the body for an async call, unified across methods, static methods, and free functions.
1077///
1078/// - `core_call`: the expression to await, e.g. `inner.method(args)` or `CoreType::fn(args)`.
1079/// For Pyo3FutureIntoPy opaque methods this should reference `inner` (the Arc clone);
1080/// for all other patterns it may reference `self.inner` or a static call expression.
1081/// - `cfg`: binding configuration (determines which async pattern to emit)
1082/// - `has_error`: whether the core call returns a `Result`
1083/// - `return_wrap`: expression to produce the binding return value from `result`,
1084/// e.g. `"result"` or `"TypeName::from(result)"`
1085///
1086/// - `is_opaque`: whether the binding type is Arc-wrapped (affects TokioBlockOn wrapping)
1087/// - `inner_clone_line`: optional statement emitted before the pattern-specific body,
1088/// e.g. `"let inner = self.inner.clone();\n "` for opaque instance methods, or `""`.
1089/// Required when `core_call` references `inner` (Pyo3FutureIntoPy opaque case).
1090#[allow(clippy::too_many_arguments)]
1091pub fn gen_async_body(
1092 core_call: &str,
1093 cfg: &RustBindingConfig,
1094 has_error: bool,
1095 return_wrap: &str,
1096 is_opaque: bool,
1097 inner_clone_line: &str,
1098 is_unit_return: bool,
1099 return_type: Option<&str>,
1100) -> String {
1101 let pattern_body = match cfg.async_pattern {
1102 AsyncPattern::Pyo3FutureIntoPy => {
1103 let result_handling = if has_error {
1104 format!(
1105 "let result = {core_call}.await\n \
1106 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
1107 )
1108 } else if is_unit_return {
1109 format!("{core_call}.await;")
1110 } else {
1111 format!("let result = {core_call}.await;")
1112 };
1113 let (ok_expr, extra_binding) = if is_unit_return && !has_error {
1114 ("()".to_string(), String::new())
1115 } else if return_wrap.contains(".into()") || return_wrap.contains("::from(") {
1116 // When return_wrap contains type conversions like .into() or ::from(),
1117 // bind to a variable to help type inference for the generic future_into_py.
1118 // This avoids E0283 "type annotations needed".
1119 let wrapped_var = "wrapped_result";
1120 let binding = if let Some(ret_type) = return_type {
1121 // Add explicit type annotation to help type inference
1122 format!("let {wrapped_var}: {ret_type} = {return_wrap};\n ")
1123 } else {
1124 format!("let {wrapped_var} = {return_wrap};\n ")
1125 };
1126 (wrapped_var.to_string(), binding)
1127 } else {
1128 (return_wrap.to_string(), String::new())
1129 };
1130 format!(
1131 "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
1132 {result_handling}\n \
1133 {extra_binding}Ok({ok_expr})\n }})"
1134 )
1135 }
1136 AsyncPattern::WasmNativeAsync => {
1137 let result_handling = if has_error {
1138 format!(
1139 "let result = {core_call}.await\n \
1140 .map_err(|e| JsValue::from_str(&e.to_string()))?;"
1141 )
1142 } else if is_unit_return {
1143 format!("{core_call}.await;")
1144 } else {
1145 format!("let result = {core_call}.await;")
1146 };
1147 let ok_expr = if is_unit_return && !has_error {
1148 "()"
1149 } else {
1150 return_wrap
1151 };
1152 format!(
1153 "{result_handling}\n \
1154 Ok({ok_expr})"
1155 )
1156 }
1157 AsyncPattern::NapiNativeAsync => {
1158 let result_handling = if has_error {
1159 format!(
1160 "let result = {core_call}.await\n \
1161 .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
1162 )
1163 } else if is_unit_return {
1164 format!("{core_call}.await;")
1165 } else {
1166 format!("let result = {core_call}.await;")
1167 };
1168 if !has_error && !is_unit_return {
1169 // No error type: return value directly without Ok() wrapper
1170 format!(
1171 "{result_handling}\n \
1172 {return_wrap}"
1173 )
1174 } else {
1175 let ok_expr = if is_unit_return && !has_error {
1176 "()"
1177 } else {
1178 return_wrap
1179 };
1180 format!(
1181 "{result_handling}\n \
1182 Ok({ok_expr})"
1183 )
1184 }
1185 }
1186 AsyncPattern::TokioBlockOn => {
1187 if has_error {
1188 if is_opaque {
1189 format!(
1190 "let rt = tokio::runtime::Runtime::new()?;\n \
1191 let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n \
1192 {return_wrap}"
1193 )
1194 } else {
1195 format!(
1196 "let rt = tokio::runtime::Runtime::new()?;\n \
1197 rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
1198 )
1199 }
1200 } else if is_opaque {
1201 if is_unit_return {
1202 format!(
1203 "let rt = tokio::runtime::Runtime::new()?;\n \
1204 rt.block_on(async {{ {core_call}.await }});"
1205 )
1206 } else {
1207 format!(
1208 "let rt = tokio::runtime::Runtime::new()?;\n \
1209 let result = rt.block_on(async {{ {core_call}.await }});\n \
1210 {return_wrap}"
1211 )
1212 }
1213 } else {
1214 format!(
1215 "let rt = tokio::runtime::Runtime::new()?;\n \
1216 rt.block_on(async {{ {core_call}.await }})"
1217 )
1218 }
1219 }
1220 AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
1221 };
1222 if inner_clone_line.is_empty() {
1223 pattern_body
1224 } else {
1225 format!("{inner_clone_line}{pattern_body}")
1226 }
1227}
1228
1229/// Generate a compilable body for functions that can't be auto-delegated.
1230/// Returns a default value or error instead of `todo!()` which would panic.
1231///
1232/// `opaque_types` is the set of opaque type names (Arc-wrapped). Opaque types do not
1233/// implement `Default`, so returning `Default::default()` for their Named return types
1234/// would fail to compile. For those cases a `todo!()` body is emitted instead.
1235pub fn gen_unimplemented_body(
1236 return_type: &TypeRef,
1237 fn_name: &str,
1238 has_error: bool,
1239 cfg: &RustBindingConfig,
1240 params: &[ParamDef],
1241 opaque_types: &AHashSet<String>,
1242) -> String {
1243 // Suppress unused_variables by binding all params to `_`
1244 let suppress = if params.is_empty() {
1245 String::new()
1246 } else {
1247 let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
1248 if names.len() == 1 {
1249 format!("let _ = {};\n ", names[0])
1250 } else {
1251 format!("let _ = ({});\n ", names.join(", "))
1252 }
1253 };
1254 let err_msg = format!("Not implemented: {fn_name}");
1255 let body = if has_error {
1256 // Backend-specific error return
1257 match cfg.async_pattern {
1258 AsyncPattern::Pyo3FutureIntoPy => {
1259 format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
1260 }
1261 AsyncPattern::NapiNativeAsync => {
1262 format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
1263 }
1264 AsyncPattern::WasmNativeAsync => {
1265 format!("Err(JsValue::from_str(\"{err_msg}\"))")
1266 }
1267 _ => format!("Err(\"{err_msg}\".to_string())"),
1268 }
1269 } else {
1270 // Return type-appropriate default
1271 match return_type {
1272 TypeRef::Unit => "()".to_string(),
1273 TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
1274 TypeRef::Bytes => "Vec::new()".to_string(),
1275 TypeRef::Primitive(p) => match p {
1276 alef_core::ir::PrimitiveType::Bool => "false".to_string(),
1277 alef_core::ir::PrimitiveType::F32 => "0.0f32".to_string(),
1278 alef_core::ir::PrimitiveType::F64 => "0.0f64".to_string(),
1279 _ => "0".to_string(),
1280 },
1281 TypeRef::Optional(_) => "None".to_string(),
1282 TypeRef::Vec(_) => "Vec::new()".to_string(),
1283 TypeRef::Map(_, _) => "Default::default()".to_string(),
1284 TypeRef::Duration => "0".to_string(),
1285 TypeRef::Named(name) => {
1286 // Opaque types (Arc-wrapped) do not implement Default — use todo!() to
1287 // produce a compilable placeholder that panics at runtime if called.
1288 // Non-opaque Named types (config structs) do derive Default, so use that.
1289 if opaque_types.contains(name.as_str()) {
1290 format!("todo!(\"{err_msg}\")")
1291 } else {
1292 "Default::default()".to_string()
1293 }
1294 }
1295 TypeRef::Json => {
1296 // Json return without error type: return Default::default()
1297 "Default::default()".to_string()
1298 }
1299 }
1300 };
1301 format!("{suppress}{body}")
1302}