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_serde_let_bindings, gen_unimplemented_body, has_named_params, is_simple_non_opaque_param, wrap_return,
4};
5use crate::generators::{AdapterBodies, AsyncPattern, RustBindingConfig};
6use crate::shared::{constructor_parts, function_params, function_sig_defaults, partition_methods};
7use crate::type_mapper::TypeMapper;
8use ahash::AHashSet;
9use alef_core::ir::{MethodDef, TypeDef, TypeRef};
10use std::fmt::Write;
11
12pub fn is_trait_method_name(name: &str) -> bool {
15 crate::generators::TRAIT_METHOD_NAMES.contains(&name)
16}
17
18pub fn gen_constructor(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
20 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
21
22 let (param_list, sig_defaults, assignments) = if typ.has_default {
24 crate::shared::config_constructor_parts(&typ.fields, &map_fn)
25 } else {
26 constructor_parts(&typ.fields, &map_fn)
27 };
28
29 let mut out = String::with_capacity(512);
30 if typ.fields.len() > 7 {
32 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
33 }
34 writeln!(out, " #[must_use]").ok();
35 if cfg.needs_signature {
36 writeln!(
37 out,
38 " {}{}{}",
39 cfg.signature_prefix, sig_defaults, cfg.signature_suffix
40 )
41 .ok();
42 }
43 write!(
44 out,
45 " {}\n pub fn new({param_list}) -> Self {{\n Self {{ {assignments} }}\n }}",
46 cfg.constructor_attr
47 )
48 .ok();
49 out
50}
51
52pub fn gen_method(
59 method: &MethodDef,
60 mapper: &dyn TypeMapper,
61 cfg: &RustBindingConfig,
62 typ: &TypeDef,
63 is_opaque: bool,
64 opaque_types: &AHashSet<String>,
65 adapter_bodies: &AdapterBodies,
66) -> String {
67 let type_name = &typ.name;
68 let core_type_path = typ.rust_path.replace('-', "_");
70
71 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
72 let params = function_params(&method.params, &map_fn);
73 let return_type = mapper.map_type(&method.return_type);
74 let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
75
76 let call_args = gen_call_args(&method.params, opaque_types);
77
78 let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
79
80 let opaque_can_delegate = is_opaque
84 && !method.sanitized
85 && (!is_owned_receiver || typ.is_clone)
86 && method
87 .params
88 .iter()
89 .all(|p| !p.sanitized && crate::shared::is_opaque_delegatable_type(&p.ty))
90 && crate::shared::is_opaque_delegatable_type(&method.return_type);
91
92 let make_core_call = |method_name: &str| -> String {
95 if is_opaque {
96 if is_owned_receiver {
97 format!("(*self.inner).clone().{method_name}({call_args})")
99 } else {
100 format!("self.inner.{method_name}({call_args})")
101 }
102 } else {
103 format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
104 }
105 };
106
107 let make_async_core_call = |method_name: &str| -> String {
109 if is_opaque {
110 format!("inner.{method_name}({call_args})")
111 } else {
112 format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
113 }
114 };
115
116 let async_result_wrap = if is_opaque {
123 wrap_return(
124 "result",
125 &method.return_type,
126 type_name,
127 opaque_types,
128 is_opaque,
129 method.returns_ref,
130 )
131 } else {
132 match &method.return_type {
135 TypeRef::Named(_) | TypeRef::Json => "result.into()".to_string(),
136 _ => "result".to_string(),
137 }
138 };
139
140 let body = if !opaque_can_delegate {
141 let adapter_key = format!("{}.{}", type_name, method.name);
143 if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
144 adapter_body.clone()
145 } else if cfg.has_serde
146 && is_opaque
147 && !method.sanitized
148 && has_named_params(&method.params, opaque_types)
149 && method.error_type.is_some()
150 && crate::shared::is_opaque_delegatable_type(&method.return_type)
151 {
152 let err_conv = match cfg.async_pattern {
154 AsyncPattern::Pyo3FutureIntoPy => {
155 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
156 }
157 AsyncPattern::NapiNativeAsync => {
158 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
159 }
160 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
161 _ => ".map_err(|e| e.to_string())",
162 };
163 let serde_bindings =
164 gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, " ");
165 let serde_call_args = gen_call_args_with_let_bindings(&method.params, opaque_types);
166 let core_call = format!("self.inner.{}({serde_call_args})", method.name);
167 if matches!(method.return_type, TypeRef::Unit) {
168 format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
169 } else {
170 let wrap = wrap_return(
171 "result",
172 &method.return_type,
173 type_name,
174 opaque_types,
175 is_opaque,
176 method.returns_ref,
177 );
178 format!("{serde_bindings}let result = {core_call}{err_conv}?;\n Ok({wrap})")
179 }
180 } else if !is_opaque
181 && !method.sanitized
182 && method
183 .params
184 .iter()
185 .all(|p| !p.sanitized && is_simple_non_opaque_param(&p.ty))
186 && crate::shared::is_delegatable_return(&method.return_type)
187 {
188 let field_conversions = gen_lossy_binding_to_core_fields(typ, cfg.core_import);
191 let core_call = format!("core_self.{}({call_args})", method.name);
192 let result_wrap = match &method.return_type {
193 TypeRef::Named(n) if n == type_name => ".into()".to_string(),
194 TypeRef::Named(_) => ".into()".to_string(),
195 TypeRef::String | TypeRef::Bytes | TypeRef::Path => ".into()".to_string(),
196 _ => String::new(),
197 };
198 if method.error_type.is_some() {
199 let err_conv = match cfg.async_pattern {
200 AsyncPattern::Pyo3FutureIntoPy => {
201 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
202 }
203 AsyncPattern::NapiNativeAsync => {
204 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
205 }
206 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
207 _ => ".map_err(|e| e.to_string())",
208 };
209 format!("{field_conversions}let result = {core_call}{err_conv}?;\n Ok(result{result_wrap})")
210 } else {
211 format!("{field_conversions}{core_call}{result_wrap}")
212 }
213 } else {
214 gen_unimplemented_body(
215 &method.return_type,
216 &format!("{type_name}.{}", method.name),
217 method.error_type.is_some(),
218 cfg,
219 &method.params,
220 )
221 }
222 } else if method.is_async {
223 let inner_clone_line = if is_opaque {
224 "let inner = self.inner.clone();\n "
225 } else {
226 ""
227 };
228 let core_call_str = make_async_core_call(&method.name);
229 gen_async_body(
230 &core_call_str,
231 cfg,
232 method.error_type.is_some(),
233 &async_result_wrap,
234 is_opaque,
235 inner_clone_line,
236 matches!(method.return_type, TypeRef::Unit),
237 )
238 } else {
239 let core_call = make_core_call(&method.name);
240 if method.error_type.is_some() {
241 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 if is_opaque {
253 if matches!(method.return_type, TypeRef::Unit) {
254 format!("{core_call}{err_conv}?;\n Ok(())")
256 } else {
257 let wrap = wrap_return(
258 "result",
259 &method.return_type,
260 type_name,
261 opaque_types,
262 is_opaque,
263 method.returns_ref,
264 );
265 format!("let result = {core_call}{err_conv}?;\n Ok({wrap})")
266 }
267 } else {
268 format!("{core_call}{err_conv}")
269 }
270 } else if is_opaque {
271 wrap_return(
272 &core_call,
273 &method.return_type,
274 type_name,
275 opaque_types,
276 is_opaque,
277 method.returns_ref,
278 )
279 } else {
280 core_call
281 }
282 };
283
284 let needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
285 let self_param = match (needs_py, params.is_empty()) {
286 (true, true) => "&self, py: Python<'py>",
287 (true, false) => "&self, py: Python<'py>, ",
288 (false, true) => "&self",
289 (false, false) => "&self, ",
290 };
291
292 let ret = if needs_py {
295 "PyResult<Bound<'py, PyAny>>".to_string()
296 } else {
297 ret
298 };
299 let method_lifetime = if needs_py { "<'py>" } else { "" };
300
301 let (sig_start, sig_params, sig_end) = if self_param.len() + params.len() > 100 {
303 let wrapped_params = method
304 .params
305 .iter()
306 .map(|p| {
307 let ty = if p.optional {
308 format!("Option<{}>", mapper.map_type(&p.ty))
309 } else {
310 mapper.map_type(&p.ty)
311 };
312 format!("{}: {}", p.name, ty)
313 })
314 .collect::<Vec<_>>()
315 .join(",\n ");
316 let py_param = if needs_py { "\n py: Python<'py>," } else { "" };
317 (
318 format!(
319 "pub fn {}{method_lifetime}(\n &self,{}\n ",
320 method.name, py_param
321 ),
322 wrapped_params,
323 "\n ) -> ".to_string(),
324 )
325 } else {
326 (
327 format!("pub fn {}{method_lifetime}({}", method.name, self_param),
328 params,
329 ") -> ".to_string(),
330 )
331 };
332
333 let mut out = String::with_capacity(1024);
334 let total_params = method.params.len() + 1 + if needs_py { 1 } else { 0 };
336 if total_params > 7 {
337 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
338 }
339 if method.error_type.is_some() {
341 writeln!(out, " #[allow(clippy::missing_errors_doc)]").ok();
342 }
343 if is_trait_method_name(&method.name) {
345 writeln!(out, " #[allow(clippy::should_implement_trait)]").ok();
346 }
347 if cfg.needs_signature {
348 let sig = function_sig_defaults(&method.params);
349 writeln!(out, " {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
350 }
351 write!(
352 out,
353 " {}{}{}{} {{\n \
354 {body}\n }}",
355 sig_start, sig_params, sig_end, ret,
356 )
357 .ok();
358 out
359}
360
361pub fn gen_static_method(
363 method: &MethodDef,
364 mapper: &dyn TypeMapper,
365 cfg: &RustBindingConfig,
366 typ: &TypeDef,
367 adapter_bodies: &AdapterBodies,
368 opaque_types: &AHashSet<String>,
369) -> String {
370 let type_name = &typ.name;
371 let core_type_path = typ.rust_path.replace('-', "_");
373 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
374 let params = function_params(&method.params, &map_fn);
375 let return_type = mapper.map_type(&method.return_type);
376 let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
377
378 let call_args = gen_call_args(&method.params, opaque_types);
379
380 let can_delegate = crate::shared::can_auto_delegate(method, opaque_types);
381
382 let body = if !can_delegate {
383 let adapter_key = format!("{}.{}", type_name, method.name);
385 if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
386 adapter_body.clone()
387 } else {
388 gen_unimplemented_body(
389 &method.return_type,
390 &format!("{type_name}::{}", method.name),
391 method.error_type.is_some(),
392 cfg,
393 &method.params,
394 )
395 }
396 } else if method.is_async {
397 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
398 let return_wrap = format!("{return_type}::from(result)");
399 gen_async_body(
400 &core_call,
401 cfg,
402 method.error_type.is_some(),
403 &return_wrap,
404 false,
405 "",
406 matches!(method.return_type, TypeRef::Unit),
407 )
408 } else {
409 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
410 if method.error_type.is_some() {
411 let err_conv = match cfg.async_pattern {
413 AsyncPattern::Pyo3FutureIntoPy => {
414 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
415 }
416 AsyncPattern::NapiNativeAsync => {
417 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
418 }
419 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
420 _ => ".map_err(|e| e.to_string())",
421 };
422 let wrapped = wrap_return(
424 "val",
425 &method.return_type,
426 type_name,
427 opaque_types,
428 typ.is_opaque,
429 method.returns_ref,
430 );
431 if wrapped == "val" {
432 format!("{core_call}{err_conv}")
433 } else {
434 format!("{core_call}.map(|val| {wrapped}){err_conv}")
435 }
436 } else {
437 wrap_return(
439 &core_call,
440 &method.return_type,
441 type_name,
442 opaque_types,
443 typ.is_opaque,
444 method.returns_ref,
445 )
446 }
447 };
448
449 let static_needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
450
451 let ret = if static_needs_py {
453 "PyResult<Bound<'py, PyAny>>".to_string()
454 } else {
455 ret
456 };
457 let method_lifetime = if static_needs_py { "<'py>" } else { "" };
458
459 let (sig_start, sig_params, sig_end) = if params.len() > 100 {
461 let wrapped_params = method
462 .params
463 .iter()
464 .map(|p| {
465 let ty = if p.optional {
466 format!("Option<{}>", mapper.map_type(&p.ty))
467 } else {
468 mapper.map_type(&p.ty)
469 };
470 format!("{}: {}", p.name, ty)
471 })
472 .collect::<Vec<_>>()
473 .join(",\n ");
474 if static_needs_py {
476 (
477 format!("pub fn {}{method_lifetime}(py: Python<'py>,\n ", method.name),
478 wrapped_params,
479 "\n ) -> ".to_string(),
480 )
481 } else {
482 (
483 format!("pub fn {}(\n ", method.name),
484 wrapped_params,
485 "\n ) -> ".to_string(),
486 )
487 }
488 } else if static_needs_py {
489 (
490 format!("pub fn {}{method_lifetime}(py: Python<'py>, ", method.name),
491 params,
492 ") -> ".to_string(),
493 )
494 } else {
495 (format!("pub fn {}(", method.name), params, ") -> ".to_string())
496 };
497
498 let mut out = String::with_capacity(1024);
499 let total_params = method.params.len() + if static_needs_py { 1 } else { 0 };
501 if total_params > 7 {
502 writeln!(out, " #[allow(clippy::too_many_arguments)]").ok();
503 }
504 if method.error_type.is_some() {
506 writeln!(out, " #[allow(clippy::missing_errors_doc)]").ok();
507 }
508 if is_trait_method_name(&method.name) {
510 writeln!(out, " #[allow(clippy::should_implement_trait)]").ok();
511 }
512 if let Some(attr) = cfg.static_attr {
513 writeln!(out, " #[{attr}]").ok();
514 }
515 if cfg.needs_signature {
516 let sig = function_sig_defaults(&method.params);
517 writeln!(out, " {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
518 }
519 write!(
520 out,
521 " {}{}{}{} {{\n \
522 {body}\n }}",
523 sig_start, sig_params, sig_end, ret,
524 )
525 .ok();
526 out
527}
528
529pub fn gen_impl_block(
531 typ: &TypeDef,
532 mapper: &dyn TypeMapper,
533 cfg: &RustBindingConfig,
534 adapter_bodies: &AdapterBodies,
535 opaque_types: &AHashSet<String>,
536) -> String {
537 let (instance, statics) = partition_methods(&typ.methods);
538 if instance.is_empty() && statics.is_empty() && typ.fields.is_empty() {
539 return String::new();
540 }
541
542 let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
543 let mut out = String::with_capacity(2048);
544 if let Some(block_attr) = cfg.method_block_attr {
545 writeln!(out, "#[{block_attr}]").ok();
546 }
547 writeln!(out, "impl {prefixed_name} {{").ok();
548
549 if !typ.fields.is_empty() {
551 out.push_str(&gen_constructor(typ, mapper, cfg));
552 out.push_str("\n\n");
553 }
554
555 for m in &instance {
557 out.push_str(&gen_method(m, mapper, cfg, typ, false, opaque_types, adapter_bodies));
558 out.push_str("\n\n");
559 }
560
561 for m in &statics {
563 out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
564 out.push_str("\n\n");
565 }
566
567 let trimmed = out.trim_end();
569 let mut result = trimmed.to_string();
570 result.push_str("\n}");
571 result
572}
573
574pub fn gen_opaque_impl_block(
579 typ: &TypeDef,
580 mapper: &dyn TypeMapper,
581 cfg: &RustBindingConfig,
582 opaque_types: &AHashSet<String>,
583 adapter_bodies: &AdapterBodies,
584) -> String {
585 let (instance, statics) = partition_methods(&typ.methods);
586 if instance.is_empty() && statics.is_empty() {
587 return String::new();
588 }
589
590 let mut out = String::with_capacity(2048);
591 let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
592 if let Some(block_attr) = cfg.method_block_attr {
593 writeln!(out, "#[{block_attr}]").ok();
594 }
595 writeln!(out, "impl {prefixed_name} {{").ok();
596
597 for m in &instance {
599 out.push_str(&gen_method(m, mapper, cfg, typ, true, opaque_types, adapter_bodies));
600 out.push_str("\n\n");
601 }
602
603 for m in &statics {
605 out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
606 out.push_str("\n\n");
607 }
608
609 let trimmed = out.trim_end();
610 let mut result = trimmed.to_string();
611 result.push_str("\n}");
612 result
613}