Skip to main content

alef_codegen/generators/
methods.rs

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