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