1use crate::generators::binding_helpers::{
2 apply_return_newtype_unwrap, gen_async_body, gen_call_args, gen_call_args_with_let_bindings,
3 gen_lossy_binding_to_core_fields, gen_lossy_binding_to_core_fields_mut, gen_named_let_bindings_pub,
4 gen_serde_let_bindings, gen_unimplemented_body, has_named_params, is_simple_non_opaque_param, wrap_return,
5};
6use crate::generators::{AdapterBodies, AsyncPattern, RustBindingConfig};
7use crate::shared::{constructor_parts, function_params, function_sig_defaults, partition_methods};
8use crate::type_mapper::TypeMapper;
9use ahash::AHashSet;
10use alef_core::ir::{MethodDef, TypeDef, TypeRef};
11use std::fmt::Write;
12
13pub fn is_trait_method_name(name: &str) -> bool {
16 crate::generators::TRAIT_METHOD_NAMES.contains(&name)
17}
18
19pub fn gen_constructor(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
21 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
22
23 let (param_list, sig_defaults, assignments) = if typ.has_default {
25 crate::shared::config_constructor_parts_with_options(&typ.fields, &map_fn, cfg.option_duration_on_defaults)
26 } else {
27 constructor_parts(&typ.fields, &map_fn)
28 };
29
30 let mut out = String::with_capacity(512);
31 if typ.fields.len() > 7 {
33 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
34 }
35 writeln!(out, " #[must_use]").ok();
36 if cfg.needs_signature {
37 writeln!(
38 out,
39 " {}{}{}",
40 cfg.signature_prefix, sig_defaults, cfg.signature_suffix
41 )
42 .ok();
43 }
44 write!(
45 out,
46 " {}\n pub fn new({param_list}) -> Self {{\n Self {{ {assignments} }}\n }}",
47 cfg.constructor_attr
48 )
49 .ok();
50 out
51}
52
53pub fn gen_method(
60 method: &MethodDef,
61 mapper: &dyn TypeMapper,
62 cfg: &RustBindingConfig,
63 typ: &TypeDef,
64 is_opaque: bool,
65 opaque_types: &AHashSet<String>,
66 adapter_bodies: &AdapterBodies,
67) -> String {
68 let type_name = &typ.name;
69 let core_type_path = typ.rust_path.replace('-', "_");
71
72 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
73 let params = function_params(&method.params, &map_fn);
74 let return_type = mapper.map_type(&method.return_type);
75 let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
76
77 let has_ref_named_params = method
80 .params
81 .iter()
82 .any(|p| p.is_ref && matches!(&p.ty, TypeRef::Named(n) if !opaque_types.contains(n.as_str())));
83 let (call_args, ref_let_bindings) = if has_ref_named_params {
84 (
85 gen_call_args_with_let_bindings(&method.params, opaque_types),
86 gen_named_let_bindings_pub(&method.params, opaque_types),
87 )
88 } else {
89 (gen_call_args(&method.params, opaque_types), String::new())
90 };
91
92 let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
93
94 let opaque_can_delegate = is_opaque
98 && !method.sanitized
99 && (!is_owned_receiver || typ.is_clone)
100 && method
101 .params
102 .iter()
103 .all(|p| !p.sanitized && crate::shared::is_opaque_delegatable_type(&p.ty))
104 && crate::shared::is_opaque_delegatable_type(&method.return_type);
105
106 let make_core_call = |method_name: &str| -> String {
109 if is_opaque {
110 if is_owned_receiver {
111 format!("(*self.inner).clone().{method_name}({call_args})")
113 } else {
114 format!("self.inner.{method_name}({call_args})")
115 }
116 } else {
117 format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
118 }
119 };
120
121 let make_async_core_call = |method_name: &str| -> String {
123 if is_opaque {
124 format!("inner.{method_name}({call_args})")
125 } else {
126 format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
127 }
128 };
129
130 let result_expr = apply_return_newtype_unwrap("result", &method.return_newtype_wrapper);
137 let async_result_wrap = if is_opaque {
138 wrap_return(
139 &result_expr,
140 &method.return_type,
141 type_name,
142 opaque_types,
143 is_opaque,
144 method.returns_ref,
145 method.returns_cow,
146 )
147 } else {
148 match &method.return_type {
151 TypeRef::Named(_) | TypeRef::Json => format!("{result_expr}.into()"),
152 _ => result_expr.clone(),
153 }
154 };
155
156 let body = if !opaque_can_delegate {
157 let adapter_key = format!("{}.{}", type_name, method.name);
159 if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
160 adapter_body.clone()
161 } else if cfg.has_serde
162 && is_opaque
163 && !method.sanitized
164 && has_named_params(&method.params, opaque_types)
165 && method.error_type.is_some()
166 && crate::shared::is_opaque_delegatable_type(&method.return_type)
167 {
168 let err_conv = match cfg.async_pattern {
170 AsyncPattern::Pyo3FutureIntoPy => {
171 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
172 }
173 AsyncPattern::NapiNativeAsync => {
174 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
175 }
176 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
177 _ => ".map_err(|e| e.to_string())",
178 };
179 let serde_bindings =
180 gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, " ");
181 let serde_call_args = gen_call_args_with_let_bindings(&method.params, opaque_types);
182 let core_call = format!("self.inner.{}({serde_call_args})", method.name);
183 if matches!(method.return_type, TypeRef::Unit) {
184 format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
185 } else {
186 let wrap = wrap_return(
187 "result",
188 &method.return_type,
189 type_name,
190 opaque_types,
191 is_opaque,
192 method.returns_ref,
193 method.returns_cow,
194 );
195 format!("{serde_bindings}let result = {core_call}{err_conv}?;\n Ok({wrap})")
196 }
197 } else if !is_opaque
198 && !method.sanitized
199 && method
200 .params
201 .iter()
202 .all(|p| !p.sanitized && is_simple_non_opaque_param(&p.ty))
203 && crate::shared::is_delegatable_return(&method.return_type)
204 {
205 let is_ref_mut = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::RefMut));
208 let field_conversions = if is_ref_mut {
209 gen_lossy_binding_to_core_fields_mut(typ, cfg.core_import)
210 } else {
211 gen_lossy_binding_to_core_fields(typ, cfg.core_import)
212 };
213 let core_call = format!("core_self.{}({call_args})", method.name);
214 let newtype_suffix = if method.return_newtype_wrapper.is_some() {
215 ".0"
216 } else {
217 ""
218 };
219 let result_wrap = match &method.return_type {
220 TypeRef::Named(n) if n == type_name && (method.returns_cow || method.returns_ref) => {
224 ".into_owned().into()".to_string()
225 }
226 TypeRef::Named(_) if method.returns_cow || method.returns_ref => ".into_owned().into()".to_string(),
227 TypeRef::Named(n) if n == type_name => ".into()".to_string(),
228 TypeRef::Named(_) => ".into()".to_string(),
229 TypeRef::String | TypeRef::Bytes | TypeRef::Path => {
230 if method.returns_ref {
231 ".to_owned()".to_string()
232 } else {
233 ".into()".to_string()
234 }
235 }
236 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
238 if method.returns_ref {
239 ".map(|v| v.clone().into())".to_string()
240 } else {
241 ".map(Into::into)".to_string()
242 }
243 }
244 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Bytes) => {
246 if method.returns_ref {
247 ".map(|v| v.to_owned())".to_string()
248 } else {
249 String::new()
250 }
251 }
252 _ => String::new(),
253 };
254 if method.error_type.is_some() {
255 let err_conv = match cfg.async_pattern {
256 AsyncPattern::Pyo3FutureIntoPy => {
257 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
258 }
259 AsyncPattern::NapiNativeAsync => {
260 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
261 }
262 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
263 _ => ".map_err(|e| e.to_string())",
264 };
265 format!(
266 "{field_conversions}let result = {core_call}{err_conv}?;\n Ok(result{newtype_suffix}{result_wrap})"
267 )
268 } else {
269 format!("{field_conversions}{core_call}{newtype_suffix}{result_wrap}")
270 }
271 } else {
272 gen_unimplemented_body(
273 &method.return_type,
274 &format!("{type_name}.{}", method.name),
275 method.error_type.is_some(),
276 cfg,
277 &method.params,
278 )
279 }
280 } else if method.is_async {
281 let inner_clone_line = if is_opaque {
282 "let inner = self.inner.clone();\n "
283 } else {
284 ""
285 };
286 let core_call_str = make_async_core_call(&method.name);
287 gen_async_body(
288 &core_call_str,
289 cfg,
290 method.error_type.is_some(),
291 &async_result_wrap,
292 is_opaque,
293 inner_clone_line,
294 matches!(method.return_type, TypeRef::Unit),
295 )
296 } else {
297 let core_call = make_core_call(&method.name);
298 if method.error_type.is_some() {
299 let err_conv = match cfg.async_pattern {
301 AsyncPattern::Pyo3FutureIntoPy => {
302 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
303 }
304 AsyncPattern::NapiNativeAsync => {
305 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
306 }
307 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
308 _ => ".map_err(|e| e.to_string())",
309 };
310 if is_opaque {
311 if matches!(method.return_type, TypeRef::Unit) {
312 format!("{core_call}{err_conv}?;\n Ok(())")
314 } else {
315 let wrap = wrap_return(
316 &result_expr,
317 &method.return_type,
318 type_name,
319 opaque_types,
320 is_opaque,
321 method.returns_ref,
322 method.returns_cow,
323 );
324 format!("let result = {core_call}{err_conv}?;\n Ok({wrap})")
325 }
326 } else {
327 format!("{core_call}{err_conv}")
328 }
329 } else if is_opaque {
330 let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
331 wrap_return(
332 &unwrapped_call,
333 &method.return_type,
334 type_name,
335 opaque_types,
336 is_opaque,
337 method.returns_ref,
338 method.returns_cow,
339 )
340 } else {
341 core_call
342 }
343 };
344 let body = if ref_let_bindings.is_empty() {
346 body
347 } else {
348 format!("{ref_let_bindings}{body}")
349 };
350
351 let needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
352 let self_param = match (needs_py, params.is_empty()) {
353 (true, true) => "&self, py: Python<'py>",
354 (true, false) => "&self, py: Python<'py>, ",
355 (false, true) => "&self",
356 (false, false) => "&self, ",
357 };
358
359 let ret = if needs_py {
362 "PyResult<Bound<'py, PyAny>>".to_string()
363 } else {
364 ret
365 };
366 let method_lifetime = if needs_py { "<'py>" } else { "" };
367
368 let (sig_start, sig_params, sig_end) = if self_param.len() + params.len() > 100 {
370 let wrapped_params = method
371 .params
372 .iter()
373 .map(|p| {
374 let ty = if p.optional {
375 format!("Option<{}>", mapper.map_type(&p.ty))
376 } else {
377 mapper.map_type(&p.ty)
378 };
379 format!("{}: {}", p.name, ty)
380 })
381 .collect::<Vec<_>>()
382 .join(",\n ");
383 let py_param = if needs_py { "\n py: Python<'py>," } else { "" };
384 (
385 format!(
386 "pub fn {}{method_lifetime}(\n &self,{}\n ",
387 method.name, py_param
388 ),
389 wrapped_params,
390 "\n ) -> ".to_string(),
391 )
392 } else {
393 (
394 format!("pub fn {}{method_lifetime}({}", method.name, self_param),
395 params,
396 ") -> ".to_string(),
397 )
398 };
399
400 let mut out = String::with_capacity(1024);
401 let total_params = method.params.len() + 1 + if needs_py { 1 } else { 0 };
403 if total_params > 7 {
404 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
405 }
406 if method.error_type.is_some() {
408 writeln!(out, " #[allow(clippy::missing_errors_doc)]").ok();
409 }
410 if is_trait_method_name(&method.name) {
412 writeln!(out, " #[allow(clippy::should_implement_trait)]").ok();
413 }
414 if cfg.needs_signature {
415 let sig = function_sig_defaults(&method.params);
416 writeln!(out, " {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
417 }
418 write!(
419 out,
420 " {}{}{}{} {{\n \
421 {body}\n }}",
422 sig_start, sig_params, sig_end, ret,
423 )
424 .ok();
425 out
426}
427
428pub fn gen_static_method(
430 method: &MethodDef,
431 mapper: &dyn TypeMapper,
432 cfg: &RustBindingConfig,
433 typ: &TypeDef,
434 adapter_bodies: &AdapterBodies,
435 opaque_types: &AHashSet<String>,
436) -> String {
437 let type_name = &typ.name;
438 let core_type_path = typ.rust_path.replace('-', "_");
440 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
441 let params = function_params(&method.params, &map_fn);
442 let return_type = mapper.map_type(&method.return_type);
443 let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
444
445 let has_ref_named_params = method
447 .params
448 .iter()
449 .any(|p| p.is_ref && matches!(&p.ty, TypeRef::Named(n) if !opaque_types.contains(n.as_str())));
450 let (call_args, ref_let_bindings) = if has_ref_named_params {
451 (
452 gen_call_args_with_let_bindings(&method.params, opaque_types),
453 gen_named_let_bindings_pub(&method.params, opaque_types),
454 )
455 } else {
456 (gen_call_args(&method.params, opaque_types), String::new())
457 };
458
459 let can_delegate = crate::shared::can_auto_delegate(method, opaque_types);
460
461 let body = if !can_delegate {
462 let adapter_key = format!("{}.{}", type_name, method.name);
464 if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
465 adapter_body.clone()
466 } else {
467 gen_unimplemented_body(
468 &method.return_type,
469 &format!("{type_name}::{}", method.name),
470 method.error_type.is_some(),
471 cfg,
472 &method.params,
473 )
474 }
475 } else if method.is_async {
476 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
477 let return_wrap = format!("{return_type}::from(result)");
478 gen_async_body(
479 &core_call,
480 cfg,
481 method.error_type.is_some(),
482 &return_wrap,
483 false,
484 "",
485 matches!(method.return_type, TypeRef::Unit),
486 )
487 } else {
488 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
489 if method.error_type.is_some() {
490 let err_conv = match cfg.async_pattern {
492 AsyncPattern::Pyo3FutureIntoPy => {
493 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
494 }
495 AsyncPattern::NapiNativeAsync => {
496 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
497 }
498 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
499 _ => ".map_err(|e| e.to_string())",
500 };
501 let val_expr = apply_return_newtype_unwrap("val", &method.return_newtype_wrapper);
503 let wrapped = wrap_return(
504 &val_expr,
505 &method.return_type,
506 type_name,
507 opaque_types,
508 typ.is_opaque,
509 method.returns_ref,
510 method.returns_cow,
511 );
512 if wrapped == val_expr {
513 format!("{core_call}{err_conv}")
514 } else {
515 format!("{core_call}.map(|val| {wrapped}){err_conv}")
516 }
517 } else {
518 let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
520 wrap_return(
521 &unwrapped_call,
522 &method.return_type,
523 type_name,
524 opaque_types,
525 typ.is_opaque,
526 method.returns_ref,
527 method.returns_cow,
528 )
529 }
530 };
531 let body = if ref_let_bindings.is_empty() {
533 body
534 } else {
535 format!("{ref_let_bindings}{body}")
536 };
537
538 let static_needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
539
540 let ret = if static_needs_py {
542 "PyResult<Bound<'py, PyAny>>".to_string()
543 } else {
544 ret
545 };
546 let method_lifetime = if static_needs_py { "<'py>" } else { "" };
547
548 let (sig_start, sig_params, sig_end) = if params.len() > 100 {
550 let wrapped_params = method
551 .params
552 .iter()
553 .map(|p| {
554 let ty = if p.optional {
555 format!("Option<{}>", mapper.map_type(&p.ty))
556 } else {
557 mapper.map_type(&p.ty)
558 };
559 format!("{}: {}", p.name, ty)
560 })
561 .collect::<Vec<_>>()
562 .join(",\n ");
563 if static_needs_py {
565 (
566 format!("pub fn {}{method_lifetime}(py: Python<'py>,\n ", method.name),
567 wrapped_params,
568 "\n ) -> ".to_string(),
569 )
570 } else {
571 (
572 format!("pub fn {}(\n ", method.name),
573 wrapped_params,
574 "\n ) -> ".to_string(),
575 )
576 }
577 } else if static_needs_py {
578 (
579 format!("pub fn {}{method_lifetime}(py: Python<'py>, ", method.name),
580 params,
581 ") -> ".to_string(),
582 )
583 } else {
584 (format!("pub fn {}(", method.name), params, ") -> ".to_string())
585 };
586
587 let mut out = String::with_capacity(1024);
588 let total_params = method.params.len() + if static_needs_py { 1 } else { 0 };
590 if total_params > 7 {
591 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
592 }
593 if method.error_type.is_some() {
595 writeln!(out, " #[allow(clippy::missing_errors_doc)]").ok();
596 }
597 if is_trait_method_name(&method.name) {
599 writeln!(out, " #[allow(clippy::should_implement_trait)]").ok();
600 }
601 if let Some(attr) = cfg.static_attr {
602 writeln!(out, " #[{attr}]").ok();
603 }
604 if cfg.needs_signature {
605 let sig = function_sig_defaults(&method.params);
606 writeln!(out, " {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
607 }
608 write!(
609 out,
610 " {}{}{}{} {{\n \
611 {body}\n }}",
612 sig_start, sig_params, sig_end, ret,
613 )
614 .ok();
615 out
616}
617
618pub fn gen_impl_block(
620 typ: &TypeDef,
621 mapper: &dyn TypeMapper,
622 cfg: &RustBindingConfig,
623 adapter_bodies: &AdapterBodies,
624 opaque_types: &AHashSet<String>,
625) -> String {
626 let (instance, statics) = partition_methods(&typ.methods);
627 if instance.is_empty() && statics.is_empty() && typ.fields.is_empty() {
628 return String::new();
629 }
630
631 let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
632 let mut out = String::with_capacity(2048);
633 if let Some(block_attr) = cfg.method_block_attr {
634 writeln!(out, "#[{block_attr}]").ok();
635 }
636 writeln!(out, "impl {prefixed_name} {{").ok();
637
638 if !typ.fields.is_empty() {
640 out.push_str(&gen_constructor(typ, mapper, cfg));
641 out.push_str("\n\n");
642 }
643
644 for m in &instance {
646 out.push_str(&gen_method(m, mapper, cfg, typ, false, opaque_types, adapter_bodies));
647 out.push_str("\n\n");
648 }
649
650 for m in &statics {
652 out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
653 out.push_str("\n\n");
654 }
655
656 let trimmed = out.trim_end();
658 let mut result = trimmed.to_string();
659 result.push_str("\n}");
660 result
661}
662
663pub fn gen_opaque_impl_block(
668 typ: &TypeDef,
669 mapper: &dyn TypeMapper,
670 cfg: &RustBindingConfig,
671 opaque_types: &AHashSet<String>,
672 adapter_bodies: &AdapterBodies,
673) -> String {
674 let (instance, statics) = partition_methods(&typ.methods);
675 if instance.is_empty() && statics.is_empty() {
676 return String::new();
677 }
678
679 let mut out = String::with_capacity(2048);
680 let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
681 if let Some(block_attr) = cfg.method_block_attr {
682 writeln!(out, "#[{block_attr}]").ok();
683 }
684 writeln!(out, "impl {prefixed_name} {{").ok();
685
686 for m in &instance {
688 out.push_str(&gen_method(m, mapper, cfg, typ, true, opaque_types, adapter_bodies));
689 out.push_str("\n\n");
690 }
691
692 for m in &statics {
694 out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
695 out.push_str("\n\n");
696 }
697
698 let trimmed = out.trim_end();
699 let mut result = trimmed.to_string();
700 result.push_str("\n}");
701 result
702}