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