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