Skip to main content

uika_codegen/rust_gen/
classes.rs

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