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