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