1use 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
14fn 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
35fn 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
66pub fn generate_class(class: &ClassInfo, ctx: &CodegenContext) -> String {
68 let mut out = String::with_capacity(8192);
69
70 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; let cpp_name = &class.cpp_name; out.push_str(&format!(
90 "/// UE class `{cpp_name}`.\n\
91 pub struct {name};\n\n"
92 ));
93
94 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 let ancestors = ancestor_chain(name, ctx);
110
111 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 let (mut prop_names, mut deduped_props) = properties::collect_deduped_properties(&class.props, Some(ctx));
129
130 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 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 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 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 for setter in &suppress_setters {
213 prop_names.remove(setter);
214 }
215
216 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 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 delegates::generate_delegate_structs(&mut out, &own_delegate_infos, name);
234
235 let mut all_delegate_infos = own_delegate_infos;
237 all_delegate_infos.extend(inherited_delegate_infos);
238
239 let trait_name = format!("{name}Ext");
242 out.push_str(&format!(
243 "pub trait {trait_name}: uika_runtime::ValidHandle {{\n"
244 ));
245
246 for prop in &deduped_props {
248 properties::generate_property(&mut out, prop, &pctx, ctx, &suppress_setters);
249 }
250
251 delegates::generate_delegate_impls(&mut out, &all_delegate_infos);
253
254 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 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
272fn is_container_param(param: &ParamInfo) -> bool {
277 matches!(
278 param.prop_type.as_str(),
279 "ArrayProperty" | "MapProperty" | "SetProperty"
280 )
281}
282
283fn 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
307fn 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
331fn 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
354fn 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
363fn 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 mapped.rust_type.clone()
378 }
379 _ => mapped.rust_type.clone(),
380 }
381}
382
383fn 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
390fn 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
400fn 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
414fn 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 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 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 ¶m.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 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 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 let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
493
494 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 let mut param_names = Vec::new();
508 let mut default_unwraps: Vec<(String, String)> = Vec::new(); for (param, dir, mapped) in &all_mapped {
510 if *dir == ParamDirection::Return {
511 continue;
512 }
513 let pname = escape_reserved(&to_snake_case(¶m.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 }
555 ParamDirection::Return => {}
556 }
557 param_names.push((pname, param, *dir, mapped));
558 }
559
560 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 for (pname, default_expr) in &default_unwraps {
576 out.push_str(&format!(" let {pname} = {pname}.unwrap_or({default_expr});\n"));
577 }
578
579 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 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, "); } 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 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 if !is_static {
647 out.push_str(" let h = self.handle();\n");
648 }
649
650 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(¶m.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 if *dir == ParamDirection::InOut && mapped.ffi_to_rust == ConversionKind::StringUtf8 {
699 let pname = escape_reserved(&to_snake_case(¶m.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 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(¶m.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 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 let out_len = out.len();
772 if out.ends_with(", ") {
773 out.truncate(out_len - 2);
774 }
775 out.push_str(") });\n");
776
777 {
779 let mut return_parts = Vec::new();
780
781 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 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(¶m.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
875struct ContainerParamMeta<'a> {
881 param: &'a ParamInfo,
882 dir: ParamDirection,
883 index: usize,
885}
886
887fn 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 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 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 ¶m.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 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 ¶m.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 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(¶m.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 for (pname, default_expr) in &default_unwraps {
1065 out.push_str(&format!(" let {pname} = {pname}.unwrap_or({default_expr});\n"));
1066 }
1067
1068 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 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, "); } 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 if !is_static {
1150 out.push_str(" let h = self.handle();\n");
1151 }
1152
1153 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 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 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 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(¶m.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 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(¶m.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 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(¶m.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 let out_len = out.len();
1307 if out.ends_with(", ") {
1308 out.truncate(out_len - 2);
1309 }
1310 out.push_str(") };\n");
1311
1312 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 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 out.push_str(" uika_runtime::ffi_infallible(__result);\n");
1333
1334 emit_container_return(out, return_param, ret_mapped, &container_params, &func.params, ctx);
1336
1337 out.push_str(" }\n\n");
1338}
1339
1340fn 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
1382fn 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
1433fn 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 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 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(¶m.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
1554fn map_param(param: &ParamInfo) -> MappedType {
1556 type_map::map_property_type(
1557 ¶m.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}