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