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