1use crate::generators::binding_helpers::{
2 gen_async_body, gen_call_args, gen_call_args_cfg, gen_call_args_with_let_bindings, gen_named_let_bindings,
3 gen_named_let_bindings_by_ref, gen_serde_let_bindings, gen_unimplemented_body, has_named_params,
4};
5use crate::generators::{AdapterBodies, AsyncPattern, RustBindingConfig};
6use crate::shared::{function_params, function_sig_defaults};
7use crate::type_mapper::TypeMapper;
8use ahash::{AHashMap, AHashSet};
9use alef_core::ir::{ApiSurface, FunctionDef, TypeRef};
10use std::fmt::Write;
11
12pub fn gen_function(
14 func: &FunctionDef,
15 mapper: &dyn TypeMapper,
16 cfg: &RustBindingConfig,
17 adapter_bodies: &AdapterBodies,
18 opaque_types: &AHashSet<String>,
19) -> String {
20 let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
21 let params = if cfg.named_non_opaque_params_by_ref {
28 let mut seen_optional = false;
29 func.params
30 .iter()
31 .enumerate()
32 .map(|(idx, p)| {
33 if p.optional {
34 seen_optional = true;
35 }
36 let promoted = seen_optional && !p.optional && crate::shared::is_promoted_optional(&func.params, idx);
37 let ty = match &p.ty {
38 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
39 if p.optional || seen_optional || promoted {
40 format!("Nullable<&{}>", map_fn(&p.ty))
41 } else {
42 format!("&{}", map_fn(&p.ty))
43 }
44 }
45 _ => {
46 if p.optional || seen_optional {
47 format!("Option<{}>", map_fn(&p.ty))
48 } else {
49 map_fn(&p.ty)
50 }
51 }
52 };
53 format!("{}: {}", p.name, ty)
54 })
55 .collect::<Vec<_>>()
56 .join(", ")
57 } else {
58 function_params(&func.params, &map_fn)
59 };
60 let return_type = mapper.map_type(&func.return_type);
61 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
62
63 let effective_params: std::borrow::Cow<[alef_core::ir::ParamDef]> = if cfg.named_non_opaque_params_by_ref {
67 let modified: Vec<alef_core::ir::ParamDef> = func
68 .params
69 .iter()
70 .map(|p| {
71 if matches!(&p.ty, TypeRef::Named(n) if !opaque_types.contains(n.as_str())) {
72 alef_core::ir::ParamDef {
73 is_ref: true,
74 ..p.clone()
75 }
76 } else {
77 p.clone()
78 }
79 })
80 .collect();
81 std::borrow::Cow::Owned(modified)
82 } else {
83 std::borrow::Cow::Borrowed(&func.params)
84 };
85 let use_let_bindings = has_named_params(&effective_params, opaque_types);
86 let call_args = if use_let_bindings {
87 gen_call_args_with_let_bindings(&effective_params, opaque_types)
88 } else if cfg.cast_uints_to_i32 || cfg.cast_large_ints_to_f64 {
89 gen_call_args_cfg(
90 &effective_params,
91 opaque_types,
92 cfg.cast_uints_to_i32,
93 cfg.cast_large_ints_to_f64,
94 )
95 } else {
96 gen_call_args(&effective_params, opaque_types)
97 };
98 let core_import = cfg.core_import;
99 let let_bindings = if use_let_bindings {
100 if cfg.named_non_opaque_params_by_ref {
101 gen_named_let_bindings_by_ref(&func.params, opaque_types, core_import)
103 } else {
104 gen_named_let_bindings(&func.params, opaque_types, core_import)
105 }
106 } else {
107 String::new()
108 };
109
110 let core_fn_path = {
112 let path = func.rust_path.replace('-', "_");
113 if path.starts_with(core_import) {
114 path
115 } else {
116 format!("{core_import}::{}", func.name)
117 }
118 };
119
120 let can_delegate = crate::shared::can_auto_delegate_function(func, opaque_types)
121 || can_delegate_with_named_let_bindings(func, opaque_types);
122
123 let serde_err_conv = match cfg.async_pattern {
125 AsyncPattern::Pyo3FutureIntoPy => ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))",
126 AsyncPattern::NapiNativeAsync => ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))",
127 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
128 AsyncPattern::TokioBlockOn => {
129 ".map_err(|e| extendr_api::Error::Other(e.to_string().replace(\":\", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").chars().take(255).collect::<String>()))"
130 }
131 _ => ".map_err(|e| e.to_string())",
132 };
133
134 let body = if !can_delegate {
136 if let Some(adapter_body) = adapter_bodies.get(&func.name) {
138 adapter_body.clone()
139 } else if cfg.has_serde && use_let_bindings && func.error_type.is_some() {
140 let is_async_pyo3 = func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
145 let (serde_indent, serde_err_async) = if is_async_pyo3 {
146 (
147 " ",
148 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))",
149 )
150 } else {
151 (" ", serde_err_conv)
152 };
153 let serde_bindings =
154 gen_serde_let_bindings(&func.params, opaque_types, core_import, serde_err_async, serde_indent);
155 let core_call = format!("{core_fn_path}({call_args})");
156
157 let returns_ref = func.returns_ref;
159 let wrap_return = |expr: &str| -> String {
160 match &func.return_type {
161 TypeRef::Vec(inner) => {
162 match inner.as_ref() {
164 TypeRef::Named(_) => {
165 format!("{expr}.into_iter().map(Into::into).collect()")
167 }
168 _ => expr.to_string(),
169 }
170 }
171 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
172 if returns_ref {
173 format!("{name} {{ inner: Arc::new({expr}.clone()) }}")
174 } else {
175 format!("{name} {{ inner: Arc::new({expr}) }}")
176 }
177 }
178 TypeRef::Named(_) => {
179 if returns_ref {
181 format!("{return_type}::from({expr}.clone())")
182 } else {
183 format!("{return_type}::from({expr})")
184 }
185 }
186 TypeRef::String | TypeRef::Bytes => expr.to_string(),
189 TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
190 TypeRef::Json => format!("{expr}.to_string()"),
191 _ => expr.to_string(),
192 }
193 };
194
195 if is_async_pyo3 {
196 let is_unit = matches!(func.return_type, TypeRef::Unit);
198 let wrapped = wrap_return("result");
199 let core_await = format!(
200 "{core_call}.await\n .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?"
201 );
202 let inner_body = if is_unit {
203 format!("{serde_bindings}{core_await};\n Ok(())")
204 } else {
205 if wrapped.contains(".into()") || wrapped.contains("::from(") || wrapped.contains("Into::into") {
209 format!(
211 "{serde_bindings}let result = {core_await};\n let wrapped_result: {return_type} = {wrapped};\n Ok(wrapped_result)"
212 )
213 } else {
214 format!("{serde_bindings}let result = {core_await};\n Ok({wrapped})")
215 }
216 };
217 format!("pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n{inner_body}\n }})")
218 } else if func.is_async {
219 let is_unit = matches!(func.return_type, TypeRef::Unit);
221 let wrapped = wrap_return("result");
222 let async_body = gen_async_body(
223 &core_call,
224 cfg,
225 func.error_type.is_some(),
226 &wrapped,
227 false,
228 "",
229 is_unit,
230 Some(&return_type),
231 );
232 format!("{serde_bindings}{async_body}")
233 } else if matches!(func.return_type, TypeRef::Unit) {
234 let await_kw = if func.is_async { ".await" } else { "" };
236 let debug_marker = if func.is_async { "/*ASYNC_UNIT*/ " } else { "" };
237 format!("{serde_bindings}{debug_marker}{core_call}{await_kw}{serde_err_conv}?;\n Ok(())")
238 } else {
239 let wrapped = wrap_return("val");
240 let await_kw = if func.is_async { ".await" } else { "" };
241 if wrapped == "val" {
242 format!("{serde_bindings}{core_call}{await_kw}{serde_err_conv}")
243 } else if wrapped == "val.into()" {
244 format!("{serde_bindings}{core_call}{await_kw}.map(Into::into){serde_err_conv}")
245 } else if let Some(type_path) = wrapped.strip_suffix("::from(val)") {
246 format!("{serde_bindings}{core_call}{await_kw}.map({type_path}::from){serde_err_conv}")
247 } else {
248 format!("{serde_bindings}{core_call}{await_kw}.map(|val| {wrapped}){serde_err_conv}")
249 }
250 }
251 } else if func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy {
252 let suppress = if func.params.is_empty() {
254 String::new()
255 } else {
256 let names: Vec<&str> = func.params.iter().map(|p| p.name.as_str()).collect();
257 format!("let _ = ({});\n ", names.join(", "))
258 };
259 format!(
260 "{suppress}Err(pyo3::exceptions::PyNotImplementedError::new_err(\"not implemented: {}\"))",
261 func.name
262 )
263 } else {
264 gen_unimplemented_body(
266 &func.return_type,
267 &func.name,
268 func.error_type.is_some(),
269 cfg,
270 &func.params,
271 opaque_types,
272 )
273 }
274 } else if func.is_async {
275 let core_call = format!("{core_fn_path}({call_args})");
277 let return_wrap = match &func.return_type {
280 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
281 format!("{n} {{ inner: Arc::new(result) }}")
282 }
283 TypeRef::Named(_) => {
284 format!("{return_type}::from(result)")
285 }
286 TypeRef::Vec(inner) => match inner.as_ref() {
287 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
288 format!("result.into_iter().map(|v| {n} {{ inner: Arc::new(v) }}).collect::<Vec<_>>()")
289 }
290 TypeRef::Named(_) => {
291 let inner_mapped = mapper.map_type(inner);
292 format!("result.into_iter().map({inner_mapped}::from).collect::<Vec<_>>()")
293 }
294 _ => "result".to_string(),
295 },
296 TypeRef::Unit => "result".to_string(),
297 _ => super::binding_helpers::wrap_return(
298 "result",
299 &func.return_type,
300 "",
301 opaque_types,
302 false,
303 func.returns_ref,
304 false,
305 ),
306 };
307 let async_body = gen_async_body(
308 &core_call,
309 cfg,
310 func.error_type.is_some(),
311 &return_wrap,
312 false,
313 "",
314 matches!(func.return_type, TypeRef::Unit),
315 Some(&return_type),
316 );
317 format!("{let_bindings}{async_body}")
318 } else {
319 let core_call = format!("{core_fn_path}({call_args})");
320
321 let returns_ref = func.returns_ref;
323 let wrap_return = |expr: &str| -> String {
324 match &func.return_type {
325 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
327 if returns_ref {
328 format!("{name} {{ inner: Arc::new({expr}.clone()) }}")
329 } else {
330 format!("{name} {{ inner: Arc::new({expr}) }}")
331 }
332 }
333 TypeRef::Named(_name) => {
335 if returns_ref {
336 format!("{expr}.clone().into()")
337 } else {
338 format!("{expr}.into()")
339 }
340 }
341 TypeRef::String | TypeRef::Bytes => {
343 if returns_ref {
344 format!("{expr}.into()")
345 } else {
346 expr.to_string()
347 }
348 }
349 TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
351 TypeRef::Json => format!("{expr}.to_string()"),
353 TypeRef::Optional(inner) => match inner.as_ref() {
355 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
356 if returns_ref {
357 format!("{expr}.map(|v| {name} {{ inner: Arc::new(v.clone()) }})")
358 } else {
359 format!("{expr}.map(|v| {name} {{ inner: Arc::new(v) }})")
360 }
361 }
362 TypeRef::Named(_) => {
363 if returns_ref {
364 format!("{expr}.map(|v| v.clone().into())")
365 } else {
366 format!("{expr}.map(Into::into)")
367 }
368 }
369 TypeRef::Path => {
370 format!("{expr}.map(|v| v.to_string_lossy().to_string())")
371 }
372 TypeRef::String | TypeRef::Bytes => {
373 if returns_ref {
374 format!("{expr}.map(Into::into)")
375 } else {
376 expr.to_string()
377 }
378 }
379 TypeRef::Vec(vi) => match vi.as_ref() {
380 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
381 format!("{expr}.map(|v| v.into_iter().map(|x| {name} {{ inner: Arc::new(x) }}).collect())")
382 }
383 TypeRef::Named(_) => {
384 format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
385 }
386 _ => expr.to_string(),
387 },
388 _ => expr.to_string(),
389 },
390 TypeRef::Vec(inner) => match inner.as_ref() {
392 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
393 if returns_ref {
394 format!("{expr}.into_iter().map(|v| {name} {{ inner: Arc::new(v.clone()) }}).collect()")
395 } else {
396 format!("{expr}.into_iter().map(|v| {name} {{ inner: Arc::new(v) }}).collect()")
397 }
398 }
399 TypeRef::Named(_) => {
400 if returns_ref {
401 format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
402 } else {
403 format!("{expr}.into_iter().map(Into::into).collect()")
404 }
405 }
406 TypeRef::Path => {
407 format!("{expr}.into_iter().map(|v| v.to_string_lossy().to_string()).collect()")
408 }
409 TypeRef::String | TypeRef::Bytes => {
410 if returns_ref {
411 format!("{expr}.into_iter().map(Into::into).collect()")
412 } else {
413 expr.to_string()
414 }
415 }
416 _ => expr.to_string(),
417 },
418 _ => expr.to_string(),
419 }
420 };
421
422 if func.error_type.is_some() {
423 let err_conv = match cfg.async_pattern {
425 AsyncPattern::Pyo3FutureIntoPy => {
426 ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
427 }
428 AsyncPattern::NapiNativeAsync => {
429 ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
430 }
431 AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
432 AsyncPattern::TokioBlockOn => {
433 ".map_err(|e| extendr_api::Error::Other(e.to_string().replace(\":\", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").chars().take(255).collect::<String>()))"
434 }
435 _ => ".map_err(|e| e.to_string())",
436 };
437 let wrapped = wrap_return("val");
438 if wrapped == "val" {
439 format!("{core_call}{err_conv}")
440 } else if wrapped == "val.into()" {
441 format!("{core_call}.map(Into::into){err_conv}")
442 } else if let Some(type_path) = wrapped.strip_suffix("::from(val)") {
443 format!("{core_call}.map({type_path}::from){err_conv}")
444 } else {
445 format!("{core_call}.map(|val| {wrapped}){err_conv}")
446 }
447 } else {
448 wrap_return(&core_call)
449 }
450 };
451
452 let body = if !let_bindings.is_empty() && !func.is_async {
456 if can_delegate {
457 format!("{let_bindings}{body}")
458 } else {
459 let vec_str_bindings: String = func.params.iter().filter(|p| {
462 p.is_ref && matches!(&p.ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char))
463 }).map(|p| {
464 if p.optional {
467 format!("let {}_refs: Vec<&str> = {}.as_ref().map(|v| v.iter().map(|s| s.as_str()).collect()).unwrap_or_default();\n ", p.name, p.name)
468 } else {
469 format!("let {}_refs: Vec<&str> = {}.iter().map(|s| s.as_str()).collect();\n ", p.name, p.name)
470 }
471 }).collect();
472 if !vec_str_bindings.is_empty() {
473 format!("{vec_str_bindings}{body}")
474 } else {
475 body
476 }
477 }
478 } else {
479 body
480 };
481
482 let async_kw = if func.is_async && cfg.async_pattern != AsyncPattern::TokioBlockOn {
486 "async "
487 } else {
488 ""
489 };
490 let func_needs_py = func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
491
492 let ret = if func_needs_py {
494 "PyResult<Bound<'py, PyAny>>".to_string()
495 } else {
496 ret
497 };
498 let func_lifetime = if func_needs_py { "<'py>" } else { "" };
499
500 let (func_sig, _params_formatted) = if params.len() > 100 {
501 let mut seen_optional = false;
503 let wrapped_params = func
504 .params
505 .iter()
506 .map(|p| {
507 if p.optional {
508 seen_optional = true;
509 }
510 let ty = if p.optional || seen_optional {
511 format!("Option<{}>", mapper.map_type(&p.ty))
512 } else {
513 mapper.map_type(&p.ty)
514 };
515 format!("{}: {}", p.name, ty)
516 })
517 .collect::<Vec<_>>()
518 .join(",\n ");
519
520 if func_needs_py {
522 (
523 format!(
524 "pub fn {}{func_lifetime}(py: Python<'py>,\n {}\n) -> {ret}",
525 func.name,
526 wrapped_params,
527 ret = ret
528 ),
529 "",
530 )
531 } else {
532 (
533 format!(
534 "pub {async_kw}fn {}(\n {}\n) -> {ret}",
535 func.name,
536 wrapped_params,
537 ret = ret
538 ),
539 "",
540 )
541 }
542 } else if func_needs_py {
543 (
544 format!(
545 "pub fn {}{func_lifetime}(py: Python<'py>, {params}) -> {ret}",
546 func.name
547 ),
548 "",
549 )
550 } else {
551 (format!("pub {async_kw}fn {}({params}) -> {ret}", func.name), "")
552 };
553
554 let mut out = String::with_capacity(1024);
555 let total_params = func.params.len() + if func_needs_py { 1 } else { 0 };
557 if total_params > 7 {
558 writeln!(out, "#[allow(clippy::too_many_arguments)]").ok();
559 }
560 if func.error_type.is_some() {
562 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
563 }
564 let attr_inner = cfg
565 .function_attr
566 .trim_start_matches('#')
567 .trim_start_matches('[')
568 .trim_end_matches(']');
569 writeln!(out, "#[{attr_inner}]").ok();
570 if cfg.needs_signature {
571 let sig = function_sig_defaults(&func.params);
572 writeln!(out, "{}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
573 }
574 write!(out, "{} {{\n {body}\n}}", func_sig,).ok();
575 out
576}
577
578fn can_delegate_with_named_let_bindings(func: &FunctionDef, opaque_types: &AHashSet<String>) -> bool {
579 !func.sanitized
580 && func
581 .params
582 .iter()
583 .all(|p| !p.sanitized && crate::shared::is_delegatable_param(&p.ty, opaque_types))
584 && crate::shared::is_delegatable_return(&func.return_type)
585}
586
587pub fn collect_trait_imports(api: &ApiSurface) -> Vec<String> {
594 let mut traits: AHashSet<String> = AHashSet::new();
599 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
600 for method in &typ.methods {
601 if let Some(ref trait_path) = method.trait_source {
602 traits.insert(trait_path.clone());
603 }
604 }
605 }
606
607 let mut by_name: AHashMap<String, String> = AHashMap::new();
609 for path in traits {
610 let name = path.split("::").last().unwrap_or(&path).to_string();
611 let entry = by_name.entry(name).or_insert_with(|| path.clone());
612 if path.len() < entry.len() {
614 *entry = path;
615 }
616 }
617
618 let mut sorted: Vec<String> = by_name.into_values().collect();
619 sorted.sort();
620 sorted
621}
622
623pub fn has_unresolved_trait_methods(api: &ApiSurface) -> bool {
629 let mut method_counts: AHashMap<&str, (usize, usize)> = AHashMap::new(); for typ in api.types.iter().filter(|typ| !typ.is_trait) {
634 if typ.is_trait {
635 continue;
636 }
637 for method in &typ.methods {
638 let entry = method_counts.entry(&method.name).or_insert((0, 0));
639 entry.0 += 1;
640 if method.trait_source.is_some() {
641 entry.1 += 1;
642 }
643 }
644 }
645 method_counts
647 .values()
648 .any(|&(total, with_source)| total >= 3 && with_source == 0)
649}
650
651pub fn collect_explicit_core_imports(api: &ApiSurface) -> Vec<String> {
662 let mut names = std::collections::BTreeSet::new();
663 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
664 names.insert(typ.name.clone());
665 }
666 for e in &api.enums {
667 names.insert(e.name.clone());
668 }
669 names.into_iter().collect()
670}