Skip to main content

alef_codegen/generators/
functions.rs

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
11/// Detect whether the core-call expression already evaluates to `Arc<T>` for the
12/// binding's `inner` field. Used to avoid wrapping `Self { inner: Arc::new(self.inner.clone()) }`
13/// where `self.inner` is already `Arc<T>`.
14fn expr_is_already_arc(expr: &str) -> bool {
15    let trimmed = expr.trim();
16    trimmed == "self.inner"
17        || trimmed == "self.inner.clone()"
18        || trimmed.starts_with("self.inner.as_ref()")
19        || trimmed.starts_with("self.inner.clone()")
20}
21
22/// Build the Arc-wrapping expression for an opaque type's `inner` field. Wraps in a
23/// `Mutex` when the opaque type has `&mut self` methods (signalled by `mutex_types`).
24fn arc_wrap_expr(val: &str, name: &str, mutex_types: &AHashSet<String>) -> String {
25    if mutex_types.contains(name) {
26        format!("Arc::new(std::sync::Mutex::new({val}))")
27    } else {
28        format!("Arc::new({val})")
29    }
30}
31
32/// Generate a free function. Equivalent to `gen_function_with_mutex` with no mutex types.
33pub fn gen_function(
34    func: &FunctionDef,
35    mapper: &dyn TypeMapper,
36    cfg: &RustBindingConfig,
37    adapter_bodies: &AdapterBodies,
38    opaque_types: &AHashSet<String>,
39) -> String {
40    gen_function_with_mutex(func, mapper, cfg, adapter_bodies, opaque_types, &AHashSet::new())
41}
42
43/// Generate a free function. `mutex_types` is the subset of opaque types whose `inner`
44/// field is `Arc<Mutex<T>>` (because the type has `&mut self` methods); their
45/// constructors emit `Arc::new(Mutex::new(v))` instead of `Arc::new(v)`.
46pub fn gen_function_with_mutex(
47    func: &FunctionDef,
48    mapper: &dyn TypeMapper,
49    cfg: &RustBindingConfig,
50    adapter_bodies: &AdapterBodies,
51    opaque_types: &AHashSet<String>,
52    mutex_types: &AHashSet<String>,
53) -> String {
54    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
55    // When named_non_opaque_params_by_ref is true (extendr backend), Named non-opaque struct
56    // params must use references because extendr only generates TryFrom<&Robj> for &T.
57    // - Required params: `&T` (extendr generates TryFrom<&Robj> for &T)
58    // - Optional params: `Nullable<&T>` (extendr's Nullable<T: TryFrom<&Robj>>)
59    // - Promoted-optional (required following optional): `Nullable<&T>` (treated as optional)
60    // After the first optional/Nullable param, all subsequent params are also promoted.
61    let params = if cfg.named_non_opaque_params_by_ref {
62        let mut seen_optional = false;
63        func.params
64            .iter()
65            .enumerate()
66            .map(|(idx, p)| {
67                if p.optional {
68                    seen_optional = true;
69                }
70                let promoted = seen_optional && !p.optional && crate::shared::is_promoted_optional(&func.params, idx);
71                let ty = match &p.ty {
72                    TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
73                        if p.optional || seen_optional || promoted {
74                            format!("Nullable<&{}>", map_fn(&p.ty))
75                        } else {
76                            format!("&{}", map_fn(&p.ty))
77                        }
78                    }
79                    _ => {
80                        if p.optional || seen_optional {
81                            format!("Option<{}>", map_fn(&p.ty))
82                        } else {
83                            map_fn(&p.ty)
84                        }
85                    }
86                };
87                format!("{}: {}", p.name, ty)
88            })
89            .collect::<Vec<_>>()
90            .join(", ")
91    } else {
92        function_params(&func.params, &map_fn)
93    };
94    let return_type = mapper.map_type(&func.return_type);
95    let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
96
97    // Use let-binding pattern for non-opaque Named params so core fns can take &CoreType.
98    // When named_non_opaque_params_by_ref is set (extendr), the binding signature uses &T,
99    // so we simulate is_ref=true for Named non-opaque params when generating call args.
100    let effective_params: std::borrow::Cow<[alef_core::ir::ParamDef]> = if cfg.named_non_opaque_params_by_ref {
101        let modified: Vec<alef_core::ir::ParamDef> = func
102            .params
103            .iter()
104            .map(|p| {
105                if matches!(&p.ty, TypeRef::Named(n) if !opaque_types.contains(n.as_str())) {
106                    alef_core::ir::ParamDef {
107                        is_ref: true,
108                        ..p.clone()
109                    }
110                } else {
111                    p.clone()
112                }
113            })
114            .collect();
115        std::borrow::Cow::Owned(modified)
116    } else {
117        std::borrow::Cow::Borrowed(&func.params)
118    };
119    let use_let_bindings = has_named_params(&effective_params, opaque_types);
120    let call_args = if use_let_bindings {
121        gen_call_args_with_let_bindings(&effective_params, opaque_types)
122    } else if cfg.cast_uints_to_i32 || cfg.cast_large_ints_to_f64 {
123        gen_call_args_cfg(
124            &effective_params,
125            opaque_types,
126            cfg.cast_uints_to_i32,
127            cfg.cast_large_ints_to_f64,
128        )
129    } else {
130        gen_call_args(&effective_params, opaque_types)
131    };
132    let core_import = cfg.core_import;
133    let let_bindings = if use_let_bindings {
134        if cfg.named_non_opaque_params_by_ref {
135            // Params are `&T` in the signature — use .clone().into() for conversion.
136            gen_named_let_bindings_by_ref(&func.params, opaque_types, core_import)
137        } else {
138            gen_named_let_bindings(&func.params, opaque_types, core_import)
139        }
140    } else {
141        String::new()
142    };
143
144    // Use the function's rust_path for correct module path resolution
145    let core_fn_path = {
146        let path = func.rust_path.replace('-', "_");
147        if path.starts_with(core_import) {
148            path
149        } else {
150            format!("{core_import}::{}", func.name)
151        }
152    };
153
154    let can_delegate = crate::shared::can_auto_delegate_function(func, opaque_types)
155        || can_delegate_with_named_let_bindings(func, opaque_types);
156
157    // Backend-specific error conversion string for serde bindings
158    let serde_err_conv = match cfg.async_pattern {
159        AsyncPattern::Pyo3FutureIntoPy => ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))",
160        AsyncPattern::NapiNativeAsync => ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))",
161        AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
162        AsyncPattern::TokioBlockOn => {
163            ".map_err(|e| extendr_api::Error::Other(e.to_string().replace(\":\", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").chars().take(255).collect::<String>()))"
164        }
165        _ => ".map_err(|e| e.to_string())",
166    };
167
168    // Generate the body based on async pattern
169    let body = if !can_delegate {
170        // Check if an adapter provides the body
171        if let Some(adapter_body) = adapter_bodies.get(&func.name) {
172            adapter_body.clone()
173        } else if cfg.has_serde && use_let_bindings && func.error_type.is_some() {
174            // MARKER_SERDE_PATH
175            // Serde-based param conversion: serialize binding types to JSON, deserialize to core types.
176            // This handles Named params (e.g., ProcessConfig) that lack binding→core From impls.
177            // For async functions with Pyo3FutureIntoPy, serde bindings use indented format.
178            let is_async_pyo3 = func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
179            let (serde_indent, serde_err_async) = if is_async_pyo3 {
180                (
181                    "        ",
182                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))",
183                )
184            } else {
185                ("    ", serde_err_conv)
186            };
187            let serde_bindings =
188                gen_serde_let_bindings(&func.params, opaque_types, core_import, serde_err_async, serde_indent);
189            let core_call = format!("{core_fn_path}({call_args})");
190
191            // Determine return wrapping strategy for serde async (uses explicit types to avoid E0283)
192            let returns_ref = func.returns_ref;
193            let wrap_return = |expr: &str| -> String {
194                match &func.return_type {
195                    TypeRef::Vec(inner) => {
196                        // Vec<T>: check if elements need conversion
197                        match inner.as_ref() {
198                            TypeRef::Named(_) => {
199                                // Vec<Named>: convert each element using Into::into
200                                format!("{expr}.into_iter().map(Into::into).collect()")
201                            }
202                            _ => expr.to_string(),
203                        }
204                    }
205                    TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
206                        let mapped_name = mapper.named(name);
207                        if returns_ref {
208                            format!("{mapped_name} {{ inner: Arc::new({expr}.clone()) }}")
209                        } else {
210                            format!("{mapped_name} {{ inner: Arc::new({expr}) }}")
211                        }
212                    }
213                    TypeRef::Named(_) => {
214                        // Use explicit type with ::from() to avoid E0283 type inference issues in async context
215                        if returns_ref {
216                            format!("{return_type}::from({expr}.clone())")
217                        } else {
218                            format!("{return_type}::from({expr})")
219                        }
220                    }
221                    // String/Bytes are identity across all backends (String->String,
222                    // Vec<u8>->Vec<u8>) — no .into() needed for owned values.
223                    TypeRef::String | TypeRef::Bytes => expr.to_string(),
224                    TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
225                    TypeRef::Json => format!("{expr}.to_string()"),
226                    _ => expr.to_string(),
227                }
228            };
229
230            if is_async_pyo3 {
231                // Async serde path: wrap everything in future_into_py
232                let is_unit = matches!(func.return_type, TypeRef::Unit);
233                let wrapped = wrap_return("result");
234                let core_await = format!(
235                    "{core_call}.await\n            .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?"
236                );
237                let inner_body = if is_unit {
238                    format!("{serde_bindings}{core_await};\n            Ok(())")
239                } else {
240                    // When wrapped contains type conversions like .into() or ::from(),
241                    // bind to a variable to help type inference for the generic future_into_py.
242                    // This avoids E0283 "type annotations needed".
243                    if wrapped.contains(".into()") || wrapped.contains("::from(") || wrapped.contains("Into::into") {
244                        // Add explicit type annotation to help type inference
245                        format!(
246                            "{serde_bindings}let result = {core_await};\n            let wrapped_result: {return_type} = {wrapped};\n            Ok(wrapped_result)"
247                        )
248                    } else {
249                        format!("{serde_bindings}let result = {core_await};\n            Ok({wrapped})")
250                    }
251                };
252                format!("pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n{inner_body}\n        }})")
253            } else if func.is_async {
254                // Async serde path for other backends (NAPI, etc.): use gen_async_body
255                let is_unit = matches!(func.return_type, TypeRef::Unit);
256                let wrapped = wrap_return("result");
257                let async_body = gen_async_body(
258                    &core_call,
259                    cfg,
260                    func.error_type.is_some(),
261                    &wrapped,
262                    false,
263                    "",
264                    is_unit,
265                    Some(&return_type),
266                );
267                format!("{serde_bindings}{async_body}")
268            } else if matches!(func.return_type, TypeRef::Unit) {
269                // Unit return with error: avoid let_unit_value
270                let await_kw = if func.is_async { ".await" } else { "" };
271                let debug_marker = if func.is_async { "/*ASYNC_UNIT*/ " } else { "" };
272                format!("{serde_bindings}{debug_marker}{core_call}{await_kw}{serde_err_conv}?;\n    Ok(())")
273            } else {
274                let wrapped = wrap_return("val");
275                let await_kw = if func.is_async { ".await" } else { "" };
276                if wrapped == "val" {
277                    format!("{serde_bindings}{core_call}{await_kw}{serde_err_conv}")
278                } else if wrapped == "val.into()" {
279                    format!("{serde_bindings}{core_call}{await_kw}.map(Into::into){serde_err_conv}")
280                } else if let Some(type_path) = wrapped.strip_suffix("::from(val)") {
281                    format!("{serde_bindings}{core_call}{await_kw}.map({type_path}::from){serde_err_conv}")
282                } else {
283                    format!("{serde_bindings}{core_call}{await_kw}.map(|val| {wrapped}){serde_err_conv}")
284                }
285            }
286        } else if func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy {
287            // Async function that can't be auto-delegated — wrap unimplemented body in future_into_py
288            let suppress = if func.params.is_empty() {
289                String::new()
290            } else {
291                let names: Vec<&str> = func.params.iter().map(|p| p.name.as_str()).collect();
292                format!("let _ = ({});\n        ", names.join(", "))
293            };
294            format!(
295                "{suppress}Err(pyo3::exceptions::PyNotImplementedError::new_err(\"not implemented: {}\"))",
296                func.name
297            )
298        } else {
299            // Function can't be auto-delegated — return a default/error based on return type
300            gen_unimplemented_body(
301                &func.return_type,
302                &func.name,
303                func.error_type.is_some(),
304                cfg,
305                &func.params,
306                opaque_types,
307            )
308        }
309    } else if func.is_async {
310        // MARKER_DELEGATE_ASYNC
311        let core_call = format!("{core_fn_path}({call_args})");
312        // In async contexts (future_into_py, etc.), the compiler often can't infer the
313        // target type for .into(). Use explicit From::from() / collect::<Vec<T>>() instead.
314        let return_wrap = match &func.return_type {
315            TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
316                let mapped_n = mapper.named(n);
317                let wrap = arc_wrap_expr("result", n, mutex_types);
318                format!("{mapped_n} {{ inner: {wrap} }}")
319            }
320            TypeRef::Named(_) => {
321                format!("{return_type}::from(result)")
322            }
323            TypeRef::Vec(inner) => match inner.as_ref() {
324                TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
325                    let mapped_n = mapper.named(n);
326                    let wrap = arc_wrap_expr("v", n, mutex_types);
327                    format!("result.into_iter().map(|v| {mapped_n} {{ inner: {wrap} }}).collect::<Vec<_>>()")
328                }
329                TypeRef::Named(_) => {
330                    let inner_mapped = mapper.map_type(inner);
331                    format!("result.into_iter().map({inner_mapped}::from).collect::<Vec<_>>()")
332                }
333                _ => "result".to_string(),
334            },
335            TypeRef::Unit => "result".to_string(),
336            _ => super::binding_helpers::wrap_return(
337                "result",
338                &func.return_type,
339                "",
340                opaque_types,
341                false,
342                func.returns_ref,
343                false,
344            ),
345        };
346        let async_body = gen_async_body(
347            &core_call,
348            cfg,
349            func.error_type.is_some(),
350            &return_wrap,
351            false,
352            "",
353            matches!(func.return_type, TypeRef::Unit),
354            Some(&return_type),
355        );
356        format!("{let_bindings}{async_body}")
357    } else {
358        let core_call = format!("{core_fn_path}({call_args})");
359
360        // Determine return wrapping strategy
361        let returns_ref = func.returns_ref;
362        let wrap_return = |expr: &str| -> String {
363            match &func.return_type {
364                // Opaque type return: wrap in Arc (or Arc<Mutex<_>> when the type
365                // has &mut self methods). Skip the wrap if `expr` is already Arc<T>.
366                TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
367                    let mapped_name = mapper.named(name);
368                    if expr_is_already_arc(expr) {
369                        format!("{mapped_name} {{ inner: {expr} }}")
370                    } else if returns_ref {
371                        let wrap = arc_wrap_expr(&format!("{expr}.clone()"), name, mutex_types);
372                        format!("{mapped_name} {{ inner: {wrap} }}")
373                    } else {
374                        let wrap = arc_wrap_expr(expr, name, mutex_types);
375                        format!("{mapped_name} {{ inner: {wrap} }}")
376                    }
377                }
378                // Non-opaque Named: use .into() if From impl exists
379                TypeRef::Named(_name) => {
380                    if returns_ref {
381                        format!("{expr}.clone().into()")
382                    } else {
383                        format!("{expr}.into()")
384                    }
385                }
386                // String/Bytes: .into() handles &str→String, skip for owned
387                TypeRef::String | TypeRef::Bytes => {
388                    if returns_ref {
389                        format!("{expr}.into()")
390                    } else {
391                        expr.to_string()
392                    }
393                }
394                // Path: PathBuf→String needs to_string_lossy
395                TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
396                // Json: serde_json::Value to string
397                TypeRef::Json => format!("{expr}.to_string()"),
398                // Optional with opaque inner
399                TypeRef::Optional(inner) => match inner.as_ref() {
400                    TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
401                        let mapped_name = mapper.named(name);
402                        if returns_ref {
403                            let wrap = arc_wrap_expr("v.clone()", name, mutex_types);
404                            format!("{expr}.map(|v| {mapped_name} {{ inner: {wrap} }})")
405                        } else {
406                            let wrap = arc_wrap_expr("v", name, mutex_types);
407                            format!("{expr}.map(|v| {mapped_name} {{ inner: {wrap} }})")
408                        }
409                    }
410                    TypeRef::Named(_) => {
411                        if returns_ref {
412                            format!("{expr}.map(|v| v.clone().into())")
413                        } else {
414                            format!("{expr}.map(Into::into)")
415                        }
416                    }
417                    TypeRef::Path => {
418                        format!("{expr}.map(|v| v.to_string_lossy().to_string())")
419                    }
420                    TypeRef::String | TypeRef::Bytes => {
421                        if returns_ref {
422                            format!("{expr}.map(Into::into)")
423                        } else {
424                            expr.to_string()
425                        }
426                    }
427                    TypeRef::Vec(vi) => match vi.as_ref() {
428                        TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
429                            let mapped_name = mapper.named(name);
430                            let wrap = arc_wrap_expr("x", name, mutex_types);
431                            format!(
432                                "{expr}.map(|v| v.into_iter().map(|x| {mapped_name} {{ inner: {wrap} }}).collect())"
433                            )
434                        }
435                        TypeRef::Named(_) => {
436                            format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
437                        }
438                        _ => expr.to_string(),
439                    },
440                    _ => expr.to_string(),
441                },
442                // Vec<Named>: map each element through Into
443                TypeRef::Vec(inner) => match inner.as_ref() {
444                    TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
445                        let mapped_name = mapper.named(name);
446                        if returns_ref {
447                            let wrap = arc_wrap_expr("v.clone()", name, mutex_types);
448                            format!("{expr}.into_iter().map(|v| {mapped_name} {{ inner: {wrap} }}).collect()")
449                        } else {
450                            let wrap = arc_wrap_expr("v", name, mutex_types);
451                            format!("{expr}.into_iter().map(|v| {mapped_name} {{ inner: {wrap} }}).collect()")
452                        }
453                    }
454                    TypeRef::Named(_) => {
455                        if returns_ref {
456                            // `&[T]` → `Vec<U>`: use `.iter()` not `.into_iter()`
457                            // to avoid clippy::into_iter_on_ref under -D warnings.
458                            format!("{expr}.iter().map(|v| v.clone().into()).collect()")
459                        } else {
460                            format!("{expr}.into_iter().map(Into::into).collect()")
461                        }
462                    }
463                    TypeRef::Path => {
464                        format!("{expr}.into_iter().map(|v| v.to_string_lossy().to_string()).collect()")
465                    }
466                    TypeRef::String => {
467                        if returns_ref {
468                            // `&[&str]` → `Vec<String>`. `Into::into` would require
469                            // `impl From<&&str> for String` (which doesn't exist).
470                            format!("{expr}.iter().map(|s| s.to_string()).collect()")
471                        } else {
472                            expr.to_string()
473                        }
474                    }
475                    TypeRef::Bytes => {
476                        if returns_ref {
477                            format!("{expr}.iter().map(|b| b.to_vec()).collect()")
478                        } else {
479                            expr.to_string()
480                        }
481                    }
482                    _ => expr.to_string(),
483                },
484                _ => expr.to_string(),
485            }
486        };
487
488        if func.error_type.is_some() {
489            // Backend-specific error conversion
490            let err_conv = match cfg.async_pattern {
491                AsyncPattern::Pyo3FutureIntoPy => {
492                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
493                }
494                AsyncPattern::NapiNativeAsync => {
495                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
496                }
497                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
498                AsyncPattern::TokioBlockOn => {
499                    ".map_err(|e| extendr_api::Error::Other(e.to_string().replace(\":\", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").chars().take(255).collect::<String>()))"
500                }
501                _ => ".map_err(|e| e.to_string())",
502            };
503            let wrapped = wrap_return("val");
504            if wrapped == "val" {
505                format!("{core_call}{err_conv}")
506            } else if wrapped == "val.into()" {
507                format!("{core_call}.map(Into::into){err_conv}")
508            } else if let Some(type_path) = wrapped.strip_suffix("::from(val)") {
509                format!("{core_call}.map({type_path}::from){err_conv}")
510            } else {
511                format!("{core_call}.map(|val| {wrapped}){err_conv}")
512            }
513        } else {
514            wrap_return(&core_call)
515        }
516    };
517
518    // Prepend let bindings for non-opaque Named params (sync delegate case).
519    // Only prepend when can_delegate is true — the !can_delegate serde path does its own bindings.
520    // However, always prepend Vec<String> ref bindings (names_refs) since serde path doesn't handle them.
521    let body = if !let_bindings.is_empty() && !func.is_async {
522        if can_delegate {
523            format!("{let_bindings}{body}")
524        } else {
525            // For the !can_delegate path, only prepend Vec<String>+is_ref bindings (names_refs)
526            // since serde bindings handle Named type conversions.
527            let vec_str_bindings: String = func.params.iter().filter(|p| {
528                p.is_ref && matches!(&p.ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char))
529            }).map(|p| {
530                // Handle both Vec<String> and Option<Vec<String>> parameters.
531                // When p.optional=true, p.ty is the inner type (Vec<String>), so we need to unwrap first.
532                if p.optional {
533                    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)
534                } else {
535                    format!("let {}_refs: Vec<&str> = {}.iter().map(|s| s.as_str()).collect();\n    ", p.name, p.name)
536                }
537            }).collect();
538            if !vec_str_bindings.is_empty() {
539                format!("{vec_str_bindings}{body}")
540            } else {
541                body
542            }
543        }
544    } else {
545        body
546    };
547
548    // Wrap long signature if necessary
549    // TokioBlockOn functions block synchronously inside the body — the generated function
550    // must NOT be `async fn` because extendr's `#[extendr]` cannot return a Future.
551    let async_kw = if func.is_async && cfg.async_pattern != AsyncPattern::TokioBlockOn {
552        "async "
553    } else {
554        ""
555    };
556    let func_needs_py = func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
557
558    // For async PyO3 free functions, override return type and add lifetime generic.
559    let ret = if func_needs_py {
560        "PyResult<Bound<'py, PyAny>>".to_string()
561    } else {
562        ret
563    };
564    let func_lifetime = if func_needs_py { "<'py>" } else { "" };
565
566    let (func_sig, _params_formatted) = if params.len() > 100 {
567        // When formatting for long signatures, promote optional params like function_params() does
568        let mut seen_optional = false;
569        let wrapped_params = func
570            .params
571            .iter()
572            .map(|p| {
573                if p.optional {
574                    seen_optional = true;
575                }
576                let ty = if p.optional || seen_optional {
577                    format!("Option<{}>", mapper.map_type(&p.ty))
578                } else {
579                    mapper.map_type(&p.ty)
580                };
581                format!("{}: {}", p.name, ty)
582            })
583            .collect::<Vec<_>>()
584            .join(",\n    ");
585
586        // For async PyO3, we need special signature handling
587        if func_needs_py {
588            (
589                format!(
590                    "pub fn {}{func_lifetime}(py: Python<'py>,\n    {}\n) -> {ret}",
591                    func.name,
592                    wrapped_params,
593                    ret = ret
594                ),
595                "",
596            )
597        } else {
598            (
599                format!(
600                    "pub {async_kw}fn {}(\n    {}\n) -> {ret}",
601                    func.name,
602                    wrapped_params,
603                    ret = ret
604                ),
605                "",
606            )
607        }
608    } else if func_needs_py {
609        (
610            format!(
611                "pub fn {}{func_lifetime}(py: Python<'py>, {params}) -> {ret}",
612                func.name
613            ),
614            "",
615        )
616    } else {
617        (format!("pub {async_kw}fn {}({params}) -> {ret}", func.name), "")
618    };
619
620    let total_params = func.params.len() + if func_needs_py { 1 } else { 0 };
621    let sig_defaults = if cfg.needs_signature {
622        function_sig_defaults(&func.params)
623    } else {
624        String::new()
625    };
626    let attr_inner = cfg
627        .function_attr
628        .trim_start_matches('#')
629        .trim_start_matches('[')
630        .trim_end_matches(']');
631
632    crate::template_env::render(
633        "generators/functions/function_definition.jinja",
634        minijinja::context! {
635            has_too_many_arguments => total_params > 7,
636            has_missing_errors_doc => func.error_type.is_some(),
637            attr_inner => attr_inner,
638            needs_signature => cfg.needs_signature,
639            signature_prefix => cfg.signature_prefix,
640            sig_defaults => sig_defaults,
641            signature_suffix => cfg.signature_suffix,
642            func_sig => func_sig,
643            body => body,
644        },
645    )
646}
647
648fn can_delegate_with_named_let_bindings(func: &FunctionDef, opaque_types: &AHashSet<String>) -> bool {
649    !func.sanitized
650        && func
651            .params
652            .iter()
653            .all(|p| !p.sanitized && crate::shared::is_delegatable_param(&p.ty, opaque_types))
654        && crate::shared::is_delegatable_return(&func.return_type)
655}
656
657/// Collect all unique trait import paths from types' methods.
658///
659/// Returns a deduplicated, sorted list of trait paths (e.g. `["liter_llm::LlmClient"]`)
660/// that need to be imported in generated binding code so that trait methods can be called.
661/// Both opaque and non-opaque types are scanned because non-opaque wrapper types also
662/// delegate trait method calls to their inner core type.
663pub fn collect_trait_imports(api: &ApiSurface) -> Vec<String> {
664    // Collect all trait paths, then deduplicate by last segment (trait name).
665    // When two paths resolve to the same trait name (e.g. `mylib_core::Dependency`
666    // and `mylib_core::di::Dependency`), only one import is needed. Keep the
667    // shorter (public re-export) path to avoid E0252 duplicate-import errors.
668    let mut traits: AHashSet<String> = AHashSet::new();
669    for typ in api.types.iter().filter(|typ| !typ.is_trait) {
670        for method in &typ.methods {
671            if let Some(ref trait_path) = method.trait_source {
672                traits.insert(trait_path.clone());
673            }
674        }
675    }
676
677    // Deduplicate by last path segment: keep the shortest path for each trait name.
678    let mut by_name: AHashMap<String, String> = AHashMap::new();
679    for path in traits {
680        let name = path.split("::").last().unwrap_or(&path).to_string();
681        let entry = by_name.entry(name).or_insert_with(|| path.clone());
682        // Prefer shorter paths (public re-exports are shorter than internal paths)
683        if path.len() < entry.len() {
684            *entry = path;
685        }
686    }
687
688    let mut sorted: Vec<String> = by_name.into_values().collect();
689    sorted.sort();
690    sorted
691}
692
693/// Check if any type has methods from trait impls whose trait_source could not be resolved.
694///
695/// When true, the binding crate should add a glob import of the core crate (e.g.
696/// `use kreuzberg::*`) to bring all publicly exported traits into scope.
697/// This handles traits defined in private submodules that are re-exported.
698pub fn has_unresolved_trait_methods(api: &ApiSurface) -> bool {
699    // Count method names that appear on multiple non-trait types but lack trait_source.
700    // Such methods likely come from trait impls whose trait path could not be resolved
701    // (e.g. traits defined in private modules but re-exported via `pub use`).
702    let mut method_counts: AHashMap<&str, (usize, usize)> = AHashMap::new(); // (total, with_source)
703    for typ in api.types.iter().filter(|typ| !typ.is_trait) {
704        if typ.is_trait {
705            continue;
706        }
707        for method in &typ.methods {
708            let entry = method_counts.entry(&method.name).or_insert((0, 0));
709            entry.0 += 1;
710            if method.trait_source.is_some() {
711                entry.1 += 1;
712            }
713        }
714    }
715    // A method appearing on 3+ types without trait_source on any is almost certainly a trait method
716    method_counts
717        .values()
718        .any(|&(total, with_source)| total >= 3 && with_source == 0)
719}
720
721/// Collect explicit type and enum names from the API surface for named imports.
722///
723/// Returns a sorted, deduplicated list of type and enum names that should be
724/// imported from the core crate. This replaces glob imports (`use core::*`)
725/// which can cause name conflicts with local binding definitions (e.g. a
726/// `convert` function or `Result` type alias from the core crate shadowing
727/// the binding's own `convert` wrapper or `std::result::Result`).
728///
729/// Only struct/enum names are included — functions and type aliases are
730/// intentionally excluded because they are the source of conflicts.
731pub fn collect_explicit_core_imports(api: &ApiSurface) -> Vec<String> {
732    let mut names = std::collections::BTreeSet::new();
733    for typ in api.types.iter().filter(|typ| !typ.is_trait) {
734        names.insert(typ.name.clone());
735    }
736    for e in &api.enums {
737        names.insert(e.name.clone());
738    }
739    names.into_iter().collect()
740}