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_serde_let_bindings,
4 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 call_args = gen_call_args(&method.params, opaque_types);
78
79 let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
80
81 let opaque_can_delegate = is_opaque
85 && !method.sanitized
86 && (!is_owned_receiver || typ.is_clone)
87 && method
88 .params
89 .iter()
90 .all(|p| !p.sanitized && crate::shared::is_opaque_delegatable_type(&p.ty))
91 && crate::shared::is_opaque_delegatable_type(&method.return_type);
92
93 let make_core_call = |method_name: &str| -> String {
96 if is_opaque {
97 if is_owned_receiver {
98 format!("(*self.inner).clone().{method_name}({call_args})")
100 } else {
101 format!("self.inner.{method_name}({call_args})")
102 }
103 } else {
104 format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
105 }
106 };
107
108 let make_async_core_call = |method_name: &str| -> String {
110 if is_opaque {
111 format!("inner.{method_name}({call_args})")
112 } else {
113 format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
114 }
115 };
116
117 let result_expr = apply_return_newtype_unwrap("result", &method.return_newtype_wrapper);
124 let async_result_wrap = if is_opaque {
125 wrap_return(
126 &result_expr,
127 &method.return_type,
128 type_name,
129 opaque_types,
130 is_opaque,
131 method.returns_ref,
132 method.returns_cow,
133 )
134 } else {
135 match &method.return_type {
138 TypeRef::Named(_) | TypeRef::Json => format!("{result_expr}.into()"),
139 _ => result_expr.clone(),
140 }
141 };
142
143 let body = if !opaque_can_delegate {
144 let adapter_key = format!("{}.{}", type_name, method.name);
146 if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
147 adapter_body.clone()
148 } else if cfg.has_serde
149 && is_opaque
150 && !method.sanitized
151 && has_named_params(&method.params, opaque_types)
152 && method.error_type.is_some()
153 && crate::shared::is_opaque_delegatable_type(&method.return_type)
154 {
155 let err_conv = match cfg.async_pattern {
157 AsyncPattern::Pyo3FutureIntoPy => {
158 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
159 }
160 AsyncPattern::NapiNativeAsync => {
161 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
162 }
163 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
164 _ => ".map_err(|e| e.to_string())",
165 };
166 let serde_bindings =
167 gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, " ");
168 let serde_call_args = gen_call_args_with_let_bindings(&method.params, opaque_types);
169 let core_call = format!("self.inner.{}({serde_call_args})", method.name);
170 if matches!(method.return_type, TypeRef::Unit) {
171 format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
172 } else {
173 let wrap = wrap_return(
174 "result",
175 &method.return_type,
176 type_name,
177 opaque_types,
178 is_opaque,
179 method.returns_ref,
180 method.returns_cow,
181 );
182 format!("{serde_bindings}let result = {core_call}{err_conv}?;\n Ok({wrap})")
183 }
184 } else if !is_opaque
185 && !method.sanitized
186 && method
187 .params
188 .iter()
189 .all(|p| !p.sanitized && is_simple_non_opaque_param(&p.ty))
190 && crate::shared::is_delegatable_return(&method.return_type)
191 {
192 let is_ref_mut = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::RefMut));
195 let field_conversions = if is_ref_mut {
196 gen_lossy_binding_to_core_fields_mut(typ, cfg.core_import)
197 } else {
198 gen_lossy_binding_to_core_fields(typ, cfg.core_import)
199 };
200 let core_call = format!("core_self.{}({call_args})", method.name);
201 let newtype_suffix = if method.return_newtype_wrapper.is_some() {
202 ".0"
203 } else {
204 ""
205 };
206 let result_wrap = match &method.return_type {
207 TypeRef::Named(n) if n == type_name && (method.returns_cow || method.returns_ref) => {
211 ".into_owned().into()".to_string()
212 }
213 TypeRef::Named(_) if method.returns_cow || method.returns_ref => ".into_owned().into()".to_string(),
214 TypeRef::Named(n) if n == type_name => ".into()".to_string(),
215 TypeRef::Named(_) => ".into()".to_string(),
216 TypeRef::String | TypeRef::Bytes | TypeRef::Path => {
217 if method.returns_ref {
218 ".to_owned()".to_string()
219 } else {
220 ".into()".to_string()
221 }
222 }
223 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
225 if method.returns_ref {
226 ".map(|v| v.clone().into())".to_string()
227 } else {
228 ".map(Into::into)".to_string()
229 }
230 }
231 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Bytes) => {
233 if method.returns_ref {
234 ".map(|v| v.to_owned())".to_string()
235 } else {
236 String::new()
237 }
238 }
239 _ => String::new(),
240 };
241 if method.error_type.is_some() {
242 let err_conv = match cfg.async_pattern {
243 AsyncPattern::Pyo3FutureIntoPy => {
244 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
245 }
246 AsyncPattern::NapiNativeAsync => {
247 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
248 }
249 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
250 _ => ".map_err(|e| e.to_string())",
251 };
252 format!(
253 "{field_conversions}let result = {core_call}{err_conv}?;\n Ok(result{newtype_suffix}{result_wrap})"
254 )
255 } else {
256 format!("{field_conversions}{core_call}{newtype_suffix}{result_wrap}")
257 }
258 } else {
259 gen_unimplemented_body(
260 &method.return_type,
261 &format!("{type_name}.{}", method.name),
262 method.error_type.is_some(),
263 cfg,
264 &method.params,
265 )
266 }
267 } else if method.is_async {
268 let inner_clone_line = if is_opaque {
269 "let inner = self.inner.clone();\n "
270 } else {
271 ""
272 };
273 let core_call_str = make_async_core_call(&method.name);
274 gen_async_body(
275 &core_call_str,
276 cfg,
277 method.error_type.is_some(),
278 &async_result_wrap,
279 is_opaque,
280 inner_clone_line,
281 matches!(method.return_type, TypeRef::Unit),
282 )
283 } else {
284 let core_call = make_core_call(&method.name);
285 if method.error_type.is_some() {
286 let err_conv = match cfg.async_pattern {
288 AsyncPattern::Pyo3FutureIntoPy => {
289 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
290 }
291 AsyncPattern::NapiNativeAsync => {
292 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
293 }
294 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
295 _ => ".map_err(|e| e.to_string())",
296 };
297 if is_opaque {
298 if matches!(method.return_type, TypeRef::Unit) {
299 format!("{core_call}{err_conv}?;\n Ok(())")
301 } else {
302 let wrap = wrap_return(
303 &result_expr,
304 &method.return_type,
305 type_name,
306 opaque_types,
307 is_opaque,
308 method.returns_ref,
309 method.returns_cow,
310 );
311 format!("let result = {core_call}{err_conv}?;\n Ok({wrap})")
312 }
313 } else {
314 format!("{core_call}{err_conv}")
315 }
316 } else if is_opaque {
317 let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
318 wrap_return(
319 &unwrapped_call,
320 &method.return_type,
321 type_name,
322 opaque_types,
323 is_opaque,
324 method.returns_ref,
325 method.returns_cow,
326 )
327 } else {
328 core_call
329 }
330 };
331
332 let needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
333 let self_param = match (needs_py, params.is_empty()) {
334 (true, true) => "&self, py: Python<'py>",
335 (true, false) => "&self, py: Python<'py>, ",
336 (false, true) => "&self",
337 (false, false) => "&self, ",
338 };
339
340 let ret = if needs_py {
343 "PyResult<Bound<'py, PyAny>>".to_string()
344 } else {
345 ret
346 };
347 let method_lifetime = if needs_py { "<'py>" } else { "" };
348
349 let (sig_start, sig_params, sig_end) = if self_param.len() + params.len() > 100 {
351 let wrapped_params = method
352 .params
353 .iter()
354 .map(|p| {
355 let ty = if p.optional {
356 format!("Option<{}>", mapper.map_type(&p.ty))
357 } else {
358 mapper.map_type(&p.ty)
359 };
360 format!("{}: {}", p.name, ty)
361 })
362 .collect::<Vec<_>>()
363 .join(",\n ");
364 let py_param = if needs_py { "\n py: Python<'py>," } else { "" };
365 (
366 format!(
367 "pub fn {}{method_lifetime}(\n &self,{}\n ",
368 method.name, py_param
369 ),
370 wrapped_params,
371 "\n ) -> ".to_string(),
372 )
373 } else {
374 (
375 format!("pub fn {}{method_lifetime}({}", method.name, self_param),
376 params,
377 ") -> ".to_string(),
378 )
379 };
380
381 let mut out = String::with_capacity(1024);
382 let total_params = method.params.len() + 1 + if needs_py { 1 } else { 0 };
384 if total_params > 7 {
385 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
386 }
387 if method.error_type.is_some() {
389 writeln!(out, " #[allow(clippy::missing_errors_doc)]").ok();
390 }
391 if is_trait_method_name(&method.name) {
393 writeln!(out, " #[allow(clippy::should_implement_trait)]").ok();
394 }
395 if cfg.needs_signature {
396 let sig = function_sig_defaults(&method.params);
397 writeln!(out, " {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
398 }
399 write!(
400 out,
401 " {}{}{}{} {{\n \
402 {body}\n }}",
403 sig_start, sig_params, sig_end, ret,
404 )
405 .ok();
406 out
407}
408
409pub fn gen_static_method(
411 method: &MethodDef,
412 mapper: &dyn TypeMapper,
413 cfg: &RustBindingConfig,
414 typ: &TypeDef,
415 adapter_bodies: &AdapterBodies,
416 opaque_types: &AHashSet<String>,
417) -> String {
418 let type_name = &typ.name;
419 let core_type_path = typ.rust_path.replace('-', "_");
421 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
422 let params = function_params(&method.params, &map_fn);
423 let return_type = mapper.map_type(&method.return_type);
424 let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
425
426 let call_args = gen_call_args(&method.params, opaque_types);
427
428 let can_delegate = crate::shared::can_auto_delegate(method, opaque_types);
429
430 let body = if !can_delegate {
431 let adapter_key = format!("{}.{}", type_name, method.name);
433 if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
434 adapter_body.clone()
435 } else {
436 gen_unimplemented_body(
437 &method.return_type,
438 &format!("{type_name}::{}", method.name),
439 method.error_type.is_some(),
440 cfg,
441 &method.params,
442 )
443 }
444 } else if method.is_async {
445 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
446 let return_wrap = format!("{return_type}::from(result)");
447 gen_async_body(
448 &core_call,
449 cfg,
450 method.error_type.is_some(),
451 &return_wrap,
452 false,
453 "",
454 matches!(method.return_type, TypeRef::Unit),
455 )
456 } else {
457 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
458 if method.error_type.is_some() {
459 let err_conv = match cfg.async_pattern {
461 AsyncPattern::Pyo3FutureIntoPy => {
462 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
463 }
464 AsyncPattern::NapiNativeAsync => {
465 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
466 }
467 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
468 _ => ".map_err(|e| e.to_string())",
469 };
470 let val_expr = apply_return_newtype_unwrap("val", &method.return_newtype_wrapper);
472 let wrapped = wrap_return(
473 &val_expr,
474 &method.return_type,
475 type_name,
476 opaque_types,
477 typ.is_opaque,
478 method.returns_ref,
479 method.returns_cow,
480 );
481 if wrapped == val_expr {
482 format!("{core_call}{err_conv}")
483 } else {
484 format!("{core_call}.map(|val| {wrapped}){err_conv}")
485 }
486 } else {
487 let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
489 wrap_return(
490 &unwrapped_call,
491 &method.return_type,
492 type_name,
493 opaque_types,
494 typ.is_opaque,
495 method.returns_ref,
496 method.returns_cow,
497 )
498 }
499 };
500
501 let static_needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
502
503 let ret = if static_needs_py {
505 "PyResult<Bound<'py, PyAny>>".to_string()
506 } else {
507 ret
508 };
509 let method_lifetime = if static_needs_py { "<'py>" } else { "" };
510
511 let (sig_start, sig_params, sig_end) = if params.len() > 100 {
513 let wrapped_params = method
514 .params
515 .iter()
516 .map(|p| {
517 let ty = if p.optional {
518 format!("Option<{}>", mapper.map_type(&p.ty))
519 } else {
520 mapper.map_type(&p.ty)
521 };
522 format!("{}: {}", p.name, ty)
523 })
524 .collect::<Vec<_>>()
525 .join(",\n ");
526 if static_needs_py {
528 (
529 format!("pub fn {}{method_lifetime}(py: Python<'py>,\n ", method.name),
530 wrapped_params,
531 "\n ) -> ".to_string(),
532 )
533 } else {
534 (
535 format!("pub fn {}(\n ", method.name),
536 wrapped_params,
537 "\n ) -> ".to_string(),
538 )
539 }
540 } else if static_needs_py {
541 (
542 format!("pub fn {}{method_lifetime}(py: Python<'py>, ", method.name),
543 params,
544 ") -> ".to_string(),
545 )
546 } else {
547 (format!("pub fn {}(", method.name), params, ") -> ".to_string())
548 };
549
550 let mut out = String::with_capacity(1024);
551 let total_params = method.params.len() + if static_needs_py { 1 } else { 0 };
553 if total_params > 7 {
554 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
555 }
556 if method.error_type.is_some() {
558 writeln!(out, " #[allow(clippy::missing_errors_doc)]").ok();
559 }
560 if is_trait_method_name(&method.name) {
562 writeln!(out, " #[allow(clippy::should_implement_trait)]").ok();
563 }
564 if let Some(attr) = cfg.static_attr {
565 writeln!(out, " #[{attr}]").ok();
566 }
567 if cfg.needs_signature {
568 let sig = function_sig_defaults(&method.params);
569 writeln!(out, " {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
570 }
571 write!(
572 out,
573 " {}{}{}{} {{\n \
574 {body}\n }}",
575 sig_start, sig_params, sig_end, ret,
576 )
577 .ok();
578 out
579}
580
581pub fn gen_impl_block(
583 typ: &TypeDef,
584 mapper: &dyn TypeMapper,
585 cfg: &RustBindingConfig,
586 adapter_bodies: &AdapterBodies,
587 opaque_types: &AHashSet<String>,
588) -> String {
589 let (instance, statics) = partition_methods(&typ.methods);
590 if instance.is_empty() && statics.is_empty() && typ.fields.is_empty() {
591 return String::new();
592 }
593
594 let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
595 let mut out = String::with_capacity(2048);
596 if let Some(block_attr) = cfg.method_block_attr {
597 writeln!(out, "#[{block_attr}]").ok();
598 }
599 writeln!(out, "impl {prefixed_name} {{").ok();
600
601 if !typ.fields.is_empty() {
603 out.push_str(&gen_constructor(typ, mapper, cfg));
604 out.push_str("\n\n");
605 }
606
607 for m in &instance {
609 out.push_str(&gen_method(m, mapper, cfg, typ, false, opaque_types, adapter_bodies));
610 out.push_str("\n\n");
611 }
612
613 for m in &statics {
615 out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
616 out.push_str("\n\n");
617 }
618
619 let trimmed = out.trim_end();
621 let mut result = trimmed.to_string();
622 result.push_str("\n}");
623 result
624}
625
626pub fn gen_opaque_impl_block(
631 typ: &TypeDef,
632 mapper: &dyn TypeMapper,
633 cfg: &RustBindingConfig,
634 opaque_types: &AHashSet<String>,
635 adapter_bodies: &AdapterBodies,
636) -> String {
637 let (instance, statics) = partition_methods(&typ.methods);
638 if instance.is_empty() && statics.is_empty() {
639 return String::new();
640 }
641
642 let mut out = String::with_capacity(2048);
643 let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
644 if let Some(block_attr) = cfg.method_block_attr {
645 writeln!(out, "#[{block_attr}]").ok();
646 }
647 writeln!(out, "impl {prefixed_name} {{").ok();
648
649 for m in &instance {
651 out.push_str(&gen_method(m, mapper, cfg, typ, true, opaque_types, adapter_bodies));
652 out.push_str("\n\n");
653 }
654
655 for m in &statics {
657 out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
658 out.push_str("\n\n");
659 }
660
661 let trimmed = out.trim_end();
662 let mut result = trimmed.to_string();
663 result.push_str("\n}");
664 result
665}