Skip to main content

uika_codegen/
type_map.rs

1// UE property type → Rust type / C++ FFI type mapping.
2
3use crate::schema::ParamInfo;
4
5/// Classification of a function parameter.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ParamDirection {
8    /// Input parameter.
9    In,
10    /// Output parameter (non-const out).
11    Out,
12    /// Input + output (reference param).
13    InOut,
14    /// Return value.
15    Return,
16}
17
18/// Mapped type information for code generation.
19#[derive(Debug, Clone)]
20pub struct MappedType {
21    /// Rust type in function signatures (e.g., "bool", "i32", "UObjectRef<AActor>").
22    pub rust_type: String,
23    /// Rust type for the FFI boundary (e.g., "bool", "i32", "UObjectHandle").
24    pub rust_ffi_type: String,
25    /// C++ type for the wrapper function (e.g., "bool", "int32", "UObject*").
26    pub cpp_type: String,
27    /// PropertyApi method name for getters (e.g., "get_bool", "get_i32").
28    pub property_getter: String,
29    /// PropertyApi method name for setters.
30    pub property_setter: String,
31    /// How to convert from Rust safe type to FFI type in function call.
32    pub rust_to_ffi: ConversionKind,
33    /// How to convert from FFI type to Rust safe type in return.
34    pub ffi_to_rust: ConversionKind,
35    /// Whether this is a supported type for Phase 3.
36    pub supported: bool,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum ConversionKind {
41    /// No conversion needed (primitives).
42    Identity,
43    /// Integer type cast (e.g., u32 ↔ i32).
44    IntCast,
45    /// Wrap in UObjectRef::from_raw / extract with .raw().
46    ObjectRef,
47    /// String: UTF-8 ptr+len on FFI, String on Rust side.
48    StringUtf8,
49    /// Enum: i64 on FFI, enum type on Rust side.
50    EnumCast,
51    /// Struct: opaque pointer on FFI.
52    StructOpaque,
53    /// FName: FNameHandle on FFI.
54    FName,
55    /// TArray container property — returns UeArray<T> handle.
56    ContainerArray,
57    /// TMap container property — returns UeMap<K, V> handle.
58    ContainerMap,
59    /// TSet container property — returns UeSet<T> handle.
60    ContainerSet,
61    /// Unicast delegate property.
62    Delegate,
63    /// Multicast delegate property (inline or sparse).
64    MulticastDelegate,
65}
66
67/// Supported UE property types.
68const SUPPORTED_TYPES: &[&str] = &[
69    "BoolProperty",
70    "Int8Property",
71    "ByteProperty",
72    "Int16Property",
73    "UInt16Property",
74    "IntProperty",
75    "UInt32Property",
76    "Int64Property",
77    "UInt64Property",
78    "FloatProperty",
79    "DoubleProperty",
80    "StrProperty",
81    "NameProperty",
82    "TextProperty",
83    "EnumProperty",
84    "ObjectProperty",
85    "ClassProperty",
86    "StructProperty",
87    "ArrayProperty",
88    "MapProperty",
89    "SetProperty",
90    "SoftObjectProperty",
91    "WeakObjectProperty",
92    "InterfaceProperty",
93    "DelegateProperty",
94    "MulticastInlineDelegateProperty",
95    "MulticastSparseDelegateProperty",
96];
97
98/// Check if a property type is supported in Phase 3.
99pub fn is_supported_type(prop_type: &str) -> bool {
100    SUPPORTED_TYPES.contains(&prop_type)
101}
102
103/// Map a UE property type string to its Rust/C++ type information.
104pub fn map_property_type(
105    prop_type: &str,
106    class_name: Option<&str>,
107    struct_name: Option<&str>,
108    enum_name: Option<&str>,
109    enum_underlying_type: Option<&str>,
110    meta_class_name: Option<&str>,
111    interface_name: Option<&str>,
112) -> MappedType {
113    match prop_type {
114        "BoolProperty" => MappedType {
115            rust_type: "bool".into(),
116            rust_ffi_type: "bool".into(),
117            cpp_type: "bool".into(),
118            property_getter: "get_bool".into(),
119            property_setter: "set_bool".into(),
120            rust_to_ffi: ConversionKind::Identity,
121            ffi_to_rust: ConversionKind::Identity,
122            supported: true,
123        },
124        "Int8Property" => int_type("i8", "int8"),
125        "ByteProperty" => {
126            // ByteProperty can be a plain uint8 or an enum
127            if let Some(en) = enum_name {
128                enum_type(en, enum_underlying_type.unwrap_or("uint8"))
129            } else {
130                int_type("u8", "uint8")
131            }
132        }
133        "Int16Property" => int_type("i16", "int16"),
134        "UInt16Property" => int_type("u16", "uint16"),
135        "IntProperty" => int_type("i32", "int32"),
136        "UInt32Property" => int_type("u32", "uint32"),
137        "Int64Property" => int_type("i64", "int64"),
138        "UInt64Property" => int_type("u64", "uint64"),
139        "FloatProperty" => MappedType {
140            rust_type: "f32".into(),
141            rust_ffi_type: "f32".into(),
142            cpp_type: "float".into(),
143            property_getter: "get_f32".into(),
144            property_setter: "set_f32".into(),
145            rust_to_ffi: ConversionKind::Identity,
146            ffi_to_rust: ConversionKind::Identity,
147            supported: true,
148        },
149        "DoubleProperty" => MappedType {
150            rust_type: "f64".into(),
151            rust_ffi_type: "f64".into(),
152            cpp_type: "double".into(),
153            property_getter: "get_f64".into(),
154            property_setter: "set_f64".into(),
155            rust_to_ffi: ConversionKind::Identity,
156            ffi_to_rust: ConversionKind::Identity,
157            supported: true,
158        },
159        "StrProperty" => MappedType {
160            rust_type: "String".into(),
161            rust_ffi_type: "*const u8".into(),
162            cpp_type: "FString".into(),
163            property_getter: "get_string".into(),
164            property_setter: "set_string".into(),
165            rust_to_ffi: ConversionKind::StringUtf8,
166            ffi_to_rust: ConversionKind::StringUtf8,
167            supported: true,
168        },
169        "TextProperty" => MappedType {
170            rust_type: "String".into(),
171            rust_ffi_type: "*const u8".into(),
172            cpp_type: "FText".into(),
173            property_getter: "get_string".into(),
174            property_setter: "set_string".into(),
175            rust_to_ffi: ConversionKind::StringUtf8,
176            ffi_to_rust: ConversionKind::StringUtf8,
177            supported: true,
178        },
179        "NameProperty" => MappedType {
180            rust_type: "uika_runtime::FNameHandle".into(),
181            rust_ffi_type: "uika_runtime::FNameHandle".into(),
182            cpp_type: "FName".into(),
183            property_getter: "get_fname".into(),
184            property_setter: "set_fname".into(),
185            rust_to_ffi: ConversionKind::FName,
186            ffi_to_rust: ConversionKind::FName,
187            supported: true,
188        },
189        "EnumProperty" => {
190            if let Some(en) = enum_name {
191                enum_type(en, enum_underlying_type.unwrap_or("uint8"))
192            } else {
193                unsupported("EnumProperty without enum_name")
194            }
195        }
196        "ObjectProperty" => {
197            if let Some(cls) = class_name {
198                MappedType {
199                    rust_type: format!("uika_runtime::UObjectRef<{cls}>"),
200                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
201                    cpp_type: format!("{cls}*"),
202                    property_getter: "get_object".into(),
203                    property_setter: "set_object".into(),
204                    rust_to_ffi: ConversionKind::ObjectRef,
205                    ffi_to_rust: ConversionKind::ObjectRef,
206                    supported: true,
207                }
208            } else {
209                // Untyped object reference — use UObject
210                MappedType {
211                    rust_type: "uika_runtime::UObjectHandle".into(),
212                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
213                    cpp_type: "UObject*".into(),
214                    property_getter: "get_object".into(),
215                    property_setter: "set_object".into(),
216                    rust_to_ffi: ConversionKind::Identity,
217                    ffi_to_rust: ConversionKind::Identity,
218                    supported: true,
219                }
220            }
221        }
222        "SoftObjectProperty" | "WeakObjectProperty" => {
223            // TSoftObjectPtr<T> / TWeakObjectPtr<T> resolve to UObject* via
224            // FObjectPropertyBase — use the same ObjectRef mapping as ObjectProperty.
225            if let Some(cls) = class_name {
226                MappedType {
227                    rust_type: format!("uika_runtime::UObjectRef<{cls}>"),
228                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
229                    cpp_type: format!("{cls}*"),
230                    property_getter: "get_object".into(),
231                    property_setter: "set_object".into(),
232                    rust_to_ffi: ConversionKind::ObjectRef,
233                    ffi_to_rust: ConversionKind::ObjectRef,
234                    supported: true,
235                }
236            } else {
237                MappedType {
238                    rust_type: "uika_runtime::UObjectHandle".into(),
239                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
240                    cpp_type: "UObject*".into(),
241                    property_getter: "get_object".into(),
242                    property_setter: "set_object".into(),
243                    rust_to_ffi: ConversionKind::Identity,
244                    ffi_to_rust: ConversionKind::Identity,
245                    supported: true,
246                }
247            }
248        }
249        "ClassProperty" => {
250            let effective_class = meta_class_name.or(class_name);
251            if let Some(cls) = effective_class {
252                MappedType {
253                    rust_type: format!("uika_runtime::UObjectRef<{cls}>"),
254                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
255                    cpp_type: format!("{cls}*"),
256                    property_getter: "get_object".into(),
257                    property_setter: "set_object".into(),
258                    rust_to_ffi: ConversionKind::ObjectRef,
259                    ffi_to_rust: ConversionKind::ObjectRef,
260                    supported: true,
261                }
262            } else {
263                MappedType {
264                    rust_type: "uika_runtime::UObjectHandle".into(),
265                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
266                    cpp_type: "UObject*".into(),
267                    property_getter: "get_object".into(),
268                    property_setter: "set_object".into(),
269                    rust_to_ffi: ConversionKind::Identity,
270                    ffi_to_rust: ConversionKind::Identity,
271                    supported: true,
272                }
273            }
274        }
275        "InterfaceProperty" => {
276            if let Some(iface) = interface_name {
277                MappedType {
278                    rust_type: format!("uika_runtime::UObjectRef<{iface}>"),
279                    rust_ffi_type: "uika_runtime::UObjectHandle".into(),
280                    cpp_type: format!("{iface}*"),
281                    property_getter: "get_object".into(),
282                    property_setter: "set_object".into(),
283                    rust_to_ffi: ConversionKind::ObjectRef,
284                    ffi_to_rust: ConversionKind::ObjectRef,
285                    supported: true,
286                }
287            } else {
288                unsupported("InterfaceProperty without interface_name")
289            }
290        }
291        "StructProperty" => {
292            if let Some(sn) = struct_name {
293                MappedType {
294                    rust_type: format!("*const u8 /* {sn} */"),
295                    rust_ffi_type: "*const u8".into(),
296                    cpp_type: format!("F{sn}"),
297                    property_getter: "get_struct".into(),
298                    property_setter: "set_struct".into(),
299                    rust_to_ffi: ConversionKind::StructOpaque,
300                    ffi_to_rust: ConversionKind::StructOpaque,
301                    supported: true,
302                }
303            } else {
304                unsupported("StructProperty without struct_name")
305            }
306        }
307        "ArrayProperty" => MappedType {
308            rust_type: "uika_runtime::UeArray<_>".into(),
309            rust_ffi_type: String::new(),
310            cpp_type: String::new(),
311            property_getter: String::new(),
312            property_setter: String::new(),
313            rust_to_ffi: ConversionKind::ContainerArray,
314            ffi_to_rust: ConversionKind::ContainerArray,
315            supported: true,
316        },
317        "MapProperty" => MappedType {
318            rust_type: "uika_runtime::UeMap<_, _>".into(),
319            rust_ffi_type: String::new(),
320            cpp_type: String::new(),
321            property_getter: String::new(),
322            property_setter: String::new(),
323            rust_to_ffi: ConversionKind::ContainerMap,
324            ffi_to_rust: ConversionKind::ContainerMap,
325            supported: true,
326        },
327        "SetProperty" => MappedType {
328            rust_type: "uika_runtime::UeSet<_>".into(),
329            rust_ffi_type: String::new(),
330            cpp_type: String::new(),
331            property_getter: String::new(),
332            property_setter: String::new(),
333            rust_to_ffi: ConversionKind::ContainerSet,
334            ffi_to_rust: ConversionKind::ContainerSet,
335            supported: true,
336        },
337        "DelegateProperty" => MappedType {
338            rust_type: "/* delegate */".into(),
339            rust_ffi_type: String::new(),
340            cpp_type: String::new(),
341            property_getter: String::new(),
342            property_setter: String::new(),
343            rust_to_ffi: ConversionKind::Delegate,
344            ffi_to_rust: ConversionKind::Delegate,
345            supported: true,
346        },
347        "MulticastInlineDelegateProperty" | "MulticastSparseDelegateProperty" => MappedType {
348            rust_type: "/* multicast delegate */".into(),
349            rust_ffi_type: String::new(),
350            cpp_type: String::new(),
351            property_getter: String::new(),
352            property_setter: String::new(),
353            rust_to_ffi: ConversionKind::MulticastDelegate,
354            ffi_to_rust: ConversionKind::MulticastDelegate,
355            supported: true,
356        },
357        _ => unsupported(prop_type),
358    }
359}
360
361/// Map a param to its direction based on prop_flags.
362pub fn param_direction(param: &ParamInfo) -> ParamDirection {
363    use crate::schema::*;
364
365    if param.prop_flags & CPF_RETURN_PARM != 0 {
366        return ParamDirection::Return;
367    }
368    let is_out = param.prop_flags & CPF_OUT_PARM != 0;
369    let is_const = param.prop_flags & CPF_CONST_PARM != 0;
370    let is_ref = param.prop_flags & CPF_REFERENCE_PARM != 0;
371
372    if is_out && is_const {
373        // const& pseudo-output → actually input
374        ParamDirection::In
375    } else if is_out && is_ref {
376        ParamDirection::InOut
377    } else if is_out {
378        ParamDirection::Out
379    } else {
380        ParamDirection::In
381    }
382}
383
384fn int_type(rust: &str, _cpp: &str) -> MappedType {
385    // Map to the FFI type that matches the available PropertyApi methods.
386    // Available: get_u8/set_u8, get_i32/set_i32, get_i64/set_i64
387    let (getter, setter, ffi_type) = match rust {
388        "i8" => ("get_u8", "set_u8", "u8"),    // cast u8 ↔ i8
389        "u8" => ("get_u8", "set_u8", "u8"),
390        "i16" => ("get_i32", "set_i32", "i32"), // cast i32 ↔ i16
391        "u16" => ("get_i32", "set_i32", "i32"), // cast i32 ↔ u16
392        "i32" => ("get_i32", "set_i32", "i32"),
393        "u32" => ("get_i32", "set_i32", "i32"), // cast i32 ↔ u32
394        "i64" => ("get_i64", "set_i64", "i64"),
395        "u64" => ("get_i64", "set_i64", "i64"), // cast i64 ↔ u64
396        _ => ("get_i32", "set_i32", "i32"),
397    };
398    let cpp = match rust {
399        "i8" => "int8",
400        "u8" => "uint8",
401        "i16" => "int16",
402        "u16" => "uint16",
403        "i32" => "int32",
404        "u32" => "uint32",
405        "i64" => "int64",
406        "u64" => "uint64",
407        _ => "int32",
408    };
409    let needs_cast = rust != ffi_type;
410    MappedType {
411        rust_type: rust.into(),
412        rust_ffi_type: ffi_type.into(),
413        cpp_type: cpp.into(),
414        property_getter: getter.into(),
415        property_setter: setter.into(),
416        rust_to_ffi: if needs_cast { ConversionKind::IntCast } else { ConversionKind::Identity },
417        ffi_to_rust: if needs_cast { ConversionKind::IntCast } else { ConversionKind::Identity },
418        supported: true,
419    }
420}
421
422fn enum_type(enum_name: &str, underlying: &str) -> MappedType {
423    let repr = match underlying {
424        "uint8" => "u8",
425        "int8" => "i8",
426        "uint16" => "u16",
427        "int16" => "i16",
428        "uint32" => "u32",
429        "int32" => "i32",
430        "uint64" => "u64",
431        "int64" => "i64",
432        _ => "u8",
433    };
434    MappedType {
435        rust_type: enum_name.to_string(),
436        rust_ffi_type: repr.into(),
437        cpp_type: enum_name.to_string(),
438        property_getter: "get_enum".into(),
439        property_setter: "set_enum".into(),
440        rust_to_ffi: ConversionKind::EnumCast,
441        ffi_to_rust: ConversionKind::EnumCast,
442        supported: true,
443    }
444}
445
446fn unsupported(reason: &str) -> MappedType {
447    MappedType {
448        rust_type: format!("/* unsupported: {reason} */"),
449        rust_ffi_type: String::new(),
450        cpp_type: String::new(),
451        property_getter: String::new(),
452        property_setter: String::new(),
453        rust_to_ffi: ConversionKind::Identity,
454        ffi_to_rust: ConversionKind::Identity,
455        supported: false,
456    }
457}
458
459// ---------------------------------------------------------------------------
460// Container inner-type resolution
461// ---------------------------------------------------------------------------
462
463use crate::context::CodegenContext;
464use crate::schema::PropertyInfo;
465
466/// Map an inner property (inside a container) to its Rust `ContainerElement` type.
467/// When `ctx` is provided, validates that referenced types are in enabled modules
468/// and returns the actual typed names (e.g., `UObjectRef<Actor>` instead of `UObjectHandle`).
469/// Returns `None` if the inner type is unsupported for container elements.
470pub fn container_element_rust_type(
471    inner: &PropertyInfo,
472    ctx: Option<&CodegenContext>,
473) -> Option<String> {
474    match inner.prop_type.as_str() {
475        "BoolProperty" => Some("bool".into()),
476        "Int8Property" => Some("i8".into()),
477        "ByteProperty" => {
478            if let Some(en) = &inner.enum_name {
479                if let Some(ctx) = ctx {
480                    if !ctx.enums.contains_key(en.as_str()) {
481                        return None;
482                    }
483                }
484                Some(en.clone())
485            } else {
486                Some("u8".into())
487            }
488        }
489        "Int16Property" => Some("i16".into()),
490        "UInt16Property" => Some("u16".into()),
491        "IntProperty" => Some("i32".into()),
492        "UInt32Property" => Some("u32".into()),
493        "Int64Property" => Some("i64".into()),
494        "UInt64Property" => Some("u64".into()),
495        "FloatProperty" => Some("f32".into()),
496        "DoubleProperty" => Some("f64".into()),
497        "StrProperty" | "TextProperty" => Some("String".into()),
498        "NameProperty" => Some("uika_runtime::FNameHandle".into()),
499        "ObjectProperty" | "SoftObjectProperty" | "WeakObjectProperty" => {
500            if let Some(cls) = &inner.class_name {
501                if let Some(ctx) = ctx {
502                    if !ctx.classes.contains_key(cls.as_str()) {
503                        return None;
504                    }
505                }
506                Some(format!("uika_runtime::UObjectRef<{cls}>"))
507            } else {
508                Some("uika_runtime::UObjectHandle".into())
509            }
510        }
511        "ClassProperty" => {
512            let effective_class = inner.meta_class_name.as_deref().or(inner.class_name.as_deref());
513            if let Some(cls) = effective_class {
514                if let Some(ctx) = ctx {
515                    if !ctx.classes.contains_key(cls) {
516                        return None;
517                    }
518                }
519                Some(format!("uika_runtime::UObjectRef<{cls}>"))
520            } else {
521                Some("uika_runtime::UObjectHandle".into())
522            }
523        }
524        "InterfaceProperty" => {
525            if let Some(ref iface) = inner.interface_name {
526                if let Some(ctx) = ctx {
527                    if !ctx.classes.contains_key(iface.as_str()) {
528                        return None;
529                    }
530                }
531                Some(format!("uika_runtime::UObjectRef<{iface}>"))
532            } else {
533                None
534            }
535        }
536        "EnumProperty" => {
537            if let Some(en) = &inner.enum_name {
538                if let Some(ctx) = ctx {
539                    if !ctx.enums.contains_key(en.as_str()) {
540                        return None;
541                    }
542                }
543                Some(en.clone())
544            } else {
545                None
546            }
547        }
548        "StructProperty" => {
549            if let Some(sn) = &inner.struct_name {
550                if let Some(ctx) = ctx {
551                    if let Some(si) = ctx.structs.get(sn.as_str()) {
552                        if si.has_static_struct {
553                            Some(format!("uika_runtime::OwnedStruct<{}>", si.cpp_name))
554                        } else {
555                            None // No static_struct → no UeStruct impl
556                        }
557                    } else {
558                        None // Not in enabled modules
559                    }
560                } else {
561                    Some(format!("uika_runtime::OwnedStruct<F{sn}>"))
562                }
563            } else {
564                None
565            }
566        }
567        _ => None, // Nested containers etc. — unsupported
568    }
569}
570
571/// Resolve the full Rust type for a container property.
572/// Returns `None` if any inner type is unsupported.
573pub fn resolve_container_rust_type(
574    prop: &PropertyInfo,
575    ctx: Option<&CodegenContext>,
576) -> Option<String> {
577    match prop.prop_type.as_str() {
578        "ArrayProperty" => {
579            let inner = prop.inner_prop.as_ref()?;
580            let elem_type = container_element_rust_type(inner, ctx)?;
581            Some(format!("uika_runtime::UeArray<{elem_type}>"))
582        }
583        "MapProperty" => {
584            let key = prop.key_prop.as_ref()?;
585            let val = prop.value_prop.as_ref()?;
586            let key_type = container_element_rust_type(key, ctx)?;
587            let val_type = container_element_rust_type(val, ctx)?;
588            Some(format!("uika_runtime::UeMap<{key_type}, {val_type}>"))
589        }
590        "SetProperty" => {
591            let elem = prop.element_prop.as_ref()?;
592            let elem_type = container_element_rust_type(elem, ctx)?;
593            Some(format!("uika_runtime::UeSet<{elem_type}>"))
594        }
595        _ => None,
596    }
597}