Skip to main content

factorio_ir/prune/
struct_utils.rs

1use std::collections::HashSet;
2
3use crate::{
4    enumeration::Enum, expression::Expression, function::Function, module::Module,
5    prune::module_graph::ModuleGraph, statement::Statement, structure::Struct,
6};
7
8/// Find a struct declaration in a module's body or exported symbols.
9pub fn find_struct<'a>(module: &'a Module, name: &str) -> Option<&'a Struct> {
10    module
11        .body
12        .statements
13        .iter()
14        .chain(module.symbols.iter().map(|symbol| &symbol.statement))
15        .find_map(|statement| match statement {
16            Statement::StructDecl(struct_decl) if struct_decl.name == name => Some(struct_decl),
17            _ => None,
18        })
19}
20
21/// Find an enum declaration in a module's body or exported symbols.
22pub fn find_enum<'a>(module: &'a Module, name: &str) -> Option<&'a Enum> {
23    module
24        .body
25        .statements
26        .iter()
27        .chain(module.symbols.iter().map(|symbol| &symbol.statement))
28        .find_map(|statement| match statement {
29            Statement::EnumDecl(enum_decl) if enum_decl.name == name => Some(enum_decl),
30            _ => None,
31        })
32}
33
34/// Returns whether `module` declares a struct named `name`.
35pub fn struct_exists(module: &Module, name: &str) -> bool {
36    find_struct(module, name).is_some() || find_enum(module, name).is_some()
37}
38
39/// Find an instance method on a struct declared in `module`.
40pub fn find_struct_method<'a>(
41    module: &'a Module,
42    struct_name: &str,
43    method_name: &str,
44) -> Option<&'a Function> {
45    find_struct(module, struct_name)
46        .and_then(|struct_decl| {
47            struct_decl
48                .methods
49                .iter()
50                .find(|method| method.name == method_name)
51        })
52        .or_else(|| {
53            find_enum(module, struct_name).and_then(|enum_decl| {
54                enum_decl
55                    .methods
56                    .iter()
57                    .find(|method| method.name == method_name)
58            })
59        })
60}
61
62/// Find an associated constant initializer on a struct declared in `module`.
63#[must_use]
64pub fn find_struct_constant<'a>(
65    module: &'a Module,
66    struct_name: &str,
67    constant_name: &str,
68) -> Option<&'a Expression> {
69    find_struct(module, struct_name)
70        .and_then(|struct_decl| {
71            struct_decl
72                .constants
73                .iter()
74                .find_map(|(name, value)| (name == constant_name).then_some(value))
75        })
76        .or_else(|| {
77            find_enum(module, struct_name).and_then(|enum_decl| {
78                enum_decl
79                    .constants
80                    .iter()
81                    .find_map(|(name, value)| (name == constant_name).then_some(value))
82            })
83        })
84}
85
86/// Returns whether `struct_name` defines an associated constant named `constant_name`.
87pub fn struct_has_constant(module: &Module, struct_name: &str, constant_name: &str) -> bool {
88    find_struct_constant(module, struct_name, constant_name).is_some()
89}
90
91/// Returns whether `struct_name` defines a method named `method_name`.
92pub fn struct_has_method(module: &Module, struct_name: &str, method_name: &str) -> bool {
93    find_struct_method(module, struct_name, method_name).is_some()
94}
95
96/// Return the module that owns a struct type referenced from `module`.
97///
98/// Checks local declarations first, then walks `use` imports and their submodules.
99#[must_use]
100pub fn struct_owner_module(graph: &ModuleGraph<'_>, module: &Module, struct_name: &str) -> String {
101    if struct_exists(module, struct_name) {
102        return module.name.clone();
103    }
104
105    for import in &module.imports {
106        for item in &import.items {
107            if item.name == struct_name || item.local == struct_name {
108                return import.module.clone();
109            }
110        }
111
112        if module_defines_struct(graph, &import.module, struct_name) {
113            return import.module.clone();
114        }
115    }
116
117    module.name.clone()
118}
119
120/// Returns whether `struct_name` is declared in `module_name` or any of its submodules.
121pub fn module_defines_struct(
122    graph: &ModuleGraph<'_>,
123    module_name: &str,
124    struct_name: &str,
125) -> bool {
126    let mut stack = vec![module_name.to_string()];
127    let mut seen = HashSet::new();
128
129    while let Some(current) = stack.pop() {
130        if !seen.insert(current.clone()) {
131            continue;
132        }
133
134        if let Some(module) = graph.get(&current)
135            && struct_exists(module, struct_name)
136        {
137            return true;
138        }
139
140        if let Some(module) = graph.get(&current) {
141            for child in graph.child_modules(&module.name) {
142                stack.push((*child).to_string());
143            }
144        }
145    }
146
147    false
148}
149
150/// Search `module_name` and its submodules for a struct method definition.
151pub fn find_struct_method_in_module_tree<'a>(
152    graph: &ModuleGraph<'a>,
153    module_name: &str,
154    struct_name: &str,
155    method_name: &str,
156) -> Option<(String, &'a Function)> {
157    let mut stack = vec![module_name.to_string()];
158    let mut seen = HashSet::new();
159
160    while let Some(current) = stack.pop() {
161        if !seen.insert(current.clone()) {
162            continue;
163        }
164
165        let Some(module) = graph.get(&current) else {
166            continue;
167        };
168
169        if let Some(method) = find_struct_method(module, struct_name, method_name) {
170            return Some((current, method));
171        }
172
173        for child in graph.child_modules(&current) {
174            stack.push((*child).to_string());
175        }
176    }
177
178    None
179}
180
181/// Search `module_name` and its submodules for a struct associated constant.
182#[must_use]
183pub fn find_struct_constant_in_module_tree<'a>(
184    graph: &ModuleGraph<'a>,
185    module_name: &str,
186    struct_name: &str,
187    constant_name: &str,
188) -> Option<(String, &'a Expression)> {
189    let mut stack = vec![module_name.to_string()];
190    let mut seen = HashSet::new();
191
192    while let Some(current) = stack.pop() {
193        if !seen.insert(current.clone()) {
194            continue;
195        }
196
197        let Some(module) = graph.get(&current) else {
198            continue;
199        };
200
201        if let Some(value) = find_struct_constant(module, struct_name, constant_name) {
202            return Some((current, value));
203        }
204
205        for child in graph.child_modules(&current) {
206            stack.push((*child).to_string());
207        }
208    }
209
210    None
211}