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