Skip to main content

rusteal_codegen/rust_gen/
classes.rs

1use std::collections::HashSet;
2
3use crate::context::{CodegenContext, FuncEntry};
4use crate::defaults;
5use crate::naming::{escape_reserved, to_snake_case};
6use crate::schema::*;
7use crate::type_map::{self, ConversionKind, MappedType, ParamDirection};
8
9use super::delegates;
10use super::param_helpers;
11use super::properties::{self, PropertyContext};
12
13pub fn generate_class(class: &ClassInfo, ctx: &CodegenContext) -> String {
14    let mut out = String::with_capacity(8192);
15
16    out.push_str("use super::*;\n");
17
18    out.push_str(
19        "use rusteal_core::{UeClass, UeStruct, UeEnum, UeHandle, ValidHandle, Pinned, Checked};\n",
20    );
21
22    let current_module = ctx
23        .package_to_module
24        .get(&class.package)
25        .map(|s| s.as_str())
26        .unwrap_or("");
27
28    let mut modules: Vec<&String> = ctx.enabled_modules.iter().collect();
29    modules.sort();
30
31    for module in modules {
32        if module != current_module {
33            if let Some(feature) = ctx.feature_for_module(module) {
34                out.push_str(&format!("#[cfg(feature = \"{feature}\")]\n"));
35            }
36
37            out.push_str(&format!("use crate::{module}::*;\n"));
38        }
39    }
40
41    out.push('\n');
42
43    let name = &class.name;
44    let cpp_name = &class.cpp_name;
45
46    out.push_str(&format!(
47        "/// UE class `{cpp_name}`.\n\
48         pub struct {name};\n\n"
49    ));
50
51    let name_bytes_len = name.len();
52    let byte_lit = format!("b\"{name}\\0\"");
53
54    out.push_str(&format!(
55        "impl rusteal_core::UeClass for {name} {{\n\
56         \x20   fn static_class() -> rusteal_core::UClassHandle {{\n\
57         \x20       static CACHE: std::sync::OnceLock<rusteal_core::UClassHandle> = std::sync::OnceLock::new();\n\
58         \x20       *CACHE.get_or_init(|| unsafe {{\n\
59         \x20           rusteal_core::ffi_dispatch::reflection_get_static_class({byte_lit}.as_ptr(), {name_bytes_len})\n\
60         \x20       }})\n\
61         \x20   }}\n\
62         }}\n\n"
63    ));
64
65    if let Some(parent) = &class.super_class
66        && ctx.classes.contains_key(parent.as_str())
67    {
68        let parent_class = ctx.classes.get(parent.as_str()).unwrap();
69
70        let parent_module = ctx
71            .package_to_module
72            .get(&parent_class.package)
73            .map(|s| s.as_str())
74            .unwrap_or("");
75
76        if parent_module != current_module
77            && let Some(feature) = ctx.feature_for_module(parent_module)
78        {
79            out.push_str(&format!("#[cfg(feature = \"{feature}\")]\n"));
80        }
81
82        out.push_str(&format!(
83            "impl rusteal_core::HasParent for {name} {{\n\
84             \x20   type Parent = {parent};\n\
85             }}\n\n"
86        ));
87    }
88
89    let mut ancestor = Some(name.as_str());
90
91    while let Some(class_name) = ancestor {
92        let Some(ancestor_class) = ctx.classes.get(class_name) else {
93            break;
94        };
95
96        let ancestor_module = ctx
97            .package_to_module
98            .get(&ancestor_class.package)
99            .map(|s| s.as_str())
100            .unwrap_or("");
101
102        if ancestor_module != current_module
103            && let Some(feature) = ctx.feature_for_module(ancestor_module)
104        {
105            out.push_str(&format!("#[cfg(feature = \"{feature}\")]\n"));
106        }
107
108        out.push_str(&format!(
109            "impl rusteal_core::Inherits<{class_name}> for {name} {{}}\n"
110        ));
111
112        ancestor = ancestor_class.super_class.as_deref();
113    }
114
115    out.push('\n');
116
117    let mut seen_func_names: HashSet<String> = HashSet::new();
118    let mut class_funcs: Vec<&FuncEntry> = Vec::new();
119
120    for entry in ctx.func_table.iter().filter(|e| e.class_name == *name) {
121        if seen_func_names.insert(entry.rust_func_name.clone()) {
122            class_funcs.push(entry);
123        }
124    }
125
126    let (mut prop_names, deduped_props) =
127        properties::collect_deduped_properties(&class.props, Some(ctx));
128
129    let own_delegate_infos = delegates::collect_delegate_props(&class.props, name, ctx);
130
131    if deduped_props.is_empty() && class_funcs.is_empty() && own_delegate_infos.is_empty() {
132        return out;
133    }
134
135    let func_names: HashSet<String> = class_funcs
136        .iter()
137        .map(|e| escape_reserved(&e.rust_func_name))
138        .collect();
139
140    let mut suppress_setters: HashSet<String> = prop_names
141        .iter()
142        .filter(|n| n.starts_with("set_") && func_names.contains(n.as_str()))
143        .cloned()
144        .collect();
145
146    suppress_setters.extend(
147        deduped_props
148            .iter()
149            .filter(|p| properties::is_read_only_non_public(p) || p.setter.is_some())
150            .map(|p| format!("set_{}", properties::rust_property_name(p))),
151    );
152
153    for setter in &suppress_setters {
154        prop_names.remove(setter);
155    }
156
157    let class_funcs: Vec<&FuncEntry> = class_funcs
158        .into_iter()
159        .filter(|e| !prop_names.contains(&escape_reserved(&e.rust_func_name)))
160        .collect();
161
162    let pctx = PropertyContext {
163        find_prop_fn: "find_property".to_string(),
164        handle_expr: format!("{name}::static_class()"),
165        pre_access: "let h = self.handle();".to_string(),
166        container_expr: "h".to_string(),
167    };
168
169    delegates::generate_delegate_structs(&mut out, &own_delegate_infos, name);
170
171    let trait_name = format!("{name}Ext");
172
173    out.push_str(&format!(
174        "pub trait {trait_name}: rusteal_core::ValidHandle {{\n"
175    ));
176
177    for prop in &deduped_props {
178        properties::generate_property(&mut out, prop, &pctx, ctx, &suppress_setters);
179    }
180
181    delegates::generate_delegate_impls(&mut out, &own_delegate_infos);
182
183    let (static_funcs, instance_funcs): (Vec<&FuncEntry>, Vec<&FuncEntry>) =
184        class_funcs.iter().partition(|e| is_static_fn(e));
185
186    for entry in &instance_funcs {
187        generate_function(&mut out, entry, &entry.class_name, ctx);
188    }
189
190    out.push_str("}\n\n");
191
192    if !static_funcs.is_empty() {
193        out.push_str(&format!("impl {name} {{\n"));
194
195        for entry in &static_funcs {
196            generate_function(&mut out, entry, &entry.class_name, ctx);
197        }
198
199        out.push_str("}\n\n");
200    }
201
202    out.push_str(&format!(
203        "impl {trait_name} for rusteal_core::Checked<{name}> {{}}\n"
204    ));
205
206    out.push_str(&format!(
207        "impl {trait_name} for rusteal_core::Pinned<{name}> {{}}\n"
208    ));
209
210    out
211}
212
213fn is_static_fn(entry: &FuncEntry) -> bool {
214    entry.func.is_static || (entry.func.func_flags & FUNC_STATIC != 0)
215}
216
217pub(super) use type_map::container_elem_type_str;
218pub(super) use type_map::container_param_input_type;
219pub(super) use type_map::container_param_output_type;
220pub(super) use type_map::is_container_param;
221
222fn build_return_type(output_types: &[String]) -> String {
223    match output_types.len() {
224        0 => "()".to_string(),
225        1 => output_types[0].clone(),
226        _ => format!("({})", output_types.join(", ")),
227    }
228}
229
230fn scalar_out_rust_type_ctx(
231    mapped: &MappedType,
232    struct_name: Option<&str>,
233    ctx: &CodegenContext,
234) -> String {
235    match mapped.ffi_to_rust {
236        ConversionKind::StructOpaque => {
237            if let Some(sn) = struct_name
238                && let Some(si) = ctx.structs.get(sn)
239                && si.has_static_struct
240            {
241                return format!("rusteal_core::OwnedStruct<{}>", si.cpp_name);
242            }
243
244            mapped.rust_type.clone()
245        }
246        _ => mapped.rust_type.clone(),
247    }
248}
249
250pub(super) fn is_struct_owned(struct_name: Option<&str>, ctx: &CodegenContext) -> bool {
251    struct_name.is_some_and(|sn| ctx.structs.get(sn).is_some_and(|si| si.has_static_struct))
252}
253
254pub(super) fn is_scalar_output_returnable(dir: ParamDirection, mapped: &MappedType) -> bool {
255    if dir == ParamDirection::InOut && mapped.ffi_to_rust == ConversionKind::StructOpaque {
256        return false;
257    }
258
259    dir == ParamDirection::Out || dir == ParamDirection::InOut
260}
261
262fn generate_function(out: &mut String, entry: &FuncEntry, class_name: &str, ctx: &CodegenContext) {
263    let has_container = entry.func.params.iter().any(is_container_param);
264
265    if has_container {
266        generate_container_function(out, entry, class_name, ctx);
267    } else {
268        generate_scalar_function(out, entry, class_name, ctx);
269    }
270}
271
272fn generate_scalar_function(
273    out: &mut String,
274    entry: &FuncEntry,
275    class_name: &str,
276    ctx: &CodegenContext,
277) {
278    let func = &entry.func;
279    let rust_fn_name = escape_reserved(&entry.rust_func_name);
280    let func_id = entry.func_id;
281
282    let mut return_param: Option<&ParamInfo> = None;
283
284    for param in &func.params {
285        let dir = type_map::param_direction(param);
286
287        if dir == ParamDirection::Return {
288            return_param = Some(param);
289        }
290    }
291
292    let mut all_mapped: Vec<(&ParamInfo, ParamDirection, MappedType)> = Vec::new();
293    let mut all_supported = true;
294
295    for param in &func.params {
296        let dir = type_map::param_direction(param);
297
298        let mapped = type_map::map_property_type(
299            &param.prop_type,
300            param.class_name.as_deref(),
301            param.struct_name.as_deref(),
302            param.enum_name.as_deref(),
303            param.enum_underlying_type.as_deref(),
304            param.meta_class_name.as_deref(),
305            param.interface_name.as_deref(),
306        );
307
308        if !mapped.supported {
309            all_supported = false;
310            break;
311        }
312
313        all_mapped.push((param, dir, mapped));
314    }
315
316    if !all_supported {
317        out.push_str(&format!(
318            "    // Skipped: {}.{} (unsupported param type)\n\n",
319            class_name, func.name
320        ));
321
322        return;
323    }
324
325    let ret_mapped = return_param.map(|rp| {
326        type_map::map_property_type(
327            &rp.prop_type,
328            rp.class_name.as_deref(),
329            rp.struct_name.as_deref(),
330            rp.enum_name.as_deref(),
331            rp.enum_underlying_type.as_deref(),
332            rp.meta_class_name.as_deref(),
333            rp.interface_name.as_deref(),
334        )
335    });
336
337    let return_rust_type = {
338        let mut output_types = Vec::new();
339        if let Some(m) = &ret_mapped {
340            let rp_struct = return_param.and_then(|rp| rp.struct_name.as_deref());
341            output_types.push(scalar_out_rust_type_ctx(m, rp_struct, ctx));
342        }
343        for (param, dir, mapped) in &all_mapped {
344            if is_scalar_output_returnable(*dir, mapped) {
345                output_types.push(scalar_out_rust_type_ctx(
346                    mapped,
347                    param.struct_name.as_deref(),
348                    ctx,
349                ));
350            }
351        }
352        build_return_type(&output_types)
353    };
354
355    let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
356
357    let mut sig = String::new();
358
359    if is_static {
360        sig.push_str(&format!("    pub fn {rust_fn_name}("));
361    } else {
362        sig.push_str(&format!("    fn {rust_fn_name}(&self, "));
363    }
364
365    let mut param_names = Vec::new();
366    let mut default_unwraps: Vec<(String, String)> = Vec::new();
367
368    for (param, dir, mapped) in &all_mapped {
369        if *dir == ParamDirection::Return {
370            continue;
371        }
372
373        let pname = escape_reserved(&to_snake_case(&param.name));
374
375        let has_default = *dir == ParamDirection::In
376            && defaults::parse_default_literal(param, mapped, ctx).is_some();
377
378        if has_default {
379            let default_expr = defaults::parse_default_literal(param, mapped, ctx)
380                .expect("default literal must be parseable (has_default was true)");
381
382            default_unwraps.push((pname.clone(), default_expr));
383        }
384
385        match dir {
386            ParamDirection::In | ParamDirection::InOut => match mapped.rust_to_ffi {
387                ConversionKind::StringUtf8 => {
388                    if has_default {
389                        sig.push_str(&format!("{pname}: Option<&str>, "));
390                    } else {
391                        sig.push_str(&format!("{pname}: &str, "));
392                    }
393                }
394                ConversionKind::StructOpaque
395                    if *dir == ParamDirection::In
396                        && is_struct_owned(param.struct_name.as_deref(), ctx) =>
397                {
398                    let si = ctx
399                        .structs
400                        .get(
401                            param
402                                .struct_name
403                                .as_deref()
404                                .expect("StructOpaque param must have struct_name"),
405                        )
406                        .expect("struct must exist in context");
407
408                    sig.push_str(&format!(
409                        "{pname}: &rusteal_core::OwnedStruct<{}>, ",
410                        si.cpp_name
411                    ));
412                }
413                ConversionKind::StructOpaque if *dir == ParamDirection::InOut => {
414                    sig.push_str(&format!("{pname}: *mut u8, "));
415                }
416                _ => {
417                    if has_default {
418                        sig.push_str(&format!("{pname}: Option<{}>, ", mapped.rust_type));
419                    } else {
420                        sig.push_str(&format!("{pname}: {}, ", mapped.rust_type));
421                    }
422                }
423            },
424            ParamDirection::Out => {}
425            ParamDirection::Return => {}
426        }
427
428        param_names.push((pname, param, *dir, mapped));
429    }
430
431    if sig.ends_with(", ") {
432        sig.truncate(sig.len() - 2);
433    }
434
435    if return_rust_type == "()" {
436        sig.push(')');
437    } else {
438        sig.push_str(&format!(") -> {return_rust_type}"));
439    }
440
441    out.push_str(&sig);
442    out.push_str(" {\n");
443
444    for (pname, default_expr) in &default_unwraps {
445        out.push_str(&format!(
446            "        let {pname} = {pname}.unwrap_or({default_expr});\n"
447        ));
448    }
449
450    out.push_str("        {\n");
451
452    let mut ffi_params = String::new();
453
454    if !is_static {
455        ffi_params.push_str("rusteal_core::UObjectHandle, ");
456    }
457
458    for (_param, dir, mapped) in &all_mapped {
459        match dir {
460            ParamDirection::In | ParamDirection::InOut => match mapped.rust_to_ffi {
461                ConversionKind::StringUtf8 => {
462                    ffi_params.push_str("*const u8, u32, ");
463
464                    if *dir == ParamDirection::InOut {
465                        ffi_params.push_str("*mut u8, u32, *mut u32, ");
466                    }
467                }
468                ConversionKind::ObjectRef => {
469                    ffi_params.push_str("rusteal_core::UObjectHandle, ");
470                }
471                ConversionKind::EnumCast => {
472                    ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type));
473                }
474                ConversionKind::StructOpaque => {
475                    if *dir == ParamDirection::InOut {
476                        ffi_params.push_str("*mut u8, ");
477                    } else {
478                        ffi_params.push_str("*const u8, ");
479                    }
480                }
481                _ => {
482                    ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type));
483                }
484            },
485            ParamDirection::Out | ParamDirection::Return => match mapped.ffi_to_rust {
486                ConversionKind::StringUtf8 => {
487                    ffi_params.push_str("*mut u8, u32, *mut u32, ");
488                }
489                ConversionKind::ObjectRef => {
490                    ffi_params.push_str("*mut rusteal_core::UObjectHandle, ");
491                }
492                ConversionKind::StructOpaque => {
493                    ffi_params.push_str("*mut u8, ");
494                }
495                ConversionKind::EnumCast => {
496                    ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type));
497                }
498                _ => {
499                    ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type));
500                }
501            },
502        }
503    }
504
505    if ffi_params.ends_with(", ") {
506        ffi_params.truncate(ffi_params.len() - 2);
507    }
508
509    out.push_str(&format!(
510        "        const FN_ID: u32 = {func_id};\n\
511         \x20       type Fn = unsafe extern \"C\" fn({ffi_params}) -> rusteal_core::RustealErrorCode;\n\
512         \x20       let __rusteal_fn: Fn = unsafe {{ std::mem::transmute(*(rusteal_core::api().func_table.add(FN_ID as usize))) }};\n"
513    ));
514
515    if !is_static {
516        out.push_str("        let h = self.handle();\n");
517    }
518
519    if let Some(_rp) = return_param {
520        let rm = ret_mapped
521            .as_ref()
522            .expect("return param must have mapped type");
523
524        match rm.ffi_to_rust {
525            ConversionKind::ObjectRef => {
526                out.push_str("        let mut _ret = rusteal_core::UObjectHandle::null();\n");
527            }
528            ConversionKind::StringUtf8 => {
529                out.push_str("        let mut _ret_buf = vec![0u8; 512];\n");
530                out.push_str("        let mut _ret_len: u32 = 0;\n");
531            }
532            ConversionKind::EnumCast => {
533                out.push_str(&format!(
534                    "        let mut _ret: {} = 0;\n",
535                    rm.rust_ffi_type
536                ));
537            }
538            ConversionKind::StructOpaque => {
539                out.push_str("        let mut _ret_struct_buf = vec![0u8; 256];\n");
540            }
541            _ => {
542                let default = properties::default_value_for(&rm.rust_ffi_type);
543                out.push_str(&format!("        let mut _ret = {default};\n"));
544            }
545        }
546    }
547
548    for (param, dir, mapped) in &all_mapped {
549        if *dir == ParamDirection::Out {
550            param_helpers::emit_out_param_var_decl(out, param, mapped);
551        }
552
553        if *dir == ParamDirection::InOut {
554            param_helpers::emit_inout_string_buf_decl(out, param, mapped);
555        }
556    }
557
558    out.push_str("        rusteal_core::ffi_infallible(unsafe { __rusteal_fn(");
559
560    if !is_static {
561        out.push_str("h, ");
562    }
563
564    for (param, dir, mapped) in &all_mapped {
565        let pname = escape_reserved(&to_snake_case(&param.name));
566
567        match dir {
568            ParamDirection::In | ParamDirection::InOut => {
569                match mapped.rust_to_ffi {
570                    ConversionKind::StringUtf8 => {
571                        out.push_str(&format!("{pname}.as_ptr(), {pname}.len() as u32, "));
572
573                        if *dir == ParamDirection::InOut {
574                            out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
575                        }
576                    }
577                    ConversionKind::ObjectRef => {
578                        out.push_str(&format!(
579                            "rusteal_core::ObjectPointer::object_handle(&{pname}), "
580                        ));
581                    }
582                    ConversionKind::EnumCast => {
583                        out.push_str(&format!("{pname} as {}, ", mapped.rust_ffi_type));
584                    }
585                    ConversionKind::StructOpaque
586                        if *dir == ParamDirection::In
587                            && is_struct_owned(param.struct_name.as_deref(), ctx) =>
588                    {
589                        out.push_str(&format!("{pname}.as_bytes().as_ptr(), "));
590                    }
591                    _ => {
592                        out.push_str(&format!("{pname}, "));
593                    }
594                }
595            }
596            ParamDirection::Out => match mapped.ffi_to_rust {
597                ConversionKind::StructOpaque => {
598                    out.push_str(&format!("{pname}_buf.as_mut_ptr(), "));
599                }
600                ConversionKind::StringUtf8 => {
601                    out.push_str(&format!(
602                        "{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "
603                    ));
604                }
605                _ => {
606                    out.push_str(&format!("&mut {pname}, "));
607                }
608            },
609            ParamDirection::Return => {
610                let rm = ret_mapped
611                    .as_ref()
612                    .expect("return param must have mapped type");
613
614                match rm.ffi_to_rust {
615                    ConversionKind::StringUtf8 => {
616                        out.push_str(
617                            "_ret_buf.as_mut_ptr(), _ret_buf.len() as u32, &mut _ret_len, ",
618                        );
619                    }
620                    ConversionKind::ObjectRef => {
621                        out.push_str("&mut _ret, ");
622                    }
623                    ConversionKind::StructOpaque => {
624                        out.push_str("_ret_struct_buf.as_mut_ptr(), ");
625                    }
626                    _ => {
627                        out.push_str("&mut _ret, ");
628                    }
629                }
630            }
631        }
632    }
633
634    let out_len = out.len();
635
636    if out.ends_with(", ") {
637        out.truncate(out_len - 2);
638    }
639
640    out.push_str(") });\n");
641
642    {
643        let mut return_parts = Vec::new();
644
645        if let Some(rp) = return_param {
646            let rm = ret_mapped
647                .as_ref()
648                .expect("return param must have mapped type");
649
650            match rm.ffi_to_rust {
651                ConversionKind::ObjectRef => {
652                    return_parts.push(
653                        "unsafe { rusteal_core::ObjectPointer::from_object_handle(_ret) }"
654                            .to_string(),
655                    );
656                }
657                ConversionKind::StringUtf8 => {
658                    out.push_str("        _ret_buf.truncate(_ret_len as usize);\n");
659
660                    out.push_str(
661                        "        let _ret_str = String::from_utf8_lossy(&_ret_buf).into_owned();\n",
662                    );
663
664                    return_parts.push("_ret_str".to_string());
665                }
666                ConversionKind::EnumCast => {
667                    let rt = &rm.rust_type;
668
669                    let actual_repr = rp
670                        .enum_name
671                        .as_deref()
672                        .and_then(|en| ctx.enum_actual_repr(en))
673                        .unwrap_or(&rm.rust_ffi_type);
674
675                    out.push_str(&format!("        let _ret_enum = {rt}::from_value(_ret as {actual_repr}).expect(\"unknown enum value\");\n"));
676                    return_parts.push("_ret_enum".to_string());
677                }
678                ConversionKind::StructOpaque => {
679                    if is_struct_owned(rp.struct_name.as_deref(), ctx) {
680                        out.push_str("        let _ret_owned = rusteal_core::OwnedStruct::from_bytes(_ret_struct_buf);\n");
681                        return_parts.push("_ret_owned".to_string());
682                    } else {
683                        out.push_str("        let _ret_ptr = _ret_struct_buf.as_ptr();\n");
684                        out.push_str("        std::mem::forget(_ret_struct_buf);\n");
685                        return_parts.push("_ret_ptr".to_string());
686                    }
687                }
688                _ => {
689                    return_parts.push("_ret".to_string());
690                }
691            }
692        }
693
694        for (param, dir, mapped) in &all_mapped {
695            if !is_scalar_output_returnable(*dir, mapped) {
696                continue;
697            }
698
699            return_parts.push(param_helpers::emit_out_param_conversion(
700                out, param, mapped, ctx,
701            ));
702        }
703
704        param_helpers::emit_return_expr(out, &return_parts);
705    }
706
707    out.push_str("        }\n");
708    out.push_str("    }\n\n");
709}
710
711struct ContainerParamMeta<'a> {
712    param: &'a ParamInfo,
713    dir: ParamDirection,
714    index: usize,
715}
716
717fn generate_container_function(
718    out: &mut String,
719    entry: &FuncEntry,
720    class_name: &str,
721    ctx: &CodegenContext,
722) {
723    let func = &entry.func;
724    let rust_fn_name = escape_reserved(&entry.rust_func_name);
725    let func_id = entry.func_id;
726    let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
727
728    let ue_name = if func.ue_name.is_empty() {
729        &entry.func_name
730    } else {
731        &func.ue_name
732    };
733
734    let mut container_params: Vec<ContainerParamMeta> = Vec::new();
735
736    for param in &func.params {
737        if is_container_param(param) {
738            let dir = type_map::param_direction(param);
739            let index = container_params.len();
740            container_params.push(ContainerParamMeta { param, dir, index });
741        }
742    }
743
744    let n_containers = container_params.len();
745
746    let mut return_param: Option<&ParamInfo> = None;
747    let mut all_supported = true;
748
749    for param in &func.params {
750        let dir = type_map::param_direction(param);
751
752        if dir == ParamDirection::Return {
753            return_param = Some(param);
754        }
755
756        if is_container_param(param) {
757            if (dir == ParamDirection::In || dir == ParamDirection::InOut)
758                && container_param_input_type(param, ctx).is_none()
759            {
760                all_supported = false;
761                break;
762            }
763
764            if (dir == ParamDirection::Out
765                || dir == ParamDirection::Return
766                || dir == ParamDirection::InOut)
767                && container_param_output_type(param, ctx).is_none()
768            {
769                all_supported = false;
770                break;
771            }
772        } else {
773            let mapped = type_map::map_property_type(
774                &param.prop_type,
775                param.class_name.as_deref(),
776                param.struct_name.as_deref(),
777                param.enum_name.as_deref(),
778                param.enum_underlying_type.as_deref(),
779                param.meta_class_name.as_deref(),
780                param.interface_name.as_deref(),
781            );
782
783            if !mapped.supported {
784                all_supported = false;
785                break;
786            }
787        }
788    }
789
790    if !all_supported {
791        out.push_str(&format!(
792            "    // Skipped: {}.{} (unsupported container inner type)\n\n",
793            class_name, func.name
794        ));
795
796        return;
797    }
798
799    let mut output_types = Vec::new();
800    let mut scalar_return_mapped: Option<MappedType> = None;
801
802    if let Some(rp) = return_param {
803        if is_container_param(rp) {
804            output_types.push(
805                container_param_output_type(rp, ctx)
806                    .expect("container return type should be resolvable"),
807            );
808        } else {
809            let rm = type_map::map_property_type(
810                &rp.prop_type,
811                rp.class_name.as_deref(),
812                rp.struct_name.as_deref(),
813                rp.enum_name.as_deref(),
814                rp.enum_underlying_type.as_deref(),
815                rp.meta_class_name.as_deref(),
816                rp.interface_name.as_deref(),
817            );
818
819            output_types.push(scalar_out_rust_type_ctx(
820                &rm,
821                rp.struct_name.as_deref(),
822                ctx,
823            ));
824
825            scalar_return_mapped = Some(rm);
826        }
827    }
828
829    for param in &func.params {
830        let dir = type_map::param_direction(param);
831
832        if dir == ParamDirection::Out || dir == ParamDirection::InOut {
833            if is_container_param(param) {
834                output_types.push(
835                    container_param_output_type(param, ctx)
836                        .expect("container out-param type should be resolvable"),
837                );
838            } else {
839                let rm = type_map::map_property_type(
840                    &param.prop_type,
841                    param.class_name.as_deref(),
842                    param.struct_name.as_deref(),
843                    param.enum_name.as_deref(),
844                    param.enum_underlying_type.as_deref(),
845                    param.meta_class_name.as_deref(),
846                    param.interface_name.as_deref(),
847                );
848
849                if is_scalar_output_returnable(dir, &rm) {
850                    output_types.push(scalar_out_rust_type_ctx(
851                        &rm,
852                        param.struct_name.as_deref(),
853                        ctx,
854                    ));
855                }
856            }
857        }
858    }
859
860    let return_rust_type = build_return_type(&output_types);
861
862    let mut sig = String::new();
863
864    if is_static {
865        sig.push_str(&format!("    pub fn {rust_fn_name}("));
866    } else {
867        sig.push_str(&format!("    fn {rust_fn_name}(&self, "));
868    }
869
870    let mut default_unwraps: Vec<(String, String)> = Vec::new();
871
872    for param in &func.params {
873        let dir = type_map::param_direction(param);
874
875        if dir == ParamDirection::Return || dir == ParamDirection::Out {
876            continue;
877        }
878
879        let pname = escape_reserved(&to_snake_case(&param.name));
880
881        if is_container_param(param) {
882            let input_type = container_param_input_type(param, ctx)
883                .expect("container input type should be resolvable");
884
885            sig.push_str(&format!("{pname}: {input_type}, "));
886        } else {
887            let mapped = map_param(param);
888
889            let has_default = dir == ParamDirection::In
890                && defaults::parse_default_literal(param, &mapped, ctx).is_some();
891
892            if has_default {
893                let default_expr = defaults::parse_default_literal(param, &mapped, ctx)
894                    .expect("default literal must be parseable (has_default was true)");
895
896                default_unwraps.push((pname.clone(), default_expr));
897            }
898
899            match mapped.rust_to_ffi {
900                ConversionKind::StringUtf8 => {
901                    if has_default {
902                        sig.push_str(&format!("{pname}: Option<&str>, "));
903                    } else {
904                        sig.push_str(&format!("{pname}: &str, "));
905                    }
906                }
907                ConversionKind::StructOpaque
908                    if dir == ParamDirection::In
909                        && is_struct_owned(param.struct_name.as_deref(), ctx) =>
910                {
911                    let si = ctx
912                        .structs
913                        .get(
914                            param
915                                .struct_name
916                                .as_deref()
917                                .expect("StructOpaque param must have struct_name"),
918                        )
919                        .expect("struct must exist in context");
920
921                    sig.push_str(&format!(
922                        "{pname}: &rusteal_core::OwnedStruct<{}>, ",
923                        si.cpp_name
924                    ));
925                }
926                ConversionKind::StructOpaque if dir == ParamDirection::InOut => {
927                    sig.push_str(&format!("{pname}: *mut u8, "));
928                }
929                _ => {
930                    if has_default {
931                        sig.push_str(&format!("{pname}: Option<{}>, ", mapped.rust_type));
932                    } else {
933                        sig.push_str(&format!("{pname}: {}, ", mapped.rust_type));
934                    }
935                }
936            }
937        }
938    }
939
940    if sig.ends_with(", ") {
941        sig.truncate(sig.len() - 2);
942    }
943
944    if return_rust_type == "()" {
945        sig.push(')');
946    } else {
947        sig.push_str(&format!(") -> {return_rust_type}"));
948    }
949
950    out.push_str(&sig);
951    out.push_str(" {\n");
952
953    for (pname, default_expr) in &default_unwraps {
954        out.push_str(&format!(
955            "        let {pname} = {pname}.unwrap_or({default_expr});\n"
956        ));
957    }
958
959    out.push_str("        {\n");
960
961    let ue_name_len = ue_name.len();
962    let ue_name_byte_lit = format!("b\"{ue_name}\\0\"");
963
964    out.push_str(&format!(
965        "        const FN_ID: u32 = {func_id};\n\
966         \x20       static CPROPS: std::sync::OnceLock<[rusteal_core::FPropertyHandle; {n_containers}]> = std::sync::OnceLock::new();\n\
967         \x20       let __cprops = CPROPS.get_or_init(|| unsafe {{\n\
968         \x20           let __ufunc = ((*rusteal_core::api().reflection).find_function_by_class)(\n\
969         \x20               {class_name}::static_class(),\n\
970         \x20               {ue_name_byte_lit}.as_ptr(), {ue_name_len});\n\
971         \x20           [\n"
972    ));
973
974    for cp in &container_params {
975        let param_name = &cp.param.name;
976        let param_name_len = param_name.len();
977        let param_byte_lit = format!("b\"{param_name}\\0\"");
978
979        out.push_str(&format!(
980            "                ((*rusteal_core::api().reflection).get_function_param)(\n\
981             \x20                   __ufunc, {param_byte_lit}.as_ptr(), {param_name_len}),\n"
982        ));
983    }
984
985    out.push_str(
986        "            ]\n\
987         \x20       });\n",
988    );
989
990    let mut ffi_params = String::new();
991
992    if !is_static {
993        ffi_params.push_str("rusteal_core::UObjectHandle, ");
994    }
995
996    for param in &func.params {
997        let dir = type_map::param_direction(param);
998
999        if is_container_param(param) {
1000            ffi_params.push_str("*mut u8, *mut u8, ");
1001        } else {
1002            let mapped = map_param(param);
1003
1004            match dir {
1005                ParamDirection::In | ParamDirection::InOut => match mapped.rust_to_ffi {
1006                    ConversionKind::StringUtf8 => {
1007                        ffi_params.push_str("*const u8, u32, ");
1008
1009                        if dir == ParamDirection::InOut {
1010                            ffi_params.push_str("*mut u8, u32, *mut u32, ");
1011                        }
1012                    }
1013                    ConversionKind::ObjectRef => {
1014                        ffi_params.push_str("rusteal_core::UObjectHandle, ")
1015                    }
1016                    ConversionKind::EnumCast => {
1017                        ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type))
1018                    }
1019                    ConversionKind::StructOpaque => {
1020                        if dir == ParamDirection::InOut {
1021                            ffi_params.push_str("*mut u8, ");
1022                        } else {
1023                            ffi_params.push_str("*const u8, ");
1024                        }
1025                    }
1026                    _ => ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type)),
1027                },
1028                ParamDirection::Out | ParamDirection::Return => match mapped.ffi_to_rust {
1029                    ConversionKind::StringUtf8 => ffi_params.push_str("*mut u8, u32, *mut u32, "),
1030                    ConversionKind::ObjectRef => {
1031                        ffi_params.push_str("*mut rusteal_core::UObjectHandle, ")
1032                    }
1033                    ConversionKind::StructOpaque => ffi_params.push_str("*mut u8, "),
1034                    ConversionKind::EnumCast => {
1035                        ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type))
1036                    }
1037                    _ => ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type)),
1038                },
1039            }
1040        }
1041    }
1042
1043    if ffi_params.ends_with(", ") {
1044        ffi_params.truncate(ffi_params.len() - 2);
1045    }
1046
1047    out.push_str(&format!(
1048        "        type Fn = unsafe extern \"C\" fn({ffi_params}) -> rusteal_core::RustealErrorCode;\n\
1049         \x20       let __rusteal_fn: Fn = unsafe {{ std::mem::transmute(*(rusteal_core::api().func_table.add(FN_ID as usize))) }};\n"
1050    ));
1051
1052    if !is_static {
1053        out.push_str("        let h = self.handle();\n");
1054    }
1055
1056    for cp in &container_params {
1057        let idx = cp.index;
1058
1059        out.push_str(&format!(
1060            "        let __temp_{idx} = unsafe {{ ((*rusteal_core::api().container).alloc_temp)(__cprops[{idx}]) }};\n"
1061        ));
1062    }
1063
1064    for cp in &container_params {
1065        if cp.dir != ParamDirection::In && cp.dir != ParamDirection::InOut {
1066            continue;
1067        }
1068
1069        let idx = cp.index;
1070        let pname = escape_reserved(&to_snake_case(&cp.param.name));
1071        emit_container_populate(out, cp.param, idx, &pname, ctx);
1072    }
1073
1074    let ret_mapped = scalar_return_mapped.as_ref();
1075
1076    if let Some(rm) = ret_mapped {
1077        match rm.ffi_to_rust {
1078            ConversionKind::ObjectRef => {
1079                out.push_str(
1080                    "        let mut __scalar_ret = rusteal_core::UObjectHandle::null();\n",
1081                );
1082            }
1083            ConversionKind::StringUtf8 => {
1084                out.push_str("        let mut __scalar_ret_buf = vec![0u8; 512];\n");
1085                out.push_str("        let mut __scalar_ret_len: u32 = 0;\n");
1086            }
1087            ConversionKind::EnumCast => {
1088                out.push_str(&format!(
1089                    "        let mut __scalar_ret: {} = 0;\n",
1090                    rm.rust_ffi_type
1091                ));
1092            }
1093            ConversionKind::StructOpaque => {
1094                out.push_str("        let mut __scalar_ret_buf = vec![0u8; 256];\n");
1095            }
1096            _ => {
1097                let default = properties::default_value_for(&rm.rust_ffi_type);
1098                out.push_str(&format!("        let mut __scalar_ret = {default};\n"));
1099            }
1100        }
1101    }
1102
1103    for param in &func.params {
1104        let dir = type_map::param_direction(param);
1105
1106        if dir == ParamDirection::Out && !is_container_param(param) {
1107            let mapped = map_param(param);
1108            param_helpers::emit_out_param_var_decl(out, param, &mapped);
1109        }
1110
1111        if dir == ParamDirection::InOut && !is_container_param(param) {
1112            let mapped = map_param(param);
1113            param_helpers::emit_inout_string_buf_decl(out, param, &mapped);
1114        }
1115    }
1116
1117    out.push_str("        let __result = unsafe { __rusteal_fn(");
1118
1119    if !is_static {
1120        out.push_str("h, ");
1121    }
1122
1123    for param in &func.params {
1124        let dir = type_map::param_direction(param);
1125
1126        if is_container_param(param) {
1127            let cp = container_params
1128                .iter()
1129                .find(|c| std::ptr::eq(c.param, param))
1130                .expect("container param must have matching metadata");
1131
1132            let idx = cp.index;
1133            out.push_str(&format!("__temp_{idx}, __cprops[{idx}].0 as *mut u8, "));
1134        } else {
1135            let pname = escape_reserved(&to_snake_case(&param.name));
1136            let mapped = map_param(param);
1137
1138            match dir {
1139                ParamDirection::In | ParamDirection::InOut => match mapped.rust_to_ffi {
1140                    ConversionKind::StringUtf8 => {
1141                        out.push_str(&format!("{pname}.as_ptr(), {pname}.len() as u32, "));
1142
1143                        if dir == ParamDirection::InOut {
1144                            out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
1145                        }
1146                    }
1147                    ConversionKind::ObjectRef => {
1148                        out.push_str(&format!(
1149                            "rusteal_core::ObjectPointer::object_handle(&{pname}), "
1150                        ));
1151                    }
1152                    ConversionKind::EnumCast => {
1153                        out.push_str(&format!("{pname} as {}, ", mapped.rust_ffi_type));
1154                    }
1155                    ConversionKind::StructOpaque
1156                        if dir == ParamDirection::In
1157                            && is_struct_owned(param.struct_name.as_deref(), ctx) =>
1158                    {
1159                        out.push_str(&format!("{pname}.as_bytes().as_ptr(), "));
1160                    }
1161                    _ => {
1162                        out.push_str(&format!("{pname}, "));
1163                    }
1164                },
1165                ParamDirection::Out => {
1166                    match mapped.ffi_to_rust {
1167                        ConversionKind::StructOpaque => {
1168                            out.push_str(&format!("{pname}_buf.as_mut_ptr(), "));
1169                        }
1170                        ConversionKind::StringUtf8 => {
1171                            out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
1172                        }
1173                        _ => {
1174                            out.push_str(&format!("&mut {pname}, "));
1175                        }
1176                    }
1177                }
1178                ParamDirection::Return => {
1179                    let rm = ret_mapped.expect("return param must have mapped type");
1180
1181                    match rm.ffi_to_rust {
1182                        ConversionKind::StringUtf8 => {
1183                            out.push_str("__scalar_ret_buf.as_mut_ptr(), __scalar_ret_buf.len() as u32, &mut __scalar_ret_len, ");
1184                        }
1185                        ConversionKind::ObjectRef => {
1186                            out.push_str("&mut __scalar_ret, ");
1187                        }
1188                        ConversionKind::StructOpaque => {
1189                            out.push_str("__scalar_ret_buf.as_mut_ptr(), ");
1190                        }
1191                        _ => {
1192                            out.push_str("&mut __scalar_ret, ");
1193                        }
1194                    }
1195                }
1196            }
1197        }
1198    }
1199
1200    let out_len = out.len();
1201
1202    if out.ends_with(", ") {
1203        out.truncate(out_len - 2);
1204    }
1205
1206    out.push_str(") };\n");
1207
1208    for cp in &container_params {
1209        if cp.dir != ParamDirection::Out
1210            && cp.dir != ParamDirection::Return
1211            && cp.dir != ParamDirection::InOut
1212        {
1213            continue;
1214        }
1215
1216        let idx = cp.index;
1217        emit_container_read(out, cp.param, idx, ctx);
1218    }
1219
1220    out.push_str("        unsafe {\n");
1221
1222    for cp in &container_params {
1223        let idx = cp.index;
1224
1225        out.push_str(&format!(
1226            "            ((*rusteal_core::api().container).free_temp)(__cprops[{idx}], __temp_{idx});\n"
1227        ));
1228    }
1229
1230    out.push_str("        }\n");
1231
1232    out.push_str("        rusteal_core::ffi_infallible(__result);\n");
1233
1234    emit_container_return(
1235        out,
1236        return_param,
1237        ret_mapped,
1238        &container_params,
1239        &func.params,
1240        ctx,
1241    );
1242
1243    out.push_str("        }\n");
1244    out.push_str("    }\n\n");
1245}
1246
1247fn emit_container_populate(
1248    out: &mut String,
1249    param: &ParamInfo,
1250    idx: usize,
1251    pname: &str,
1252    ctx: &CodegenContext,
1253) {
1254    let elem_type =
1255        container_elem_type_str(param, ctx).expect("container element type must be resolvable");
1256
1257    match param.prop_type.as_str() {
1258        "ArrayProperty" => {
1259            out.push_str(&format!(
1260                "        {{\n\
1261                 \x20           let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1262                 \x20           let __arr = rusteal_core::UeArray::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1263                 \x20           for __elem in {pname} {{\n\
1264                 \x20               let _ = __arr.push(__elem);\n\
1265                 \x20           }}\n\
1266                 \x20       }}\n"
1267            ));
1268        }
1269        "SetProperty" => {
1270            out.push_str(&format!(
1271                "        {{\n\
1272                 \x20           let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1273                 \x20           let __set = rusteal_core::UeSet::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1274                 \x20           for __elem in {pname} {{\n\
1275                 \x20               let _ = __set.add(__elem);\n\
1276                 \x20           }}\n\
1277                 \x20       }}\n"
1278            ));
1279        }
1280        "MapProperty" => {
1281            out.push_str(&format!(
1282                "        {{\n\
1283                 \x20           let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1284                 \x20           let __map = rusteal_core::UeMap::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1285                 \x20           for (__k, __v) in {pname} {{\n\
1286                 \x20               let _ = __map.add(__k, __v);\n\
1287                 \x20           }}\n\
1288                 \x20       }}\n"
1289            ));
1290        }
1291        _ => {}
1292    }
1293}
1294
1295fn emit_container_read(out: &mut String, param: &ParamInfo, idx: usize, ctx: &CodegenContext) {
1296    let elem_type =
1297        container_elem_type_str(param, ctx).expect("container element type must be resolvable");
1298
1299    match param.prop_type.as_str() {
1300        "ArrayProperty" => {
1301            out.push_str(&format!(
1302                "        let __out_{idx} = if __result == rusteal_core::RustealErrorCode::Ok {{\n\
1303                 \x20           let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1304                 \x20           let __arr = rusteal_core::UeArray::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1305                 \x20           let __len = __arr.len().unwrap_or(0);\n\
1306                 \x20           let mut __v = Vec::with_capacity(__len);\n\
1307                 \x20           for __i in 0..__len {{\n\
1308                 \x20               if let Ok(__val) = __arr.get(__i) {{ __v.push(__val); }}\n\
1309                 \x20           }}\n\
1310                 \x20           __v\n\
1311                 \x20       }} else {{ Vec::new() }};\n"
1312            ));
1313        }
1314        "SetProperty" => {
1315            out.push_str(&format!(
1316                "        let __out_{idx} = if __result == rusteal_core::RustealErrorCode::Ok {{\n\
1317                 \x20           let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1318                 \x20           let __set = rusteal_core::UeSet::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1319                 \x20           let __len = __set.len().unwrap_or(0);\n\
1320                 \x20           let mut __v = Vec::with_capacity(__len);\n\
1321                 \x20           for __i in 0..__len {{\n\
1322                 \x20               if let Ok(__val) = __set.get_element(__i) {{ __v.push(__val); }}\n\
1323                 \x20           }}\n\
1324                 \x20           __v\n\
1325                 \x20       }} else {{ Vec::new() }};\n"
1326            ));
1327        }
1328        "MapProperty" => {
1329            out.push_str(&format!(
1330                "        let __out_{idx} = if __result == rusteal_core::RustealErrorCode::Ok {{\n\
1331                 \x20           let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1332                 \x20           let __map = rusteal_core::UeMap::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1333                 \x20           let __len = __map.len().unwrap_or(0);\n\
1334                 \x20           let mut __v = Vec::with_capacity(__len);\n\
1335                 \x20           for __i in 0..__len {{\n\
1336                 \x20               if let Ok(__pair) = __map.get_pair(__i) {{ __v.push(__pair); }}\n\
1337                 \x20           }}\n\
1338                 \x20           __v\n\
1339                 \x20       }} else {{ Vec::new() }};\n"
1340            ));
1341        }
1342        _ => {}
1343    }
1344}
1345
1346fn emit_container_return(
1347    out: &mut String,
1348    return_param: Option<&ParamInfo>,
1349    ret_mapped: Option<&MappedType>,
1350    container_params: &[ContainerParamMeta],
1351    func_params: &[ParamInfo],
1352    ctx: &CodegenContext,
1353) {
1354    let mut return_parts = Vec::new();
1355
1356    if let Some(rp) = return_param {
1357        if is_container_param(rp) {
1358            let cp = container_params
1359                .iter()
1360                .find(|c| c.dir == ParamDirection::Return)
1361                .expect("container return param must exist");
1362
1363            return_parts.push(format!("__out_{}", cp.index));
1364        } else if let Some(rm) = ret_mapped {
1365            match rm.ffi_to_rust {
1366                ConversionKind::ObjectRef => {
1367                    return_parts.push(
1368                        "unsafe { rusteal_core::ObjectPointer::from_object_handle(__scalar_ret) }"
1369                            .to_string(),
1370                    );
1371                }
1372                ConversionKind::StringUtf8 => {
1373                    out.push_str("        __scalar_ret_buf.truncate(__scalar_ret_len as usize);\n");
1374                    out.push_str("        let __scalar_str = String::from_utf8_lossy(&__scalar_ret_buf).into_owned();\n");
1375                    return_parts.push("__scalar_str".to_string());
1376                }
1377                ConversionKind::EnumCast => {
1378                    let rt = &rm.rust_type;
1379
1380                    let rp_ref =
1381                        return_param.expect("return_param must be Some in return conversion");
1382
1383                    let actual_repr = rp_ref
1384                        .enum_name
1385                        .as_deref()
1386                        .and_then(|en| ctx.enum_actual_repr(en))
1387                        .unwrap_or(&rm.rust_ffi_type);
1388
1389                    out.push_str(&format!(
1390                        "        let __scalar_enum = {rt}::from_value(__scalar_ret as {actual_repr}).expect(\"unknown enum value\");\n"
1391                    ));
1392
1393                    return_parts.push("__scalar_enum".to_string());
1394                }
1395                ConversionKind::StructOpaque => {
1396                    let rp_ref =
1397                        return_param.expect("return_param must be Some in return conversion");
1398
1399                    if is_struct_owned(rp_ref.struct_name.as_deref(), ctx) {
1400                        out.push_str("        let __scalar_owned = rusteal_core::OwnedStruct::from_bytes(__scalar_ret_buf);\n");
1401                        return_parts.push("__scalar_owned".to_string());
1402                    } else {
1403                        out.push_str("        let __scalar_ptr = __scalar_ret_buf.as_ptr();\n");
1404                        out.push_str("        std::mem::forget(__scalar_ret_buf);\n");
1405                        return_parts.push("__scalar_ptr".to_string());
1406                    }
1407                }
1408                _ => {
1409                    return_parts.push("__scalar_ret".to_string());
1410                }
1411            }
1412        }
1413    }
1414
1415    for param in func_params {
1416        let dir = type_map::param_direction(param);
1417
1418        if dir != ParamDirection::Out && dir != ParamDirection::InOut {
1419            continue;
1420        }
1421
1422        if is_container_param(param) {
1423            let cp = container_params
1424                .iter()
1425                .find(|c| std::ptr::eq(c.param, param))
1426                .expect("container param must have matching metadata");
1427
1428            return_parts.push(format!("__out_{}", cp.index));
1429        } else {
1430            let mapped = map_param(param);
1431
1432            if !is_scalar_output_returnable(dir, &mapped) {
1433                continue;
1434            }
1435
1436            return_parts.push(param_helpers::emit_out_param_conversion(
1437                out, param, &mapped, ctx,
1438            ));
1439        }
1440    }
1441
1442    param_helpers::emit_return_expr(out, &return_parts);
1443}
1444
1445pub(super) fn map_param(param: &ParamInfo) -> MappedType {
1446    type_map::map_property_type(
1447        &param.prop_type,
1448        param.class_name.as_deref(),
1449        param.struct_name.as_deref(),
1450        param.enum_name.as_deref(),
1451        param.enum_underlying_type.as_deref(),
1452        param.meta_class_name.as_deref(),
1453        param.interface_name.as_deref(),
1454    )
1455}