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, wrap_return,
5};
6use crate::generators::{AdapterBodies, AsyncPattern, RustBindingConfig};
7use crate::shared::{constructor_parts, function_params, function_sig_defaults, partition_methods};
8use crate::type_mapper::TypeMapper;
9use ahash::AHashSet;
10use alef_core::ir::{MethodDef, TypeDef, TypeRef};
11use std::fmt::Write;
12
13/// Returns true when `name` matches a known trait method that would trigger
14/// `clippy::should_implement_trait`.
15pub fn is_trait_method_name(name: &str) -> bool {
16    crate::generators::TRAIT_METHOD_NAMES.contains(&name)
17}
18
19/// Generate a constructor method.
20pub fn gen_constructor(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
21    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
22
23    // For types with has_default, generate optional kwargs-style constructor
24    let (param_list, sig_defaults, assignments) = if typ.has_default {
25        crate::shared::config_constructor_parts_with_options(&typ.fields, &map_fn, cfg.option_duration_on_defaults)
26    } else {
27        constructor_parts(&typ.fields, &map_fn)
28    };
29
30    let mut out = String::with_capacity(512);
31    // Per-item clippy suppression: too_many_arguments when >7 params
32    if typ.fields.len() > 7 {
33        writeln!(out, "    #[allow(clippy::too_many_arguments)]").ok();
34    }
35    writeln!(out, "    #[must_use]").ok();
36    if cfg.needs_signature {
37        writeln!(
38            out,
39            "    {}{}{}",
40            cfg.signature_prefix, sig_defaults, cfg.signature_suffix
41        )
42        .ok();
43    }
44    write!(
45        out,
46        "    {}\n    pub fn new({param_list}) -> Self {{\n        Self {{ {assignments} }}\n    }}",
47        cfg.constructor_attr
48    )
49    .ok();
50    out
51}
52
53/// Generate an instance method.
54///
55/// When `is_opaque` is true, generates delegation to `self.inner` via Arc clone
56/// instead of converting self to core type.
57///
58/// `opaque_types` is the set of opaque type names, used for correct return wrapping.
59pub fn gen_method(
60    method: &MethodDef,
61    mapper: &dyn TypeMapper,
62    cfg: &RustBindingConfig,
63    typ: &TypeDef,
64    is_opaque: bool,
65    opaque_types: &AHashSet<String>,
66    adapter_bodies: &AdapterBodies,
67) -> String {
68    let type_name = &typ.name;
69    // Use the full rust_path (with hyphens replaced by underscores) for core type references
70    let core_type_path = typ.rust_path.replace('-', "_");
71
72    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
73    let params = function_params(&method.params, &map_fn);
74    let return_type = mapper.map_type(&method.return_type);
75    let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
76
77    // When non-opaque Named params have is_ref=true, we need let bindings so the
78    // converted value outlives the borrow. Use gen_call_args_with_let_bindings in that case.
79    let has_ref_named_params = method
80        .params
81        .iter()
82        .any(|p| p.is_ref && matches!(&p.ty, TypeRef::Named(n) if !opaque_types.contains(n.as_str())));
83    let (call_args, ref_let_bindings) = if has_ref_named_params {
84        (
85            gen_call_args_with_let_bindings(&method.params, opaque_types),
86            gen_named_let_bindings_pub(&method.params, opaque_types),
87        )
88    } else {
89        (gen_call_args(&method.params, opaque_types), String::new())
90    };
91
92    let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
93
94    // Auto-delegate opaque methods: unwrap Arc for params, wrap Arc for returns.
95    // Owned receivers require the type to implement Clone (builder pattern).
96    // Async methods are allowed — gen_async_body handles them below.
97    let opaque_can_delegate = is_opaque
98        && !method.sanitized
99        && (!is_owned_receiver || typ.is_clone)
100        && method
101            .params
102            .iter()
103            .all(|p| !p.sanitized && crate::shared::is_opaque_delegatable_type(&p.ty))
104        && crate::shared::is_opaque_delegatable_type(&method.return_type);
105
106    // Build the core call expression: opaque types delegate to self.inner directly,
107    // non-opaque types convert self to core type first.
108    let make_core_call = |method_name: &str| -> String {
109        if is_opaque {
110            if is_owned_receiver {
111                // Owned receiver: clone out of Arc to get an owned value
112                format!("(*self.inner).clone().{method_name}({call_args})")
113            } else {
114                format!("self.inner.{method_name}({call_args})")
115            }
116        } else {
117            format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
118        }
119    };
120
121    // For async opaque methods, we clone the Arc before moving into the future.
122    let make_async_core_call = |method_name: &str| -> String {
123        if is_opaque {
124            format!("inner.{method_name}({call_args})")
125        } else {
126            format!("{core_type_path}::from(self.clone()).{method_name}({call_args})")
127        }
128    };
129
130    // Generate the body: convert self to core type, call method, convert result back
131    //
132    // For opaque types, wrap the return value appropriately:
133    //   - Named(self) → Self { inner: Arc::new(result) }
134    //   - Named(other) → OtherType::from(result)
135    //   - primitives/String/Vec/Unit → pass through
136    let result_expr = apply_return_newtype_unwrap("result", &method.return_newtype_wrapper);
137    let async_result_wrap = if is_opaque {
138        wrap_return(
139            &result_expr,
140            &method.return_type,
141            type_name,
142            opaque_types,
143            is_opaque,
144            method.returns_ref,
145            method.returns_cow,
146        )
147    } else {
148        // For non-opaque types, only use From conversion if the return type is simple
149        // enough. Named return types may not have a From impl.
150        match &method.return_type {
151            TypeRef::Named(_) | TypeRef::Json => format!("{result_expr}.into()"),
152            _ => result_expr.clone(),
153        }
154    };
155
156    let body = if !opaque_can_delegate {
157        // Check if an adapter provides the body
158        let adapter_key = format!("{}.{}", type_name, method.name);
159        if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
160            adapter_body.clone()
161        } else if cfg.has_serde
162            && is_opaque
163            && !method.sanitized
164            && has_named_params(&method.params, opaque_types)
165            && method.error_type.is_some()
166            && crate::shared::is_opaque_delegatable_type(&method.return_type)
167        {
168            // Serde-based param conversion for opaque methods with non-opaque Named params.
169            let err_conv = match cfg.async_pattern {
170                AsyncPattern::Pyo3FutureIntoPy => {
171                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
172                }
173                AsyncPattern::NapiNativeAsync => {
174                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
175                }
176                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
177                _ => ".map_err(|e| e.to_string())",
178            };
179            let serde_bindings =
180                gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, "        ");
181            let serde_call_args = gen_call_args_with_let_bindings(&method.params, opaque_types);
182            let core_call = format!("self.inner.{}({serde_call_args})", method.name);
183            if matches!(method.return_type, TypeRef::Unit) {
184                format!("{serde_bindings}{core_call}{err_conv}?;\n        Ok(())")
185            } else {
186                let wrap = wrap_return(
187                    "result",
188                    &method.return_type,
189                    type_name,
190                    opaque_types,
191                    is_opaque,
192                    method.returns_ref,
193                    method.returns_cow,
194                );
195                format!("{serde_bindings}let result = {core_call}{err_conv}?;\n        Ok({wrap})")
196            }
197        } else if !is_opaque
198            && !method.sanitized
199            && method
200                .params
201                .iter()
202                .all(|p| !p.sanitized && is_simple_non_opaque_param(&p.ty))
203            && crate::shared::is_delegatable_return(&method.return_type)
204        {
205            // Non-opaque delegation: construct core type field-by-field, call method, convert back.
206            // Sanitized fields use Default::default() (lossy but functional for builder pattern).
207            let is_ref_mut = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::RefMut));
208            let field_conversions = if is_ref_mut {
209                gen_lossy_binding_to_core_fields_mut(typ, cfg.core_import)
210            } else {
211                gen_lossy_binding_to_core_fields(typ, cfg.core_import)
212            };
213            let core_call = format!("core_self.{}({call_args})", method.name);
214            let newtype_suffix = if method.return_newtype_wrapper.is_some() {
215                ".0"
216            } else {
217                ""
218            };
219            let result_wrap = match &method.return_type {
220                // When returns_cow=true the core returns Cow<'_, T>: call .into_owned() to
221                // obtain an owned T before the binding→core From conversion.
222                // When returns_ref=true (or &T / Cow<'_, T> via the old flag), same treatment.
223                TypeRef::Named(n) if n == type_name && (method.returns_cow || method.returns_ref) => {
224                    ".into_owned().into()".to_string()
225                }
226                TypeRef::Named(_) if method.returns_cow || method.returns_ref => ".into_owned().into()".to_string(),
227                TypeRef::Named(n) if n == type_name => ".into()".to_string(),
228                TypeRef::Named(_) => ".into()".to_string(),
229                TypeRef::String | TypeRef::Bytes | TypeRef::Path => {
230                    if method.returns_ref {
231                        ".to_owned()".to_string()
232                    } else {
233                        ".into()".to_string()
234                    }
235                }
236                // Optional<Named>: when core returns Option<&T>, need .map(|v| v.clone().into())
237                TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
238                    if method.returns_ref {
239                        ".map(|v| v.clone().into())".to_string()
240                    } else {
241                        ".map(Into::into)".to_string()
242                    }
243                }
244                // Optional<String>: when core returns Option<&str>, need .map(|v| v.to_owned())
245                TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Bytes) => {
246                    if method.returns_ref {
247                        ".map(|v| v.to_owned())".to_string()
248                    } else {
249                        String::new()
250                    }
251                }
252                _ => String::new(),
253            };
254            if method.error_type.is_some() {
255                let err_conv = match cfg.async_pattern {
256                    AsyncPattern::Pyo3FutureIntoPy => {
257                        ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
258                    }
259                    AsyncPattern::NapiNativeAsync => {
260                        ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
261                    }
262                    AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
263                    _ => ".map_err(|e| e.to_string())",
264                };
265                format!(
266                    "{field_conversions}let result = {core_call}{err_conv}?;\n        Ok(result{newtype_suffix}{result_wrap})"
267                )
268            } else {
269                format!("{field_conversions}{core_call}{newtype_suffix}{result_wrap}")
270            }
271        } else {
272            gen_unimplemented_body(
273                &method.return_type,
274                &format!("{type_name}.{}", method.name),
275                method.error_type.is_some(),
276                cfg,
277                &method.params,
278            )
279        }
280    } else if method.is_async {
281        let inner_clone_line = if is_opaque {
282            "let inner = self.inner.clone();\n        "
283        } else {
284            ""
285        };
286        let core_call_str = make_async_core_call(&method.name);
287        gen_async_body(
288            &core_call_str,
289            cfg,
290            method.error_type.is_some(),
291            &async_result_wrap,
292            is_opaque,
293            inner_clone_line,
294            matches!(method.return_type, TypeRef::Unit),
295        )
296    } else {
297        let core_call = make_core_call(&method.name);
298        if method.error_type.is_some() {
299            // Backend-specific error conversion
300            let err_conv = match cfg.async_pattern {
301                AsyncPattern::Pyo3FutureIntoPy => {
302                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
303                }
304                AsyncPattern::NapiNativeAsync => {
305                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
306                }
307                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
308                _ => ".map_err(|e| e.to_string())",
309            };
310            if is_opaque {
311                if matches!(method.return_type, TypeRef::Unit) {
312                    // Unit return: avoid let_unit_value by not binding the result
313                    format!("{core_call}{err_conv}?;\n        Ok(())")
314                } else {
315                    let wrap = wrap_return(
316                        &result_expr,
317                        &method.return_type,
318                        type_name,
319                        opaque_types,
320                        is_opaque,
321                        method.returns_ref,
322                        method.returns_cow,
323                    );
324                    format!("let result = {core_call}{err_conv}?;\n        Ok({wrap})")
325                }
326            } else {
327                format!("{core_call}{err_conv}")
328            }
329        } else if is_opaque {
330            let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
331            wrap_return(
332                &unwrapped_call,
333                &method.return_type,
334                type_name,
335                opaque_types,
336                is_opaque,
337                method.returns_ref,
338                method.returns_cow,
339            )
340        } else {
341            core_call
342        }
343    };
344    // Prepend let bindings for non-opaque Named ref params (needed for borrow lifetime)
345    let body = if ref_let_bindings.is_empty() {
346        body
347    } else {
348        format!("{ref_let_bindings}{body}")
349    };
350
351    let needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
352    let self_param = match (needs_py, params.is_empty()) {
353        (true, true) => "&self, py: Python<'py>",
354        (true, false) => "&self, py: Python<'py>, ",
355        (false, true) => "&self",
356        (false, false) => "&self, ",
357    };
358
359    // For async PyO3 methods, override return type to PyResult<Bound<'py, PyAny>>
360    // and add the 'py lifetime generic on the method name.
361    let ret = if needs_py {
362        "PyResult<Bound<'py, PyAny>>".to_string()
363    } else {
364        ret
365    };
366    let method_lifetime = if needs_py { "<'py>" } else { "" };
367
368    // Wrap long signature if necessary
369    let (sig_start, sig_params, sig_end) = if self_param.len() + params.len() > 100 {
370        let wrapped_params = method
371            .params
372            .iter()
373            .map(|p| {
374                let ty = if p.optional {
375                    format!("Option<{}>", mapper.map_type(&p.ty))
376                } else {
377                    mapper.map_type(&p.ty)
378                };
379                format!("{}: {}", p.name, ty)
380            })
381            .collect::<Vec<_>>()
382            .join(",\n        ");
383        let py_param = if needs_py { "\n        py: Python<'py>," } else { "" };
384        (
385            format!(
386                "pub fn {}{method_lifetime}(\n        &self,{}\n        ",
387                method.name, py_param
388            ),
389            wrapped_params,
390            "\n    ) -> ".to_string(),
391        )
392    } else {
393        (
394            format!("pub fn {}{method_lifetime}({}", method.name, self_param),
395            params,
396            ") -> ".to_string(),
397        )
398    };
399
400    let mut out = String::with_capacity(1024);
401    // Per-item clippy suppression: too_many_arguments when >7 params (including &self and py)
402    let total_params = method.params.len() + 1 + if needs_py { 1 } else { 0 };
403    if total_params > 7 {
404        writeln!(out, "    #[allow(clippy::too_many_arguments)]").ok();
405    }
406    // Per-item clippy suppression: missing_errors_doc for Result-returning methods
407    if method.error_type.is_some() {
408        writeln!(out, "    #[allow(clippy::missing_errors_doc)]").ok();
409    }
410    // Per-item clippy suppression: should_implement_trait for trait-conflicting names
411    if is_trait_method_name(&method.name) {
412        writeln!(out, "    #[allow(clippy::should_implement_trait)]").ok();
413    }
414    if cfg.needs_signature {
415        let sig = function_sig_defaults(&method.params);
416        writeln!(out, "    {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
417    }
418    write!(
419        out,
420        "    {}{}{}{} {{\n        \
421         {body}\n    }}",
422        sig_start, sig_params, sig_end, ret,
423    )
424    .ok();
425    out
426}
427
428/// Generate a static method.
429pub fn gen_static_method(
430    method: &MethodDef,
431    mapper: &dyn TypeMapper,
432    cfg: &RustBindingConfig,
433    typ: &TypeDef,
434    adapter_bodies: &AdapterBodies,
435    opaque_types: &AHashSet<String>,
436) -> String {
437    let type_name = &typ.name;
438    // Use the full rust_path (with hyphens replaced by underscores) for core type references
439    let core_type_path = typ.rust_path.replace('-', "_");
440    let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
441    let params = function_params(&method.params, &map_fn);
442    let return_type = mapper.map_type(&method.return_type);
443    let ret = mapper.wrap_return(&return_type, method.error_type.is_some());
444
445    // Use let bindings when non-opaque Named params have is_ref=true
446    let has_ref_named_params = method
447        .params
448        .iter()
449        .any(|p| p.is_ref && matches!(&p.ty, TypeRef::Named(n) if !opaque_types.contains(n.as_str())));
450    let (call_args, ref_let_bindings) = if has_ref_named_params {
451        (
452            gen_call_args_with_let_bindings(&method.params, opaque_types),
453            gen_named_let_bindings_pub(&method.params, opaque_types),
454        )
455    } else {
456        (gen_call_args(&method.params, opaque_types), String::new())
457    };
458
459    let can_delegate = crate::shared::can_auto_delegate(method, opaque_types);
460
461    let body = if !can_delegate {
462        // Check if an adapter provides the body
463        let adapter_key = format!("{}.{}", type_name, method.name);
464        if let Some(adapter_body) = adapter_bodies.get(&adapter_key) {
465            adapter_body.clone()
466        } else {
467            gen_unimplemented_body(
468                &method.return_type,
469                &format!("{type_name}::{}", method.name),
470                method.error_type.is_some(),
471                cfg,
472                &method.params,
473            )
474        }
475    } else if method.is_async {
476        let core_call = format!("{core_type_path}::{}({call_args})", method.name);
477        let return_wrap = format!("{return_type}::from(result)");
478        gen_async_body(
479            &core_call,
480            cfg,
481            method.error_type.is_some(),
482            &return_wrap,
483            false,
484            "",
485            matches!(method.return_type, TypeRef::Unit),
486        )
487    } else {
488        let core_call = format!("{core_type_path}::{}({call_args})", method.name);
489        if method.error_type.is_some() {
490            // Backend-specific error conversion
491            let err_conv = match cfg.async_pattern {
492                AsyncPattern::Pyo3FutureIntoPy => {
493                    ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
494                }
495                AsyncPattern::NapiNativeAsync => {
496                    ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
497                }
498                AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
499                _ => ".map_err(|e| e.to_string())",
500            };
501            // Wrap the Ok value if the return type needs conversion (e.g. PathBuf→String)
502            let val_expr = apply_return_newtype_unwrap("val", &method.return_newtype_wrapper);
503            let wrapped = wrap_return(
504                &val_expr,
505                &method.return_type,
506                type_name,
507                opaque_types,
508                typ.is_opaque,
509                method.returns_ref,
510                method.returns_cow,
511            );
512            if wrapped == val_expr {
513                format!("{core_call}{err_conv}")
514            } else {
515                format!("{core_call}.map(|val| {wrapped}){err_conv}")
516            }
517        } else {
518            // Wrap return value for non-error case too (e.g. PathBuf→String)
519            let unwrapped_call = apply_return_newtype_unwrap(&core_call, &method.return_newtype_wrapper);
520            wrap_return(
521                &unwrapped_call,
522                &method.return_type,
523                type_name,
524                opaque_types,
525                typ.is_opaque,
526                method.returns_ref,
527                method.returns_cow,
528            )
529        }
530    };
531    // Prepend let bindings for non-opaque Named ref params
532    let body = if ref_let_bindings.is_empty() {
533        body
534    } else {
535        format!("{ref_let_bindings}{body}")
536    };
537
538    let static_needs_py = method.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
539
540    // For async PyO3 static methods, override return type and add lifetime generic.
541    let ret = if static_needs_py {
542        "PyResult<Bound<'py, PyAny>>".to_string()
543    } else {
544        ret
545    };
546    let method_lifetime = if static_needs_py { "<'py>" } else { "" };
547
548    // Wrap long signature if necessary
549    let (sig_start, sig_params, sig_end) = if params.len() > 100 {
550        let wrapped_params = method
551            .params
552            .iter()
553            .map(|p| {
554                let ty = if p.optional {
555                    format!("Option<{}>", mapper.map_type(&p.ty))
556                } else {
557                    mapper.map_type(&p.ty)
558                };
559                format!("{}: {}", p.name, ty)
560            })
561            .collect::<Vec<_>>()
562            .join(",\n        ");
563        // For async PyO3, add py parameter
564        if static_needs_py {
565            (
566                format!("pub fn {}{method_lifetime}(py: Python<'py>,\n        ", method.name),
567                wrapped_params,
568                "\n    ) -> ".to_string(),
569            )
570        } else {
571            (
572                format!("pub fn {}(\n        ", method.name),
573                wrapped_params,
574                "\n    ) -> ".to_string(),
575            )
576        }
577    } else if static_needs_py {
578        (
579            format!("pub fn {}{method_lifetime}(py: Python<'py>, ", method.name),
580            params,
581            ") -> ".to_string(),
582        )
583    } else {
584        (format!("pub fn {}(", method.name), params, ") -> ".to_string())
585    };
586
587    let mut out = String::with_capacity(1024);
588    // Per-item clippy suppression: too_many_arguments when >7 params (including py)
589    let total_params = method.params.len() + if static_needs_py { 1 } else { 0 };
590    if total_params > 7 {
591        writeln!(out, "    #[allow(clippy::too_many_arguments)]").ok();
592    }
593    // Per-item clippy suppression: missing_errors_doc for Result-returning methods
594    if method.error_type.is_some() {
595        writeln!(out, "    #[allow(clippy::missing_errors_doc)]").ok();
596    }
597    // Per-item clippy suppression: should_implement_trait for trait-conflicting names
598    if is_trait_method_name(&method.name) {
599        writeln!(out, "    #[allow(clippy::should_implement_trait)]").ok();
600    }
601    if let Some(attr) = cfg.static_attr {
602        writeln!(out, "    #[{attr}]").ok();
603    }
604    if cfg.needs_signature {
605        let sig = function_sig_defaults(&method.params);
606        writeln!(out, "    {}{}{}", cfg.signature_prefix, sig, cfg.signature_suffix).ok();
607    }
608    write!(
609        out,
610        "    {}{}{}{} {{\n        \
611         {body}\n    }}",
612        sig_start, sig_params, sig_end, ret,
613    )
614    .ok();
615    out
616}
617
618/// Generate a full methods impl block (non-opaque types).
619pub fn gen_impl_block(
620    typ: &TypeDef,
621    mapper: &dyn TypeMapper,
622    cfg: &RustBindingConfig,
623    adapter_bodies: &AdapterBodies,
624    opaque_types: &AHashSet<String>,
625) -> String {
626    let (instance, statics) = partition_methods(&typ.methods);
627    if instance.is_empty() && statics.is_empty() && typ.fields.is_empty() {
628        return String::new();
629    }
630
631    let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
632    let mut out = String::with_capacity(2048);
633    if let Some(block_attr) = cfg.method_block_attr {
634        writeln!(out, "#[{block_attr}]").ok();
635    }
636    writeln!(out, "impl {prefixed_name} {{").ok();
637
638    // Constructor
639    if !typ.fields.is_empty() {
640        out.push_str(&gen_constructor(typ, mapper, cfg));
641        out.push_str("\n\n");
642    }
643
644    // Instance methods
645    for m in &instance {
646        out.push_str(&gen_method(m, mapper, cfg, typ, false, opaque_types, adapter_bodies));
647        out.push_str("\n\n");
648    }
649
650    // Static methods
651    for m in &statics {
652        out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
653        out.push_str("\n\n");
654    }
655
656    // Trim trailing newlines inside impl block
657    let trimmed = out.trim_end();
658    let mut result = trimmed.to_string();
659    result.push_str("\n}");
660    result
661}
662
663/// Generate a full impl block for an opaque type, delegating methods to `self.inner`.
664///
665/// `opaque_types` is the set of type names that are opaque wrappers (use `Arc<inner>`).
666/// This is needed so that return-type wrapping uses the correct pattern for cross-type returns.
667pub fn gen_opaque_impl_block(
668    typ: &TypeDef,
669    mapper: &dyn TypeMapper,
670    cfg: &RustBindingConfig,
671    opaque_types: &AHashSet<String>,
672    adapter_bodies: &AdapterBodies,
673) -> String {
674    let (instance, statics) = partition_methods(&typ.methods);
675    if instance.is_empty() && statics.is_empty() {
676        return String::new();
677    }
678
679    let mut out = String::with_capacity(2048);
680    let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
681    if let Some(block_attr) = cfg.method_block_attr {
682        writeln!(out, "#[{block_attr}]").ok();
683    }
684    writeln!(out, "impl {prefixed_name} {{").ok();
685
686    // Instance methods — delegate to self.inner
687    for m in &instance {
688        out.push_str(&gen_method(m, mapper, cfg, typ, true, opaque_types, adapter_bodies));
689        out.push_str("\n\n");
690    }
691
692    // Static methods
693    for m in &statics {
694        out.push_str(&gen_static_method(m, mapper, cfg, typ, adapter_bodies, opaque_types));
695        out.push_str("\n\n");
696    }
697
698    let trimmed = out.trim_end();
699    let mut result = trimmed.to_string();
700    result.push_str("\n}");
701    result
702}