Skip to main content

alef_codegen/generators/
methods.rs

1use crate::generators::binding_helpers::{
2    apply_return_newtype_unwrap, gen_async_body, gen_call_args, gen_call_args_with_let_bindings,
3    gen_lossy_binding_to_core_fields, gen_lossy_binding_to_core_fields_mut, gen_named_let_bindings_pub,
4    gen_serde_let_bindings, gen_unimplemented_body, has_named_params, is_simple_non_opaque_param,
5    wrap_return_with_mutex,
6};
7use crate::generators::{AdapterBodies, AsyncPattern, RustBindingConfig};
8use crate::shared::{constructor_parts, function_params, function_sig_defaults, partition_methods};
9use crate::type_mapper::TypeMapper;
10use ahash::AHashSet;
11use alef_core::ir::{MethodDef, TypeDef, TypeRef};
12use std::fmt::Write;
13
14/// Returns true when `name` matches a known trait method that would trigger
15/// `clippy::should_implement_trait`.
16pub fn is_trait_method_name(name: &str) -> bool {
17    crate::generators::TRAIT_METHOD_NAMES.contains(&name)
18}
19
20/// Generate a constructor method.
21pub fn gen_constructor(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
22    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
23
24    // For types with has_default, generate optional kwargs-style constructor
25    let (param_list, sig_defaults, assignments) = if typ.has_default {
26        crate::shared::config_constructor_parts_with_options(&typ.fields, &map_fn, cfg.option_duration_on_defaults)
27    } else {
28        constructor_parts(&typ.fields, &map_fn)
29    };
30
31    let mut out = String::with_capacity(512);
32    // Per-item clippy suppression: too_many_arguments when >7 params
33    if typ.fields.len() > 7 {
34        writeln!(out, "    #[allow(clippy::too_many_arguments)]").ok();
35    }
36    writeln!(out, "    #[must_use]").ok();
37    if cfg.needs_signature {
38        writeln!(
39            out,
40            "    {}{}{}",
41            cfg.signature_prefix, sig_defaults, cfg.signature_suffix
42        )
43        .ok();
44    }
45    write!(
46        out,
47        "    {}\n    pub fn new({param_list}) -> Self {{\n        Self {{ {assignments} }}\n    }}",
48        cfg.constructor_attr
49    )
50    .ok();
51    out
52}
53
54/// Generate an instance method.
55///
56/// When `is_opaque` is true, generates delegation to `self.inner` via Arc clone
57/// instead of converting self to core type.
58///
59/// `opaque_types` is the set of opaque type names, used for correct return wrapping.
60/// `mutex_types` is the subset of opaque types whose `inner` field is `Arc<Mutex<T>>`;
61/// method dispatch uses `.lock().unwrap()` for these types.
62#[allow(clippy::too_many_arguments)]
63pub fn gen_method(
64    method: &MethodDef,
65    mapper: &dyn TypeMapper,
66    cfg: &RustBindingConfig,
67    typ: &TypeDef,
68    is_opaque: bool,
69    opaque_types: &AHashSet<String>,
70    mutex_types: &AHashSet<String>,
71    adapter_bodies: &AdapterBodies,
72) -> String {
73    let type_name = &typ.name;
74    // Use the full rust_path (with hyphens replaced by underscores) for core type references
75    let core_type_path = typ.rust_path.replace('-', "_");
76
77    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
78    let params = function_params(&method.params, &map_fn);
79    let return_type = mapper.map_type(&method.return_type);
80    let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
81
82    let core_import = cfg.core_import;
83
84    // When non-opaque Named params have is_ref=true, or Vec<String> params have is_ref=true,
85    // we need let bindings so the converted/intermediate value outlives the borrow.
86    // Use has_named_params which covers both Named types and Vec<String> with is_ref=true.
87    let has_ref_named_params = has_named_params(&method.params, opaque_types);
88    let (call_args, ref_let_bindings) = if has_ref_named_params {
89        (
90            gen_call_args_with_let_bindings(&method.params, opaque_types),
91            gen_named_let_bindings_pub(&method.params, opaque_types, core_import),
92        )
93    } else {
94        (gen_call_args(&method.params, opaque_types), String::new())
95    };
96
97    let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
98    let is_ref_mut_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::RefMut));
99
100    // Detect non-opaque RefMut methods that can use the functional clone-mutate-return pattern.
101    // These cannot use &mut self in frozen PyO3 classes or immutable WASM structs, so instead
102    // we generate: clone self to core, apply mutation, convert back to Self.
103    // Conditions: non-opaque, RefMut receiver, no trait source (trait methods need special handling),
104    // all params delegatable (Named types are allowed — gen_call_args handles them via .into()),
105    // and not sanitized.
106    let is_functional_ref_mut = !is_opaque
107        && is_ref_mut_receiver
108        && !method.sanitized
109        && method.trait_source.is_none()
110        && method
111            .params
112            .iter()
113            .all(|p| !p.sanitized && crate::shared::is_delegatable_param(&p.ty, opaque_types));
114
115    // Methods from trait impls can't be called on Arc<dyn Trait> through deref.
116    // Skip these unless there's an adapter body that can handle them.
117    let is_trait_method = method.trait_source.is_some();
118
119    // Whether this opaque type uses Arc<Mutex<T>> for interior mutability.
120    let self_needs_mutex = is_opaque && mutex_types.contains(type_name.as_str());
121
122    // Auto-delegate opaque methods: unwrap Arc for params, wrap Arc for returns.
123    // Owned receivers require the type to implement Clone (builder pattern).
124    // RefMut receivers normally can't be delegated on Arc<T>, but Arc<Mutex<T>> allows
125    // &mut T via .lock().unwrap(), so mutex types CAN delegate RefMut methods.
126    // Trait methods can't be delegated on opaque types (Arc deref doesn't expose trait methods).
127    // Async methods are allowed — gen_async_body handles them below.
128    let opaque_can_delegate = is_opaque
129        && !method.sanitized
130        && (!is_ref_mut_receiver || self_needs_mutex)
131        && !is_trait_method
132        && (!is_owned_receiver || typ.is_clone)
133        && method
134            .params
135            .iter()
136            .all(|p| !p.sanitized && crate::shared::is_opaque_delegatable_type(&p.ty))
137        && crate::shared::is_opaque_delegatable_type(&method.return_type);
138
139    // Build the core call expression: opaque types delegate to self.inner directly,
140    // non-opaque types convert self to core type first.
141    // For mutex types, acquire the lock before calling the method.
142    let make_core_call = |method_name: &str| -> String {
143        if is_opaque {
144            if is_owned_receiver {
145                // Owned receiver: clone out of Arc/Mutex to get an owned value.
146                // For Mutex types, lock first then clone the inner value.
147                if self_needs_mutex {
148                    format!("self.inner.lock().unwrap().clone().{method_name}({call_args})")
149                } else {
150                    format!("(*self.inner).clone().{method_name}({call_args})")
151                }
152            } else if self_needs_mutex {
153                // Mutex type: lock to get &mut T (works for both &self and &mut self methods).
154                format!("self.inner.lock().unwrap().{method_name}({call_args})")
155            } else {
156                format!("self.inner.{method_name}({call_args})")
157            }
158        } else {
159            format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
160        }
161    };
162
163    // For async opaque methods, we clone the Arc before moving into the future.
164    // For mutex types, the cloned Arc<Mutex<T>> is locked inside the async block.
165    let make_async_core_call = |method_name: &str| -> String {
166        if is_opaque {
167            if self_needs_mutex {
168                format!("inner.lock().unwrap().{method_name}({call_args})")
169            } else {
170                format!("inner.{method_name}({call_args})")
171            }
172        } else {
173            format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
174        }
175    };
176
177    // Generate the body: convert self to core type, call method, convert result back
178    //
179    // For opaque types, wrap the return value appropriately:
180    //   - Named(self) → Self { inner: Arc::new(result) }
181    //   - Named(other) → OtherType::from(result)
182    //   - primitives/String/Vec/Unit → pass through
183    let result_expr = apply_return_newtype_unwrap("result", &method.return_newtype_wrapper);
184    let async_result_wrap = if is_opaque {
185        wrap_return_with_mutex(
186            &result_expr,
187            &method.return_type,
188            type_name,
189            opaque_types,
190            mutex_types,
191            is_opaque,
192            method.returns_ref,
193            method.returns_cow,
194        )
195    } else {
196        // For non-opaque types, only use From conversion if the return type is simple
197        // enough. Named return types may not have a From impl.
198        match &method.return_type {
199            TypeRef::Named(_) | TypeRef::Json => format!("{result_expr}.into()"),
200            _ => result_expr.clone(),
201        }
202    };
203
204    let body = if !opaque_can_delegate {
205        // Check if an adapter provides the body
206        let adapter_key_inner = format!("{}.{}", type_name, method.name);
207        if let Some(adapter_body) = adapter_bodies.get(&adapter_key_inner) {
208            adapter_body.clone()
209        } else if cfg.has_serde
210            && is_opaque
211            && !method.sanitized
212            && !is_trait_method
213            && has_named_params(&method.params, opaque_types)
214            && method.error_type.is_some()
215            && crate::shared::is_opaque_delegatable_type(&method.return_type)
216        {
217            // Serde-based param conversion for opaque methods with non-opaque Named params.
218            // NOTE: Only executed when has_serde=true, ensuring serde_json calls are gated.
219            let err_conv = match cfg.async_pattern {
220                AsyncPattern::Pyo3FutureIntoPy => {
221                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
222                }
223                AsyncPattern::NapiNativeAsync => {
224                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
225                }
226                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
227                _ => ".map_err(|e| e.to_string())",
228            };
229            let serde_bindings =
230                gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, "        ");
231            let serde_call_args = gen_call_args_with_let_bindings(&method.params, opaque_types);
232            let core_call = if self_needs_mutex {
233                format!("self.inner.lock().unwrap().{}({serde_call_args})", method.name)
234            } else {
235                format!("self.inner.{}({serde_call_args})", method.name)
236            };
237            if matches!(method.return_type, TypeRef::Unit) {
238                format!("{serde_bindings}{core_call}{err_conv}?;\n        Ok(())")
239            } else {
240                let wrap = wrap_return_with_mutex(
241                    "result",
242                    &method.return_type,
243                    type_name,
244                    opaque_types,
245                    mutex_types,
246                    is_opaque,
247                    method.returns_ref,
248                    method.returns_cow,
249                );
250                format!("{serde_bindings}let result = {core_call}{err_conv}?;\n        Ok({wrap})")
251            }
252        } else if is_functional_ref_mut {
253            // Functional clone-mutate-return pattern for non-opaque RefMut methods.
254            // PyO3 frozen classes and WASM structs don't support &mut self, so instead:
255            //   1. Convert binding self to a mutable core type.
256            //   2. Call the mutating core method (which changes core_self in place).
257            //   3. Convert the mutated core type back to the binding type and return Self.
258            //
259            // The generated signature uses &self -> Self (or -> Result<Self, E> if fallible),
260            // making the method work correctly with immutable binding wrappers.
261            let field_conversions =
262                gen_lossy_binding_to_core_fields_mut(typ, cfg.core_import, cfg.option_duration_on_defaults);
263            let core_call = format!("core_self.{}({call_args})", method.name);
264            if method.error_type.is_some() {
265                let err_conv = match cfg.async_pattern {
266                    AsyncPattern::Pyo3FutureIntoPy => {
267                        ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
268                    }
269                    AsyncPattern::NapiNativeAsync => {
270                        ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
271                    }
272                    AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
273                    _ => ".map_err(|e| e.to_string())",
274                };
275                format!("{field_conversions}{core_call}{err_conv}?;\n        Ok(core_self.into())")
276            } else {
277                format!("{field_conversions}{core_call};\n        core_self.into()")
278            }
279        } else if !is_opaque
280            && !method.sanitized
281            && method
282                .params
283                .iter()
284                .all(|p| !p.sanitized && is_simple_non_opaque_param(&p.ty))
285            && crate::shared::is_delegatable_return(&method.return_type)
286        {
287            // Non-opaque delegation: construct core type field-by-field, call method, convert back.
288            // Sanitized fields use Default::default() (lossy but functional for builder pattern).
289            let is_ref_mut = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::RefMut));
290            let field_conversions = if is_ref_mut {
291                gen_lossy_binding_to_core_fields_mut(typ, cfg.core_import, cfg.option_duration_on_defaults)
292            } else {
293                gen_lossy_binding_to_core_fields(typ, cfg.core_import, cfg.option_duration_on_defaults)
294            };
295            let core_call = format!("core_self.{}({call_args})", method.name);
296            let newtype_suffix = if method.return_newtype_wrapper.is_some() {
297                ".0"
298            } else {
299                ""
300            };
301            let result_wrap = match &method.return_type {
302                // When returns_cow=true the core returns Cow<'_, T>: call .into_owned() to
303                // obtain an owned T before the binding→core From conversion.
304                // When returns_ref=true (or &T / Cow<'_, T> via the old flag), same treatment.
305                TypeRef::Named(n) if n == type_name && (method.returns_cow || method.returns_ref) => {
306                    ".into_owned().into()".to_string()
307                }
308                TypeRef::Named(_) if method.returns_cow || method.returns_ref => ".into_owned().into()".to_string(),
309                TypeRef::Named(n) if n == type_name => ".into()".to_string(),
310                TypeRef::Named(_) => ".into()".to_string(),
311                TypeRef::String | TypeRef::Path => {
312                    if method.returns_ref {
313                        ".to_owned()".to_string()
314                    } else {
315                        ".into()".to_string()
316                    }
317                }
318                // Bytes: binding uses Vec<u8>. When core returns &[u8] (returns_ref=true),
319                // use .to_vec() to produce Vec<u8>. When core returns owned Bytes, use .into().
320                TypeRef::Bytes => {
321                    if method.returns_ref {
322                        ".to_vec()".to_string()
323                    } else {
324                        ".into()".to_string()
325                    }
326                }
327                // Optional<Named>: when core returns Option<&T>, need .map(|v| v.clone().into())
328                TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
329                    if method.returns_ref {
330                        ".map(|v| v.clone().into())".to_string()
331                    } else {
332                        ".map(Into::into)".to_string()
333                    }
334                }
335                // Optional<String>: when core returns Option<&str>, need .map(|v| v.to_owned())
336                TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Bytes) => {
337                    if method.returns_ref {
338                        ".map(|v| v.to_owned())".to_string()
339                    } else {
340                        String::new()
341                    }
342                }
343                _ => String::new(),
344            };
345            if method.error_type.is_some() {
346                let err_conv = match cfg.async_pattern {
347                    AsyncPattern::Pyo3FutureIntoPy => {
348                        ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
349                    }
350                    AsyncPattern::NapiNativeAsync => {
351                        ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
352                    }
353                    AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
354                    _ => ".map_err(|e| e.to_string())",
355                };
356                format!(
357                    "{field_conversions}let result = {core_call}{err_conv}?;\n        Ok(result{newtype_suffix}{result_wrap})"
358                )
359            } else {
360                format!("{field_conversions}{core_call}{newtype_suffix}{result_wrap}")
361            }
362        } else if is_opaque
363            && !method.sanitized
364            && (!is_ref_mut_receiver || self_needs_mutex)
365            && (!is_owned_receiver || typ.is_clone)
366            && method.error_type.is_none()
367            && method
368                .params
369                .iter()
370                .all(|p| !p.sanitized && crate::shared::is_opaque_delegatable_type(&p.ty))
371            && matches!(&method.return_type, TypeRef::Named(n) if n == type_name)
372        {
373            // Builder pattern for opaque types: method returns Self without error type.
374            // Delegate to core method and wrap result back in Self { inner: Arc::new(...) }.
375            let core_call = if is_owned_receiver {
376                if self_needs_mutex {
377                    format!("self.inner.lock().unwrap().clone().{}({call_args})", method.name)
378                } else {
379                    format!("(*self.inner).clone().{}({call_args})", method.name)
380                }
381            } else if self_needs_mutex {
382                format!("self.inner.lock().unwrap().{}({call_args})", method.name)
383            } else {
384                format!("self.inner.{}({call_args})", method.name)
385            };
386            let unwrapped = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
387            let arc_expr = if self_needs_mutex {
388                format!("Arc::new(std::sync::Mutex::new({unwrapped}))")
389            } else {
390                format!("Arc::new({unwrapped})")
391            };
392            format!("Self {{ inner: {arc_expr} }}")
393        } else if !is_opaque
394            && !method.sanitized
395            && !is_ref_mut_receiver
396            && (!is_owned_receiver || typ.is_clone)
397            && method.error_type.is_none()
398            && method
399                .params
400                .iter()
401                .all(|p| !p.sanitized && is_simple_non_opaque_param(&p.ty))
402            && matches!(&method.return_type, TypeRef::Named(n) if n == type_name)
403        {
404            // Builder pattern for non-opaque types: method returns Self without error type.
405            // Construct core type field-by-field, call method, convert result back via .into().
406            let is_ref_mut = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::RefMut));
407            let field_conversions = if is_ref_mut {
408                gen_lossy_binding_to_core_fields_mut(typ, cfg.core_import, cfg.option_duration_on_defaults)
409            } else {
410                gen_lossy_binding_to_core_fields(typ, cfg.core_import, cfg.option_duration_on_defaults)
411            };
412            let core_call = format!("core_self.{}({call_args})", method.name);
413            let newtype_suffix = if method.return_newtype_wrapper.is_some() {
414                ".0"
415            } else {
416                ""
417            };
418            let result_wrap = if method.returns_cow || method.returns_ref {
419                ".into_owned().into()"
420            } else {
421                ".into()"
422            };
423            format!("{field_conversions}{core_call}{newtype_suffix}{result_wrap}")
424        } else {
425            gen_unimplemented_body(
426                &method.return_type,
427                &format!("{type_name}.{}", method.name),
428                method.error_type.is_some(),
429                cfg,
430                &method.params,
431                opaque_types,
432            )
433        }
434    } else if method.is_async {
435        let inner_clone_line = if is_opaque {
436            "let inner = self.inner.clone();\n        "
437        } else {
438            ""
439        };
440        let core_call_str = make_async_core_call(&method.name);
441        gen_async_body(
442            &core_call_str,
443            cfg,
444            method.error_type.is_some(),
445            &async_result_wrap,
446            is_opaque,
447            inner_clone_line,
448            matches!(method.return_type, TypeRef::Unit),
449            Some(&return_type),
450        )
451    } else {
452        let core_call = make_core_call(&method.name);
453        if method.error_type.is_some() {
454            // Backend-specific error conversion
455            let err_conv = match cfg.async_pattern {
456                AsyncPattern::Pyo3FutureIntoPy => {
457                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
458                }
459                AsyncPattern::NapiNativeAsync => {
460                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
461                }
462                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
463                _ => ".map_err(|e| e.to_string())",
464            };
465            if is_opaque {
466                if matches!(method.return_type, TypeRef::Unit) {
467                    // Unit return: avoid let_unit_value by not binding the result
468                    format!("{core_call}{err_conv}?;\n        Ok(())")
469                } else {
470                    let wrap = wrap_return_with_mutex(
471                        &result_expr,
472                        &method.return_type,
473                        type_name,
474                        opaque_types,
475                        mutex_types,
476                        is_opaque,
477                        method.returns_ref,
478                        method.returns_cow,
479                    );
480                    format!("let result = {core_call}{err_conv}?;\n        Ok({wrap})")
481                }
482            } else {
483                format!("{core_call}{err_conv}")
484            }
485        } else if is_opaque {
486            let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
487            wrap_return_with_mutex(
488                &unwrapped_call,
489                &method.return_type,
490                type_name,
491                opaque_types,
492                mutex_types,
493                is_opaque,
494                method.returns_ref,
495                method.returns_cow,
496            )
497        } else {
498            core_call
499        }
500    };
501    let adapter_key = format!("{}.{}", type_name, method.name);
502    let has_adapter = adapter_bodies.contains_key(&adapter_key);
503
504    // Prepend let bindings for non-opaque Named ref params (needed for borrow lifetime).
505    // Skip when an adapter body is used: the adapter body is self-contained and already
506    // includes its own parameter conversions (via core_let_bindings). Prepending the
507    // normal {name}_core bindings would produce a duplicate .into() call on a moved value
508    // (E0382 use of moved value).
509    let body = if ref_let_bindings.is_empty() || has_adapter {
510        body
511    } else {
512        format!("{ref_let_bindings}{body}")
513    };
514
515    let needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
516
517    // When an async PyO3 method could not be auto-delegated (e.g. sanitized params,
518    // non-delegatable return type, or trait methods), the body was computed for a
519    // synchronous context but the generated signature will be
520    // `PyResult<Bound<'py, PyAny>>`. Return `Err` directly — wrapping in
521    // `future_into_py` would cause E0283 because the async block only returns `Err`
522    // and Rust cannot infer the generic `T` parameter.
523    let body = if needs_py && !opaque_can_delegate && !has_adapter {
524        let err_msg = format!("Not implemented: {type_name}.{}", method.name);
525        // Suppress unused parameter warnings — params are not used in the stub body.
526        let suppress = if method.params.is_empty() {
527            String::new()
528        } else {
529            let names: Vec<&str> = method.params.iter().map(|p| p.name.as_str()).collect();
530            if names.len() == 1 {
531                format!("let _ = {};\n        ", names[0])
532            } else {
533                format!("let _ = ({});\n        ", names.join(", "))
534            }
535        };
536        format!("{suppress}Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
537    } else {
538        body
539    };
540    let self_param = match (needs_py, params.is_empty()) {
541        (true, true) => "&self, py: Python<'py>",
542        (true, false) => "&self, py: Python<'py>, ",
543        (false, true) => "&self",
544        (false, false) => "&self, ",
545    };
546
547    // For async PyO3 methods, override return type to PyResult<Bound<'py, PyAny>>
548    // and add the 'py lifetime generic on the method name.
549    // For functional RefMut methods, override to Self (or Result<Self, E>) because the
550    // generated body clones self, applies the mutation, and returns the updated value.
551    let ret = if needs_py {
552        "PyResult<Bound<'py, PyAny>>".to_string()
553    } else if is_functional_ref_mut {
554        mapper.wrap_return("Self", method.error_type.is_some())
555    } else {
556        ret
557    };
558    let method_lifetime = if needs_py { "<'py>" } else { "" };
559
560    // Wrap long signature if necessary
561    let (sig_start, sig_params, sig_end) = if self_param.len() + params.len() > 100 {
562        let wrapped_params = method
563            .params
564            .iter()
565            .map(|p| {
566                let ty = if p.optional {
567                    format!("Option<{}>", mapper.map_type(&p.ty))
568                } else {
569                    mapper.map_type(&p.ty)
570                };
571                format!("{}: {}", p.name, ty)
572            })
573            .collect::<Vec<_>>()
574            .join(",\n        ");
575        let py_param = if needs_py { "\n        py: Python<'py>," } else { "" };
576        (
577            format!(
578                "pub fn {}{method_lifetime}(\n        &self,{}\n        ",
579                method.name, py_param
580            ),
581            wrapped_params,
582            "\n    ) -> ".to_string(),
583        )
584    } else {
585        (
586            format!("pub fn {}{method_lifetime}({}", method.name, self_param),
587            params,
588            ") -> ".to_string(),
589        )
590    };
591
592    let mut out = String::with_capacity(1024);
593    // Per-item clippy suppression: too_many_arguments when >7 params (including &self and py)
594    let total_params = method.params.len() + 1 + if needs_py { 1 } else { 0 };
595    if total_params > 7 {
596        writeln!(out, "    #[allow(clippy::too_many_arguments)]").ok();
597    }
598    // Per-item clippy suppression: missing_errors_doc for Result-returning methods
599    if method.error_type.is_some() {
600        writeln!(out, "    #[allow(clippy::missing_errors_doc)]").ok();
601    }
602    // Per-item clippy suppression: should_implement_trait for trait-conflicting names
603    if is_trait_method_name(&method.name) {
604        writeln!(out, "    #[allow(clippy::should_implement_trait)]").ok();
605    }
606    if cfg.needs_signature {
607        let sig = function_sig_defaults(&method.params);
608        writeln!(out, "    {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
609    }
610    write!(
611        out,
612        "    {}{}{}{} {{\n        \
613         {body}\n    }}",
614        sig_start, sig_params, sig_end, ret,
615    )
616    .ok();
617    out
618}
619
620/// Generate a static method.
621pub fn gen_static_method(
622    method: &MethodDef,
623    mapper: &dyn TypeMapper,
624    cfg: &RustBindingConfig,
625    typ: &TypeDef,
626    adapter_bodies: &AdapterBodies,
627    opaque_types: &AHashSet<String>,
628    mutex_types: &AHashSet<String>,
629) -> String {
630    let type_name = &typ.name;
631    // Use the full rust_path (with hyphens replaced by underscores) for core type references
632    let core_type_path = typ.rust_path.replace('-', "_");
633    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
634    let params = function_params(&method.params, &map_fn);
635    let return_type = mapper.map_type(&method.return_type);
636    let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
637
638    let core_import = cfg.core_import;
639
640    // Use let bindings when any non-opaque Named or Vec<Named> params exist.
641    // This includes Vec<Named> without is_ref=true, which need element conversion.
642    let use_let_bindings = has_named_params(&method.params, opaque_types);
643    let (call_args, ref_let_bindings) = if use_let_bindings {
644        (
645            gen_call_args_with_let_bindings(&method.params, opaque_types),
646            gen_named_let_bindings_pub(&method.params, opaque_types, core_import),
647        )
648    } else {
649        (gen_call_args(&method.params, opaque_types), String::new())
650    };
651
652    let can_delegate = crate::shared::can_auto_delegate(method, opaque_types);
653
654    let body = if !can_delegate {
655        // Check if an adapter provides the body
656        let adapter_key = format!("{}.{}", type_name, method.name);
657        if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
658            adapter_body.clone()
659        } else {
660            gen_unimplemented_body(
661                &method.return_type,
662                &format!("{type_name}::{}", method.name),
663                method.error_type.is_some(),
664                cfg,
665                &method.params,
666                opaque_types,
667            )
668        }
669    } else if method.is_async {
670        let core_call = format!("{core_type_path}::{}({call_args})", method.name);
671        let return_wrap = format!("{return_type}::from(result)");
672        gen_async_body(
673            &core_call,
674            cfg,
675            method.error_type.is_some(),
676            &return_wrap,
677            false,
678            "",
679            matches!(method.return_type, TypeRef::Unit),
680            Some(&return_type),
681        )
682    } else {
683        let core_call = format!("{core_type_path}::{}({call_args})", method.name);
684        if method.error_type.is_some() {
685            // Backend-specific error conversion
686            let err_conv = match cfg.async_pattern {
687                AsyncPattern::Pyo3FutureIntoPy => {
688                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
689                }
690                AsyncPattern::NapiNativeAsync => {
691                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
692                }
693                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
694                _ => ".map_err(|e| e.to_string())",
695            };
696            // Wrap the Ok value if the return type needs conversion (e.g. PathBuf→String)
697            let val_expr = apply_return_newtype_unwrap("val", &method.return_newtype_wrapper);
698            let wrapped = wrap_return_with_mutex(
699                &val_expr,
700                &method.return_type,
701                type_name,
702                opaque_types,
703                mutex_types,
704                typ.is_opaque,
705                method.returns_ref,
706                method.returns_cow,
707            );
708            if wrapped == val_expr {
709                format!("{core_call}{err_conv}")
710            } else {
711                format!("{core_call}.map(|val| {wrapped}){err_conv}")
712            }
713        } else {
714            // Wrap return value for non-error case too (e.g. PathBuf→String)
715            let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
716            wrap_return_with_mutex(
717                &unwrapped_call,
718                &method.return_type,
719                type_name,
720                opaque_types,
721                mutex_types,
722                typ.is_opaque,
723                method.returns_ref,
724                method.returns_cow,
725            )
726        }
727    };
728    // Prepend let bindings for non-opaque Named ref params
729    let body = if ref_let_bindings.is_empty() {
730        body
731    } else {
732        format!("{ref_let_bindings}{body}")
733    };
734
735    let static_needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
736
737    // For async PyO3 static methods, override return type and add lifetime generic.
738    let ret = if static_needs_py {
739        "PyResult<Bound<'py, PyAny>>".to_string()
740    } else {
741        ret
742    };
743    let method_lifetime = if static_needs_py { "<'py>" } else { "" };
744
745    // Wrap long signature if necessary
746    let (sig_start, sig_params, sig_end) = if params.len() > 100 {
747        let wrapped_params = method
748            .params
749            .iter()
750            .map(|p| {
751                let ty = if p.optional {
752                    format!("Option<{}>", mapper.map_type(&p.ty))
753                } else {
754                    mapper.map_type(&p.ty)
755                };
756                format!("{}: {}", p.name, ty)
757            })
758            .collect::<Vec<_>>()
759            .join(",\n        ");
760        // For async PyO3, add py parameter
761        if static_needs_py {
762            (
763                format!("pub fn {}{method_lifetime}(py: Python<'py>,\n        ", method.name),
764                wrapped_params,
765                "\n    ) -> ".to_string(),
766            )
767        } else {
768            (
769                format!("pub fn {}(\n        ", method.name),
770                wrapped_params,
771                "\n    ) -> ".to_string(),
772            )
773        }
774    } else if static_needs_py {
775        (
776            format!("pub fn {}{method_lifetime}(py: Python<'py>, ", method.name),
777            params,
778            ") -> ".to_string(),
779        )
780    } else {
781        (format!("pub fn {}(", method.name), params, ") -> ".to_string())
782    };
783
784    let mut out = String::with_capacity(1024);
785    // Per-item clippy suppression: too_many_arguments when >7 params (including py)
786    let total_params = method.params.len() + if static_needs_py { 1 } else { 0 };
787    if total_params > 7 {
788        writeln!(out, "    #[allow(clippy::too_many_arguments)]").ok();
789    }
790    // Per-item clippy suppression: missing_errors_doc for Result-returning methods
791    if method.error_type.is_some() {
792        writeln!(out, "    #[allow(clippy::missing_errors_doc)]").ok();
793    }
794    // Per-item clippy suppression: should_implement_trait for trait-conflicting names
795    if is_trait_method_name(&method.name) {
796        writeln!(out, "    #[allow(clippy::should_implement_trait)]").ok();
797    }
798    if let Some(attr) = cfg.static_attr {
799        writeln!(out, "    #[{attr}]").ok();
800    }
801    if cfg.needs_signature {
802        let sig = function_sig_defaults(&method.params);
803        writeln!(out, "    {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
804    }
805    write!(
806        out,
807        "    {}{}{}{} {{\n        \
808         {body}\n    }}",
809        sig_start, sig_params, sig_end, ret,
810    )
811    .ok();
812    out
813}
814
815/// Generate a full methods impl block (non-opaque types).
816pub fn gen_impl_block(
817    typ: &TypeDef,
818    mapper: &dyn TypeMapper,
819    cfg: &RustBindingConfig,
820    adapter_bodies: &AdapterBodies,
821    opaque_types: &AHashSet<String>,
822) -> String {
823    let (instance, statics) = partition_methods(&typ.methods);
824    // Compute effective (non-sanitized or adapter-overridden) method counts for the early-return
825    // check. Sanitized methods without adapters are skipped in the loops below, so they do not
826    // contribute real content to the impl block.
827    let has_emittable_instance = instance
828        .iter()
829        .any(|m| !m.sanitized || adapter_bodies.contains_key(&format!("{}.{}", typ.name, m.name)));
830    let has_emittable_statics = statics
831        .iter()
832        .any(|m| !m.sanitized || adapter_bodies.contains_key(&format!("{}.{}", typ.name, m.name)));
833    if !has_emittable_instance && !has_emittable_statics && typ.fields.is_empty() {
834        return String::new();
835    }
836
837    let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
838    let mut out = String::with_capacity(2048);
839    if let Some(block_attr) = cfg.method_block_attr {
840        writeln!(out, "#[{block_attr}]").ok();
841    }
842    writeln!(out, "impl {prefixed_name} {{").ok();
843
844    // Constructor
845    if !typ.fields.is_empty() {
846        out.push_str(&gen_constructor(typ, mapper, cfg));
847        out.push_str("\n\n");
848    }
849
850    // Instance methods
851    let empty_mutex_types: AHashSet<String> = AHashSet::new();
852    for m in &instance {
853        // Skip sanitized methods that have no adapter override — they cannot be delegated
854        // and emitting an unimplemented stub pollutes the public API with dead placeholders.
855        // Adapter bodies are explicit overrides and always take precedence.
856        let adapter_key = format!("{}.{}", typ.name, m.name);
857        if m.sanitized && !adapter_bodies.contains_key(&adapter_key) {
858            continue;
859        }
860        out.push_str(&gen_method(
861            m,
862            mapper,
863            cfg,
864            typ,
865            false,
866            opaque_types,
867            &empty_mutex_types,
868            adapter_bodies,
869        ));
870        out.push_str("\n\n");
871    }
872
873    // Static methods
874    for m in &statics {
875        // Skip sanitized static methods that have no adapter override.
876        let adapter_key = format!("{}.{}", typ.name, m.name);
877        if m.sanitized && !adapter_bodies.contains_key(&adapter_key) {
878            continue;
879        }
880        out.push_str(&gen_static_method(
881            m,
882            mapper,
883            cfg,
884            typ,
885            adapter_bodies,
886            opaque_types,
887            &empty_mutex_types,
888        ));
889        out.push_str("\n\n");
890    }
891
892    // Trim trailing newlines inside impl block
893    let trimmed = out.trim_end();
894    let mut result = trimmed.to_string();
895    result.push_str("\n}");
896    result
897}
898
899/// Generate a full impl block for an opaque type, delegating methods to `self.inner`.
900///
901/// `opaque_types` is the set of type names that are opaque wrappers (use `Arc<inner>`).
902/// This is needed so that return-type wrapping uses the correct pattern for cross-type returns.
903/// `mutex_types` is the subset of opaque types whose inner field uses `Arc<Mutex<T>>`;
904/// method dispatch uses `.lock().unwrap()` for these types.
905pub fn gen_opaque_impl_block(
906    typ: &TypeDef,
907    mapper: &dyn TypeMapper,
908    cfg: &RustBindingConfig,
909    opaque_types: &AHashSet<String>,
910    mutex_types: &AHashSet<String>,
911    adapter_bodies: &AdapterBodies,
912) -> String {
913    let (instance, statics) = partition_methods(&typ.methods);
914    // Compute effective (non-sanitized or adapter-overridden) method counts.
915    let has_emittable_instance = instance
916        .iter()
917        .any(|m| !m.sanitized || adapter_bodies.contains_key(&format!("{}.{}", typ.name, m.name)));
918    let has_emittable_statics = statics
919        .iter()
920        .any(|m| !m.sanitized || adapter_bodies.contains_key(&format!("{}.{}", typ.name, m.name)));
921    if !has_emittable_instance && !has_emittable_statics {
922        return String::new();
923    }
924
925    let mut out = String::with_capacity(2048);
926    let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
927    if let Some(block_attr) = cfg.method_block_attr {
928        writeln!(out, "#[{block_attr}]").ok();
929    }
930    writeln!(out, "impl {prefixed_name} {{").ok();
931
932    // Instance methods — delegate to self.inner
933    for m in &instance {
934        // Skip sanitized methods that have no adapter override — they cannot be delegated
935        // and emitting an unimplemented stub pollutes the public API with dead placeholders.
936        let adapter_key = format!("{}.{}", typ.name, m.name);
937        if m.sanitized && !adapter_bodies.contains_key(&adapter_key) {
938            continue;
939        }
940        out.push_str(&gen_method(
941            m,
942            mapper,
943            cfg,
944            typ,
945            true,
946            opaque_types,
947            mutex_types,
948            adapter_bodies,
949        ));
950        out.push_str("\n\n");
951    }
952
953    // Static methods
954    for m in &statics {
955        // Skip sanitized static methods that have no adapter override.
956        let adapter_key = format!("{}.{}", typ.name, m.name);
957        if m.sanitized && !adapter_bodies.contains_key(&adapter_key) {
958            continue;
959        }
960        out.push_str(&gen_static_method(
961            m,
962            mapper,
963            cfg,
964            typ,
965            adapter_bodies,
966            opaque_types,
967            mutex_types,
968        ));
969        out.push_str("\n\n");
970    }
971
972    let trimmed = out.trim_end();
973    let mut result = trimmed.to_string();
974    result.push_str("\n}");
975    result
976}