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