Skip to main content

mago_codex/populator/
mod.rs

1use foldhash::HashSet;
2
3use mago_atom::Atom;
4use mago_atom::AtomMap;
5use mago_atom::AtomSet;
6
7use crate::metadata::CodebaseMetadata;
8use crate::metadata::constant::ConstantMetadata;
9use crate::metadata::flags::MetadataFlags;
10use crate::reference::ReferenceSource;
11use crate::reference::SymbolReferences;
12use crate::symbol::SymbolIdentifier;
13use crate::symbol::Symbols;
14use crate::ttype::union::populate_union_type;
15
16mod docblock;
17mod hierarchy;
18mod merge;
19mod methods;
20mod properties;
21mod signatures;
22mod sorter;
23mod templates;
24
25/// Populates the codebase metadata, resolving types and inheritance.
26///
27/// This function processes class-likes, function-likes, and constants to:
28///
29/// - Resolve type signatures (populating `TUnion` and `TAtomic` types).
30/// - Calculate inheritance hierarchies (parent classes, interfaces, traits).
31/// - Determine method and property origins (declaring vs. appearing).
32/// - Build descendant maps for efficient lookup.
33#[allow(clippy::implicit_hasher)]
34pub fn populate_codebase(
35    codebase: &mut CodebaseMetadata,
36    symbol_references: &mut SymbolReferences,
37    safe_symbols: AtomSet,
38    safe_symbol_members: HashSet<SymbolIdentifier>,
39) {
40    populate_codebase_inner(codebase, symbol_references, safe_symbols, safe_symbol_members, None)
41}
42
43/// Populates the codebase with an optional set of dirty (invalidated) symbols for targeted iteration.
44///
45/// When `dirty_symbols` is provided, function-like, class-type, and constant repopulation
46/// uses targeted `get_mut()` lookups instead of scanning the entire HashMap — O(dirty) instead of O(all).
47/// This is critical for incremental mode where only a few symbols change per cycle.
48#[allow(clippy::implicit_hasher)]
49pub fn populate_codebase_targeted(
50    codebase: &mut CodebaseMetadata,
51    symbol_references: &mut SymbolReferences,
52    safe_symbols: AtomSet,
53    safe_symbol_members: HashSet<SymbolIdentifier>,
54    dirty_symbols: HashSet<SymbolIdentifier>,
55) {
56    populate_codebase_inner(codebase, symbol_references, safe_symbols, safe_symbol_members, Some(dirty_symbols))
57}
58
59fn populate_codebase_inner(
60    codebase: &mut CodebaseMetadata,
61    symbol_references: &mut SymbolReferences,
62    safe_symbols: AtomSet,
63    safe_symbol_members: HashSet<SymbolIdentifier>,
64    dirty_symbols: Option<HashSet<SymbolIdentifier>>,
65) {
66    let mut class_likes_to_repopulate = AtomSet::default();
67    if let Some(dirty) = &dirty_symbols {
68        let mut dirty_class_names = AtomSet::default();
69        for (name, _) in dirty {
70            dirty_class_names.insert(*name);
71        }
72
73        for class_name in &dirty_class_names {
74            if let Some(metadata) = codebase.class_likes.get(class_name)
75                && (!metadata.flags.is_populated()
76                    || (metadata.flags.is_user_defined() && !safe_symbols.contains(class_name)))
77            {
78                class_likes_to_repopulate.insert(*class_name);
79            }
80        }
81
82        // Also repopulate user-defined classes that were invalidated by the
83        // cascade (not in safe_symbols) but are not directly in dirty_symbols.
84        // This handles e.g. a child class whose parent's method was removed:
85        // the child is invalidated (not safe) but its file didn't change,
86        // so it's not in dirty_symbols.
87        for (class_name, metadata) in &codebase.class_likes {
88            if metadata.flags.is_user_defined()
89                && !safe_symbols.contains(class_name)
90                && !class_likes_to_repopulate.contains(class_name)
91            {
92                class_likes_to_repopulate.insert(*class_name);
93            }
94        }
95    } else {
96        for (name, metadata) in &codebase.class_likes {
97            if !metadata.flags.is_populated() || (metadata.flags.is_user_defined() && !safe_symbols.contains(name)) {
98                class_likes_to_repopulate.insert(*name);
99            }
100        }
101    }
102
103    for class_like_name in &class_likes_to_repopulate {
104        if let Some(classlike_info) = codebase.class_likes.get_mut(class_like_name) {
105            classlike_info.flags &= !MetadataFlags::POPULATED;
106            classlike_info.declaring_property_ids.clear();
107            classlike_info.appearing_property_ids.clear();
108            classlike_info.declaring_method_ids.clear();
109            classlike_info.appearing_method_ids.clear();
110            classlike_info.overridden_method_ids.clear();
111            classlike_info.overridden_property_ids.clear();
112            classlike_info.invalid_dependencies.clear();
113        }
114    }
115
116    let sorted_classes = sorter::sort_class_likes(codebase, &class_likes_to_repopulate);
117    for class_name in sorted_classes {
118        hierarchy::populate_class_like_metadata_iterative(class_name, codebase, symbol_references);
119    }
120
121    let incremental = !safe_symbols.is_empty() || !safe_symbol_members.is_empty();
122
123    if let Some(dirty) = &dirty_symbols {
124        for dirty_key in dirty {
125            if let Some(function_like_metadata) = codebase.function_likes.get_mut(dirty_key) {
126                let force_repopulation = function_like_metadata.flags.is_user_defined();
127                if function_like_metadata.flags.is_populated() && !force_repopulation {
128                    continue;
129                }
130
131                let reference_source = if dirty_key.1.is_empty() || function_like_metadata.get_kind().is_closure() {
132                    ReferenceSource::Symbol(true, dirty_key.0)
133                } else {
134                    ReferenceSource::ClassLikeMember(true, dirty_key.0, dirty_key.1)
135                };
136
137                signatures::populate_function_like_metadata(
138                    function_like_metadata,
139                    &codebase.symbols,
140                    &reference_source,
141                    symbol_references,
142                    force_repopulation,
143                );
144            }
145        }
146
147        // Also repopulate non-dirty function_likes that are not safe but need repopulation.
148        // This handles e.g. a child method when the parent class was re-added:
149        // the child method isn't dirty (file didn't change) but it's not safe
150        // (parent class changed), so it needs type signature repopulation.
151        for (name, function_like_metadata) in &mut codebase.function_likes {
152            if dirty.contains(name) {
153                continue;
154            }
155
156            let is_closure_or_arrow =
157                function_like_metadata.get_kind().is_closure() || function_like_metadata.get_kind().is_arrow_function();
158
159            let is_safe = if is_closure_or_arrow {
160                true
161            } else if name.1.is_empty() {
162                safe_symbols.contains(&name.0)
163            } else {
164                safe_symbol_members.contains(name) || safe_symbols.contains(&name.0)
165            };
166
167            let force_repopulation = function_like_metadata.flags.is_user_defined() && !is_safe;
168            if function_like_metadata.flags.is_populated() && !force_repopulation {
169                continue;
170            }
171
172            let reference_source = if name.1.is_empty() || function_like_metadata.get_kind().is_closure() {
173                ReferenceSource::Symbol(true, name.0)
174            } else {
175                ReferenceSource::ClassLikeMember(true, name.0, name.1)
176            };
177
178            signatures::populate_function_like_metadata(
179                function_like_metadata,
180                &codebase.symbols,
181                &reference_source,
182                symbol_references,
183                force_repopulation,
184            );
185        }
186    } else {
187        for (name, function_like_metadata) in &mut codebase.function_likes {
188            let is_closure_or_arrow =
189                function_like_metadata.get_kind().is_closure() || function_like_metadata.get_kind().is_arrow_function();
190
191            let is_safe = if is_closure_or_arrow {
192                true
193            } else if name.1.is_empty() {
194                safe_symbols.contains(&name.0)
195            } else {
196                safe_symbol_members.contains(name) || safe_symbols.contains(&name.0)
197            };
198
199            let force_repopulation = function_like_metadata.flags.is_user_defined() && !is_safe;
200            if incremental && function_like_metadata.flags.is_populated() && !force_repopulation {
201                continue;
202            }
203
204            let reference_source = if name.1.is_empty() || function_like_metadata.get_kind().is_closure() {
205                ReferenceSource::Symbol(true, name.0)
206            } else {
207                ReferenceSource::ClassLikeMember(true, name.0, name.1)
208            };
209
210            signatures::populate_function_like_metadata(
211                function_like_metadata,
212                &codebase.symbols,
213                &reference_source,
214                symbol_references,
215                force_repopulation,
216            );
217        }
218    }
219
220    if let Some(_dirty) = &dirty_symbols {
221        for class_name in &class_likes_to_repopulate {
222            if let Some(metadata) = codebase.class_likes.get_mut(class_name) {
223                hierarchy::populate_class_like_types(
224                    *class_name,
225                    metadata,
226                    &codebase.symbols,
227                    symbol_references,
228                    true, // force: these are in the repopulate set
229                );
230            }
231        }
232    } else {
233        for (name, metadata) in &mut codebase.class_likes {
234            let force_repopulation = metadata.flags.is_user_defined() && !safe_symbols.contains(name);
235
236            if incremental && metadata.flags.is_populated() && !force_repopulation {
237                continue;
238            }
239
240            hierarchy::populate_class_like_types(
241                *name,
242                metadata,
243                &codebase.symbols,
244                symbol_references,
245                force_repopulation,
246            );
247        }
248    }
249
250    if let Some(dirty) = &dirty_symbols {
251        let mut dirty_const_names: AtomSet = AtomSet::default();
252        for (name, member) in dirty {
253            if member.is_empty() {
254                dirty_const_names.insert(*name);
255            }
256        }
257
258        for const_name in dirty_const_names {
259            if let Some(constant) = codebase.constants.get_mut(&const_name) {
260                let force_repopulation = constant.flags.is_user_defined();
261                if constant.flags.is_populated() && !force_repopulation {
262                    continue;
263                }
264
265                populate_constant(const_name, constant, &codebase.symbols, symbol_references, force_repopulation);
266            }
267        }
268    } else {
269        for (name, constant) in &mut codebase.constants {
270            let force_repopulation = constant.flags.is_user_defined() && !safe_symbols.contains(name);
271            if incremental && constant.flags.is_populated() && !force_repopulation {
272                continue;
273            }
274
275            populate_constant(*name, constant, &codebase.symbols, symbol_references, force_repopulation);
276        }
277    }
278
279    if !incremental || !class_likes_to_repopulate.is_empty() {
280        let mut direct_classlike_descendants = AtomMap::default();
281        let mut all_classlike_descendants = AtomMap::default();
282
283        for (class_like_name, class_like_metadata) in &codebase.class_likes {
284            for parent_interface in &class_like_metadata.all_parent_interfaces {
285                all_classlike_descendants
286                    .entry(*parent_interface)
287                    .or_insert_with(AtomSet::default)
288                    .insert(*class_like_name);
289            }
290
291            for parent_interface in &class_like_metadata.direct_parent_interfaces {
292                direct_classlike_descendants
293                    .entry(*parent_interface)
294                    .or_insert_with(AtomSet::default)
295                    .insert(*class_like_name);
296            }
297
298            for parent_class in &class_like_metadata.all_parent_classes {
299                all_classlike_descendants
300                    .entry(*parent_class)
301                    .or_insert_with(AtomSet::default)
302                    .insert(*class_like_name);
303            }
304
305            for used_trait in &class_like_metadata.used_traits {
306                all_classlike_descendants.entry(*used_trait).or_default().insert(*class_like_name);
307            }
308
309            if let Some(parent_class) = &class_like_metadata.direct_parent_class {
310                direct_classlike_descendants
311                    .entry(*parent_class)
312                    .or_insert_with(AtomSet::default)
313                    .insert(*class_like_name);
314            }
315        }
316
317        for (parent_name, children) in &direct_classlike_descendants {
318            if let Some(parent_metadata) = codebase.class_likes.get_mut(parent_name) {
319                parent_metadata.child_class_likes = Some(children.clone());
320            }
321        }
322
323        codebase.all_class_like_descendants = all_classlike_descendants;
324        codebase.direct_classlike_descendants = direct_classlike_descendants;
325    }
326
327    if !incremental || !class_likes_to_repopulate.is_empty() {
328        let dirty_classes = if dirty_symbols.is_some() { Some(&class_likes_to_repopulate) } else { None };
329
330        docblock::inherit_method_docblocks(codebase, &safe_symbols, dirty_classes);
331    }
332
333    codebase.safe_symbols = safe_symbols;
334    codebase.safe_symbol_members = safe_symbol_members;
335}
336
337/// Populates a single constant's type metadata.
338fn populate_constant(
339    name: Atom,
340    constant: &mut ConstantMetadata,
341    symbols: &Symbols,
342    symbol_references: &mut SymbolReferences,
343    force_repopulation: bool,
344) {
345    for attribute_metadata in &constant.attributes {
346        symbol_references.add_symbol_reference_to_symbol(name, attribute_metadata.name, true);
347    }
348
349    if let Some(type_metadata) = &mut constant.type_metadata {
350        populate_union_type(
351            &mut type_metadata.type_union,
352            symbols,
353            Some(&ReferenceSource::Symbol(true, name)),
354            symbol_references,
355            force_repopulation,
356        );
357    }
358
359    if let Some(inferred_type) = &mut constant.inferred_type {
360        populate_union_type(
361            inferred_type,
362            symbols,
363            Some(&ReferenceSource::Symbol(true, name)),
364            symbol_references,
365            force_repopulation,
366        );
367    }
368
369    constant.flags |= MetadataFlags::POPULATED;
370}