Skip to main content

uika_codegen/
filter.rs

1// Secondary filtering: K2_ dedup, FUNC_Native gate, type exportability, overloads.
2
3use std::collections::{HashMap, HashSet};
4
5use crate::config::Blocklist;
6use crate::context::CodegenContext;
7use crate::schema::*;
8use crate::type_map;
9
10/// Apply all filters to the context's module_classes in place.
11pub fn apply_filters(ctx: &mut CodegenContext, blocklist: &Blocklist) {
12    // Pre-collect the set of available types to avoid borrowing ctx inside the loop.
13    let available_types: HashSet<String> = ctx
14        .classes
15        .keys()
16        .chain(ctx.structs.keys())
17        .chain(ctx.enums.keys())
18        .cloned()
19        .collect();
20
21    // Build lookup sets from config blocklist
22    let blocked_classes: HashSet<&str> = blocklist.classes.iter().map(|s| s.as_str()).collect();
23    let blocked_structs: HashSet<&str> = blocklist.structs.iter().map(|s| s.as_str()).collect();
24    let blocked_functions: Vec<(String, String)> = blocklist.function_tuples();
25
26    // Remove blocked classes from both module_classes and ctx.classes
27    for cls in &blocklist.classes {
28        ctx.classes.remove(cls);
29    }
30
31    for classes in ctx.module_classes.values_mut() {
32        // Remove blocked classes entirely
33        classes.retain(|c| !blocked_classes.contains(c.name.as_str()));
34
35        for class in classes.iter_mut() {
36            // Filter properties
37            class
38                .props
39                .retain(|p| is_property_exportable(p, &available_types));
40
41            // Filter functions
42            filter_functions(&class.name, &mut class.funcs, &available_types, &blocked_structs, &blocked_functions);
43        }
44    }
45}
46
47/// Check if a property is exportable (supported type, not private/protected, single array dim).
48fn is_property_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
49    // Skip unsupported types
50    if !type_map::is_supported_type(&prop.prop_type) {
51        return false;
52    }
53
54    // Skip fixed arrays of string/name/text types (CopySingleValue not safe for FString)
55    if prop.array_dim > 1 {
56        match prop.prop_type.as_str() {
57            "StrProperty" | "NameProperty" | "TextProperty" => return false,
58            _ => {} // allow through
59        }
60    }
61
62    // Skip private/protected
63    if prop.prop_flags & CPF_NATIVE_ACCESS_PRIVATE != 0 {
64        return false;
65    }
66    if prop.prop_flags & CPF_NATIVE_ACCESS_PROTECTED != 0 {
67        return false;
68    }
69
70    // Delegate properties: validate all params in func_info are exportable
71    if is_delegate_type(&prop.prop_type) {
72        return is_delegate_exportable(prop, available);
73    }
74
75    // Check referenced types are available
76    if let Some(ref cls) = prop.class_name {
77        if !available.contains(cls) {
78            return false;
79        }
80    }
81    if let Some(ref sn) = prop.struct_name {
82        if !available.contains(sn) {
83            return false;
84        }
85    }
86    if let Some(ref en) = prop.enum_name {
87        if !available.contains(en) {
88            return false;
89        }
90    }
91    if let Some(ref iface) = prop.interface_name {
92        if !available.contains(iface) {
93            return false;
94        }
95    }
96    if prop.prop_type == "ClassProperty" {
97        if let Some(ref meta_cls) = prop.meta_class_name {
98            if !available.contains(meta_cls) {
99                return false;
100            }
101        }
102    }
103
104    true
105}
106
107fn is_delegate_type(prop_type: &str) -> bool {
108    matches!(
109        prop_type,
110        "DelegateProperty" | "MulticastInlineDelegateProperty" | "MulticastSparseDelegateProperty"
111    )
112}
113
114/// Check if a delegate property's func_info params are all exportable.
115fn is_delegate_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
116    let func_info = match &prop.func_info {
117        Some(fi) => fi,
118        None => return false, // No signature info — can't export
119    };
120
121    // Parse func_info params
122    let params = match func_info.get("params").and_then(|p| p.as_array()) {
123        Some(params) => params,
124        None => return true, // No params — zero-arg delegate, always exportable
125    };
126
127    for param_value in params {
128        let param_type = match param_value.get("type").and_then(|t| t.as_str()) {
129            Some(t) => t,
130            None => return false,
131        };
132
133        // Each delegate param type must be supported
134        if !type_map::is_supported_type(param_type) {
135            return false;
136        }
137
138        // Delegate params cannot themselves be delegates or containers
139        if is_delegate_type(param_type) {
140            return false;
141        }
142        if matches!(param_type, "ArrayProperty" | "MapProperty" | "SetProperty") {
143            return false;
144        }
145
146        // Check referenced types are available
147        if let Some(cls) = param_value.get("class_name").and_then(|v| v.as_str()) {
148            if !available.contains(cls) {
149                return false;
150            }
151        }
152        if let Some(sn) = param_value.get("struct_name").and_then(|v| v.as_str()) {
153            if !available.contains(sn) {
154                return false;
155            }
156        }
157        if let Some(en) = param_value.get("enum_name").and_then(|v| v.as_str()) {
158            if !available.contains(en) {
159                return false;
160            }
161        }
162    }
163
164    true
165}
166
167/// Check if a container parameter's inner types are exportable.
168fn is_container_param_exportable(param: &ParamInfo, available: &HashSet<String>) -> bool {
169    match param.prop_type.as_str() {
170        "ArrayProperty" => {
171            if let Some(ref inner) = param.inner_prop {
172                is_inner_type_exportable(inner, available)
173            } else {
174                false
175            }
176        }
177        "MapProperty" => {
178            let key_ok = param.key_prop.as_ref()
179                .map(|k| is_inner_type_exportable(k, available))
180                .unwrap_or(false);
181            let val_ok = param.value_prop.as_ref()
182                .map(|v| is_inner_type_exportable(v, available))
183                .unwrap_or(false);
184            key_ok && val_ok
185        }
186        "SetProperty" => {
187            if let Some(ref elem) = param.element_prop {
188                is_inner_type_exportable(elem, available)
189            } else {
190                false
191            }
192        }
193        _ => false,
194    }
195}
196
197/// Check if a container inner type is supported and its referenced types are available.
198fn is_inner_type_exportable(inner: &PropertyInfo, available: &HashSet<String>) -> bool {
199    if !type_map::is_supported_type(&inner.prop_type) {
200        return false;
201    }
202    // Nested containers not supported
203    if matches!(inner.prop_type.as_str(), "ArrayProperty" | "MapProperty" | "SetProperty") {
204        return false;
205    }
206    if let Some(ref cls) = inner.class_name {
207        if !available.contains(cls) {
208            return false;
209        }
210    }
211    if let Some(ref sn) = inner.struct_name {
212        if !available.contains(sn) {
213            return false;
214        }
215    }
216    if let Some(ref en) = inner.enum_name {
217        if !available.contains(en) {
218            return false;
219        }
220    }
221    if let Some(ref iface) = inner.interface_name {
222        if !available.contains(iface) {
223            return false;
224        }
225    }
226    true
227}
228
229/// Filter functions on a class: FUNC_Native gate, K2_ dedup, param type check, overload rename.
230fn filter_functions(
231    class_name: &str,
232    funcs: &mut Vec<FunctionInfo>,
233    available: &HashSet<String>,
234    blocked_structs: &HashSet<&str>,
235    blocked_functions: &[(String, String)],
236) {
237    // Step 1: Collect all function names for K2_ dedup
238    let all_names: HashSet<String> = funcs.iter().map(|f| f.name.clone()).collect();
239
240    // Step 2: Filter
241    funcs.retain(|f| {
242        // Function-level blocklist (unlinked symbols)
243        if blocked_functions.iter().any(|(c, func)| c == class_name && func == &f.name) {
244            return false;
245        }
246
247        // FUNC_Native gate
248        if f.func_flags & FUNC_NATIVE == 0 {
249            return false;
250        }
251
252        // K2_ dedup: if this is K2_Foo and Foo also exists, skip K2_Foo
253        if f.name.starts_with("K2_") {
254            let base_name = &f.name[3..];
255            if all_names.contains(base_name) {
256                return false;
257            }
258        }
259
260        // Check all param types are supported and referenced types are available
261        for param in &f.params {
262            if !type_map::is_supported_type(&param.prop_type) {
263                return false;
264            }
265            // Delegate-typed params are not valid in function signatures
266            if is_delegate_type(&param.prop_type) {
267                return false;
268            }
269            // Check container inner types are resolvable
270            if matches!(param.prop_type.as_str(), "ArrayProperty" | "MapProperty" | "SetProperty") {
271                if !is_container_param_exportable(param, available) {
272                    return false;
273                }
274            }
275            if let Some(ref cls) = param.class_name {
276                if !available.contains(cls) {
277                    return false;
278                }
279            }
280            if let Some(ref sn) = param.struct_name {
281                if !available.contains(sn) || blocked_structs.contains(sn.as_str()) {
282                    return false;
283                }
284            }
285            if let Some(ref en) = param.enum_name {
286                if !available.contains(en) {
287                    return false;
288                }
289            }
290            if let Some(ref iface) = param.interface_name {
291                if !available.contains(iface) {
292                    return false;
293                }
294            }
295            if param.prop_type == "ClassProperty" {
296                if let Some(ref meta_cls) = param.meta_class_name {
297                    if !available.contains(meta_cls) {
298                        return false;
299                    }
300                }
301            }
302        }
303
304        true
305    });
306
307    // Preserve original UE function names before overload renaming
308    for f in funcs.iter_mut() {
309        f.ue_name = f.name.clone();
310    }
311
312    // Step 3: Handle overloads — rename duplicates with _1, _2 suffix
313    let mut name_counts: HashMap<String, usize> = HashMap::new();
314    for f in funcs.iter() {
315        *name_counts.entry(f.name.clone()).or_default() += 1;
316    }
317
318    let mut name_indices: HashMap<String, usize> = HashMap::new();
319    for f in funcs.iter_mut() {
320        if let Some(&count) = name_counts.get(&f.name) {
321            if count > 1 {
322                let idx = name_indices.entry(f.name.clone()).or_insert(0);
323                *idx += 1;
324                f.name = format!("{}_{}", f.name, idx);
325            }
326        }
327    }
328}