Skip to main content

rusteal_codegen/
context.rs

1// Build context: type lookups, module mapping, FuncId assignment.
2
3use std::collections::{BTreeMap, HashMap, HashSet};
4
5use crate::config::CodegenConfig;
6use crate::schema::{ClassInfo, EnumInfo, FunctionInfo, PropertyInfo, StructInfo};
7
8/// Central build context for the codegen pipeline.
9pub struct CodegenContext {
10    /// All classes by name.
11    pub classes: HashMap<String, ClassInfo>,
12    /// All structs by name.
13    pub structs: HashMap<String, StructInfo>,
14    /// All enums by name.
15    pub enums: HashMap<String, EnumInfo>,
16
17    /// Package name → Rust module name.
18    pub package_to_module: HashMap<String, String>,
19    /// Rust module name → Cargo feature name.
20    pub module_to_feature: HashMap<String, String>,
21    /// Set of enabled Rust module names (derived from features).
22    pub enabled_modules: HashSet<String>,
23
24    /// Classes grouped by module name. Sorted for determinism.
25    pub module_classes: BTreeMap<String, Vec<ClassInfo>>,
26    /// Structs grouped by module name.
27    pub module_structs: BTreeMap<String, Vec<StructInfo>>,
28    /// Enums grouped by module name.
29    pub module_enums: BTreeMap<String, Vec<EnumInfo>>,
30
31    /// All exportable functions sorted by (module, class, func) for FuncId assignment.
32    pub func_table: Vec<FuncEntry>,
33
34    /// Module name → set of other modules whose types it references.
35    /// Drives feature dependency emission in the generated `rusteal-bindings/Cargo.toml`.
36    pub module_deps: BTreeMap<String, std::collections::BTreeSet<String>>,
37}
38
39/// An entry in the global function table.
40#[derive(Clone)]
41pub struct FuncEntry {
42    pub func_id: u32,
43    pub module_name: String,
44    pub class_name: String,
45    pub func_name: String,
46    /// The actual resolved Rust function name (may include _1, _2 suffix for overloads).
47    pub rust_func_name: String,
48    pub func: FunctionInfo,
49    /// The C++ class name (with prefix, e.g. "AActor").
50    pub cpp_class_name: String,
51    /// The header file to include.
52    pub header: String,
53}
54
55impl CodegenContext {
56    pub fn new(
57        classes: Vec<ClassInfo>,
58        structs: Vec<StructInfo>,
59        enums: Vec<EnumInfo>,
60        config: &CodegenConfig,
61    ) -> Self {
62        // Build package→module and module→feature maps from config
63        let mut package_to_module = HashMap::new();
64        let mut module_to_feature_map = HashMap::new();
65
66        for (pkg, mapping) in &config.modules {
67            package_to_module.insert(pkg.clone(), mapping.module.clone());
68            module_to_feature_map.insert(mapping.module.clone(), mapping.feature.clone());
69        }
70
71        // Build enabled modules from features
72        let enabled_features: HashSet<&str> = config.features.iter().map(|s| s.as_str()).collect();
73        let mut enabled_modules = HashSet::new();
74        for (pkg, mapping) in &config.modules {
75            if enabled_features.contains(mapping.feature.as_str()) {
76                enabled_modules.insert(mapping.module.clone());
77                // Also ensure the package mapping exists for auto-derived packages
78                package_to_module.entry(pkg.clone()).or_insert_with(|| mapping.module.clone());
79            }
80        }
81
82        // Auto-derive module names for packages not in config
83        let mut all_packages = HashSet::new();
84        for c in &classes {
85            all_packages.insert(c.package.clone());
86        }
87        for s in &structs {
88            all_packages.insert(s.package.clone());
89        }
90        for e in &enums {
91            all_packages.insert(e.package.clone());
92        }
93
94        for pkg in &all_packages {
95            if !package_to_module.contains_key(pkg) {
96                let module = crate::naming::to_snake_case(pkg);
97                package_to_module.insert(pkg.clone(), module);
98            }
99        }
100
101        // Group types by module
102        let mut module_classes: BTreeMap<String, Vec<ClassInfo>> = BTreeMap::new();
103        let mut module_structs: BTreeMap<String, Vec<StructInfo>> = BTreeMap::new();
104        let mut module_enums: BTreeMap<String, Vec<EnumInfo>> = BTreeMap::new();
105
106        let mut classes_map = HashMap::new();
107        for c in classes {
108            if let Some(module) = package_to_module.get(&c.package)
109                && enabled_modules.contains(module)
110            {
111                classes_map.insert(c.name.clone(), c.clone());
112                module_classes.entry(module.clone()).or_default().push(c);
113            }
114        }
115
116        let mut structs_map = HashMap::new();
117        for s in structs {
118            if let Some(module) = package_to_module.get(&s.package)
119                && enabled_modules.contains(module)
120            {
121                structs_map.insert(s.name.clone(), s.clone());
122                module_structs.entry(module.clone()).or_default().push(s);
123            }
124        }
125
126        let mut enums_map = HashMap::new();
127        for e in enums {
128            if let Some(module) = package_to_module.get(&e.package)
129                && enabled_modules.contains(module)
130            {
131                enums_map.insert(e.name.clone(), e.clone());
132                module_enums.entry(module.clone()).or_default().push(e);
133            }
134        }
135
136        // Sort within each module for determinism
137        for classes in module_classes.values_mut() {
138            classes.sort_by(|a, b| a.name.cmp(&b.name));
139        }
140        for structs in module_structs.values_mut() {
141            structs.sort_by(|a, b| a.name.cmp(&b.name));
142        }
143        for enums in module_enums.values_mut() {
144            enums.sort_by(|a, b| a.name.cmp(&b.name));
145        }
146
147        let mut ctx = CodegenContext {
148            classes: classes_map,
149            structs: structs_map,
150            enums: enums_map,
151            package_to_module,
152            module_to_feature: module_to_feature_map,
153            enabled_modules,
154            module_classes,
155            module_structs,
156            module_enums,
157            func_table: Vec::new(),
158            module_deps: BTreeMap::new(),
159        };
160        ctx.module_deps = ctx.compute_module_deps();
161        ctx
162    }
163
164    /// Walk all enabled types and record cross-module type references.
165    ///
166    /// For each (module M, type T in M), if T references a type owned by
167    /// another module M', record `M depends on M'`. The resulting graph
168    /// drives feature dependency emission in `rusteal-bindings/Cargo.toml`,
169    /// ensuring `cargo check` succeeds at any enabled feature combination.
170    fn compute_module_deps(&self) -> BTreeMap<String, std::collections::BTreeSet<String>> {
171        let mut deps: BTreeMap<String, std::collections::BTreeSet<String>> = BTreeMap::new();
172        for m in &self.enabled_modules {
173            deps.entry(m.clone()).or_default();
174        }
175
176        for (module, classes) in &self.module_classes {
177            for class in classes {
178                if let Some(parent) = &class.super_class
179                    && let Some(pc) = self.classes.get(parent.as_str())
180                {
181                    self.record_dep(module, &pc.package, &mut deps);
182                }
183                for iface in &class.interfaces {
184                    if let Some(ic) = self.classes.get(iface.as_str()) {
185                        self.record_dep(module, &ic.package, &mut deps);
186                    }
187                }
188                for prop in &class.props {
189                    self.walk_prop_deps(prop, module, &mut deps);
190                }
191                for func in &class.funcs {
192                    for p in &func.params {
193                        self.walk_param_deps(p, module, &mut deps);
194                    }
195                }
196            }
197        }
198
199        for (module, structs) in &self.module_structs {
200            for s in structs {
201                if let Some(parent) = &s.super_struct
202                    && let Some(ps) = self.structs.get(parent.as_str())
203                {
204                    self.record_dep(module, &ps.package, &mut deps);
205                }
206                for prop in &s.props {
207                    self.walk_prop_deps(prop, module, &mut deps);
208                }
209            }
210        }
211
212        deps
213    }
214
215    fn record_dep(
216        &self,
217        current_module: &str,
218        target_package: &str,
219        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
220    ) {
221        if let Some(target_module) = self.package_to_module.get(target_package)
222            && target_module != current_module && self.enabled_modules.contains(target_module)
223        {
224            deps.entry(current_module.to_string())
225                .or_default()
226                .insert(target_module.clone());
227        }
228    }
229
230    fn walk_prop_deps(
231        &self,
232        prop: &PropertyInfo,
233        current_module: &str,
234        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
235    ) {
236        if let Some(en) = &prop.enum_name
237            && let Some(e) = self.enums.get(en.as_str())
238        {
239            self.record_dep(current_module, &e.package, deps);
240        }
241        if let Some(cn) = &prop.class_name
242            && let Some(c) = self.classes.get(cn.as_str())
243        {
244            self.record_dep(current_module, &c.package, deps);
245        }
246        if let Some(mc) = &prop.meta_class_name
247            && let Some(c) = self.classes.get(mc.as_str())
248        {
249            self.record_dep(current_module, &c.package, deps);
250        }
251        if let Some(sn) = &prop.struct_name
252            && let Some(s) = self.structs.get(sn.as_str())
253        {
254            self.record_dep(current_module, &s.package, deps);
255        }
256        if let Some(in_) = &prop.interface_name
257            && let Some(c) = self.classes.get(in_.as_str())
258        {
259            self.record_dep(current_module, &c.package, deps);
260        }
261        if let Some(fi) = &prop.func_info {
262            self.walk_delegate_func_info_deps(fi, current_module, deps);
263        }
264        if let Some(inner) = &prop.inner_prop {
265            self.walk_prop_deps(inner, current_module, deps);
266        }
267        if let Some(key) = &prop.key_prop {
268            self.walk_prop_deps(key, current_module, deps);
269        }
270        if let Some(value) = &prop.value_prop {
271            self.walk_prop_deps(value, current_module, deps);
272        }
273        if let Some(element) = &prop.element_prop {
274            self.walk_prop_deps(element, current_module, deps);
275        }
276    }
277
278    fn walk_param_deps(
279        &self,
280        param: &crate::schema::ParamInfo,
281        current_module: &str,
282        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
283    ) {
284        if let Some(en) = &param.enum_name
285            && let Some(e) = self.enums.get(en.as_str())
286        {
287            self.record_dep(current_module, &e.package, deps);
288        }
289        if let Some(cn) = &param.class_name
290            && let Some(c) = self.classes.get(cn.as_str())
291        {
292            self.record_dep(current_module, &c.package, deps);
293        }
294        if let Some(mc) = &param.meta_class_name
295            && let Some(c) = self.classes.get(mc.as_str())
296        {
297            self.record_dep(current_module, &c.package, deps);
298        }
299        if let Some(sn) = &param.struct_name
300            && let Some(s) = self.structs.get(sn.as_str())
301        {
302            self.record_dep(current_module, &s.package, deps);
303        }
304        if let Some(in_) = &param.interface_name
305            && let Some(c) = self.classes.get(in_.as_str())
306        {
307            self.record_dep(current_module, &c.package, deps);
308        }
309        if let Some(inner) = &param.inner_prop {
310            self.walk_prop_deps(inner, current_module, deps);
311        }
312        if let Some(key) = &param.key_prop {
313            self.walk_prop_deps(key, current_module, deps);
314        }
315        if let Some(value) = &param.value_prop {
316            self.walk_prop_deps(value, current_module, deps);
317        }
318        if let Some(element) = &param.element_prop {
319            self.walk_prop_deps(element, current_module, deps);
320        }
321    }
322
323    fn walk_delegate_func_info_deps(
324        &self,
325        fi: &serde_json::Value,
326        current_module: &str,
327        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
328    ) {
329        let Some(params) = fi.get("params").and_then(|p| p.as_array()) else { return };
330        for p in params {
331            if let Some(en) = p.get("enum_name").and_then(|v| v.as_str())
332                && let Some(e) = self.enums.get(en)
333            {
334                self.record_dep(current_module, &e.package, deps);
335            }
336            if let Some(cn) = p.get("class_name").and_then(|v| v.as_str())
337                && let Some(c) = self.classes.get(cn)
338            {
339                self.record_dep(current_module, &c.package, deps);
340            }
341            if let Some(mc) = p.get("meta_class_name").and_then(|v| v.as_str())
342                && let Some(c) = self.classes.get(mc)
343            {
344                self.record_dep(current_module, &c.package, deps);
345            }
346            if let Some(sn) = p.get("struct_name").and_then(|v| v.as_str())
347                && let Some(s) = self.structs.get(sn)
348            {
349                self.record_dep(current_module, &s.package, deps);
350            }
351            if let Some(in_) = p.get("interface_name").and_then(|v| v.as_str())
352                && let Some(c) = self.classes.get(in_)
353            {
354                self.record_dep(current_module, &c.package, deps);
355            }
356        }
357    }
358
359    /// Check if a type (class, struct, or enum) exists in an enabled module.
360    #[allow(dead_code)]
361    pub fn is_type_available(&self, type_name: &str) -> bool {
362        self.classes.contains_key(type_name)
363            || self.structs.contains_key(type_name)
364            || self.enums.contains_key(type_name)
365    }
366
367    /// Get the module name for a package.
368    #[allow(dead_code)]
369    pub fn module_for_package(&self, package: &str) -> Option<&str> {
370        self.package_to_module.get(package).map(|s| s.as_str())
371    }
372
373    /// Get the Cargo feature name for a module.
374    pub fn feature_for_module(&self, module: &str) -> Option<&str> {
375        self.module_to_feature.get(module).map(|s| s.as_str())
376    }
377
378    /// Get the actual Rust repr type for an enum, accounting for signed promotion.
379    /// This must match the logic in `rust_gen/enums.rs`.
380    pub fn enum_actual_repr(&self, enum_name: &str) -> Option<&'static str> {
381        let e = self.enums.get(enum_name)?;
382        let has_negative = e.pairs.iter().any(|(_, v)| *v < 0);
383        Some(if has_negative {
384            match e.underlying_type.as_str() {
385                "uint8" => "i8",
386                "int8" => "i8",
387                "uint16" => "i16",
388                "int16" => "i16",
389                "uint32" => "i32",
390                "int32" => "i32",
391                "uint64" => "i64",
392                "int64" => "i64",
393                _ => "i8",
394            }
395        } else {
396            match e.underlying_type.as_str() {
397                "uint8" => "u8",
398                "int8" => "i8",
399                "uint16" => "u16",
400                "int16" => "i16",
401                "uint32" => "u32",
402                "int32" => "i32",
403                "uint64" => "u64",
404                "int64" => "i64",
405                _ => "u8",
406            }
407        })
408    }
409}