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 is_closure_or_arrow = function_like_metadata.get_kind().is_closure()
125                    || function_like_metadata.get_kind().is_arrow_function();
126
127                if is_closure_or_arrow {
128                    continue;
129                }
130
131                let force_repopulation = function_like_metadata.flags.is_user_defined();
132                if function_like_metadata.flags.is_populated() && !force_repopulation {
133                    continue;
134                }
135
136                let reference_source = if dirty_key.1.is_empty() || function_like_metadata.get_kind().is_closure() {
137                    ReferenceSource::Symbol(true, dirty_key.0)
138                } else {
139                    ReferenceSource::ClassLikeMember(true, dirty_key.0, dirty_key.1)
140                };
141
142                signatures::populate_function_like_metadata(
143                    function_like_metadata,
144                    &codebase.symbols,
145                    &reference_source,
146                    symbol_references,
147                    force_repopulation,
148                );
149            }
150        }
151
152        // Also repopulate non-dirty function_likes that are not safe but need repopulation.
153        // This handles e.g. a child method when the parent class was re-added:
154        // the child method isn't dirty (file didn't change) but it's not safe
155        // (parent class changed), so it needs type signature repopulation.
156        for (name, function_like_metadata) in &mut codebase.function_likes {
157            if dirty.contains(name) {
158                continue;
159            }
160
161            let is_closure_or_arrow =
162                function_like_metadata.get_kind().is_closure() || function_like_metadata.get_kind().is_arrow_function();
163
164            let is_safe = if is_closure_or_arrow {
165                true
166            } else if name.1.is_empty() {
167                safe_symbols.contains(&name.0)
168            } else {
169                safe_symbol_members.contains(name) || safe_symbols.contains(&name.0)
170            };
171
172            let force_repopulation = function_like_metadata.flags.is_user_defined() && !is_safe;
173            if function_like_metadata.flags.is_populated() && !force_repopulation {
174                continue;
175            }
176
177            let reference_source = if name.1.is_empty() || function_like_metadata.get_kind().is_closure() {
178                ReferenceSource::Symbol(true, name.0)
179            } else {
180                ReferenceSource::ClassLikeMember(true, name.0, name.1)
181            };
182
183            signatures::populate_function_like_metadata(
184                function_like_metadata,
185                &codebase.symbols,
186                &reference_source,
187                symbol_references,
188                force_repopulation,
189            );
190        }
191    } else {
192        for (name, function_like_metadata) in &mut codebase.function_likes {
193            let is_closure_or_arrow =
194                function_like_metadata.get_kind().is_closure() || function_like_metadata.get_kind().is_arrow_function();
195
196            let is_safe = if is_closure_or_arrow {
197                true
198            } else if name.1.is_empty() {
199                safe_symbols.contains(&name.0)
200            } else {
201                safe_symbol_members.contains(name) || safe_symbols.contains(&name.0)
202            };
203
204            let force_repopulation = function_like_metadata.flags.is_user_defined() && !is_safe;
205            if incremental && function_like_metadata.flags.is_populated() && !force_repopulation {
206                continue;
207            }
208
209            let reference_source = if name.1.is_empty() || function_like_metadata.get_kind().is_closure() {
210                ReferenceSource::Symbol(true, name.0)
211            } else {
212                ReferenceSource::ClassLikeMember(true, name.0, name.1)
213            };
214
215            signatures::populate_function_like_metadata(
216                function_like_metadata,
217                &codebase.symbols,
218                &reference_source,
219                symbol_references,
220                force_repopulation,
221            );
222        }
223    }
224
225    if let Some(_dirty) = &dirty_symbols {
226        for class_name in &class_likes_to_repopulate {
227            if let Some(metadata) = codebase.class_likes.get_mut(class_name) {
228                hierarchy::populate_class_like_types(
229                    *class_name,
230                    metadata,
231                    &codebase.symbols,
232                    symbol_references,
233                    true, // force: these are in the repopulate set
234                );
235            }
236        }
237    } else {
238        for (name, metadata) in &mut codebase.class_likes {
239            let force_repopulation = metadata.flags.is_user_defined() && !safe_symbols.contains(name);
240
241            if incremental && metadata.flags.is_populated() && !force_repopulation {
242                continue;
243            }
244
245            hierarchy::populate_class_like_types(
246                *name,
247                metadata,
248                &codebase.symbols,
249                symbol_references,
250                force_repopulation,
251            );
252        }
253    }
254
255    if let Some(dirty) = &dirty_symbols {
256        let mut dirty_const_names: AtomSet = AtomSet::default();
257        for (name, member) in dirty {
258            if member.is_empty() {
259                dirty_const_names.insert(*name);
260            }
261        }
262
263        for const_name in dirty_const_names {
264            if let Some(constant) = codebase.constants.get_mut(&const_name) {
265                let force_repopulation = constant.flags.is_user_defined();
266                if constant.flags.is_populated() && !force_repopulation {
267                    continue;
268                }
269
270                populate_constant(const_name, constant, &codebase.symbols, symbol_references, force_repopulation);
271            }
272        }
273    } else {
274        for (name, constant) in &mut codebase.constants {
275            let force_repopulation = constant.flags.is_user_defined() && !safe_symbols.contains(name);
276            if incremental && constant.flags.is_populated() && !force_repopulation {
277                continue;
278            }
279
280            populate_constant(*name, constant, &codebase.symbols, symbol_references, force_repopulation);
281        }
282    }
283
284    if !incremental || !class_likes_to_repopulate.is_empty() {
285        let mut direct_classlike_descendants = AtomMap::default();
286        let mut all_classlike_descendants = AtomMap::default();
287
288        for (class_like_name, class_like_metadata) in &codebase.class_likes {
289            for parent_interface in &class_like_metadata.all_parent_interfaces {
290                all_classlike_descendants
291                    .entry(*parent_interface)
292                    .or_insert_with(AtomSet::default)
293                    .insert(*class_like_name);
294            }
295
296            for parent_interface in &class_like_metadata.direct_parent_interfaces {
297                direct_classlike_descendants
298                    .entry(*parent_interface)
299                    .or_insert_with(AtomSet::default)
300                    .insert(*class_like_name);
301            }
302
303            for parent_class in &class_like_metadata.all_parent_classes {
304                all_classlike_descendants
305                    .entry(*parent_class)
306                    .or_insert_with(AtomSet::default)
307                    .insert(*class_like_name);
308            }
309
310            for used_trait in &class_like_metadata.used_traits {
311                all_classlike_descendants.entry(*used_trait).or_default().insert(*class_like_name);
312            }
313
314            if let Some(parent_class) = &class_like_metadata.direct_parent_class {
315                direct_classlike_descendants
316                    .entry(*parent_class)
317                    .or_insert_with(AtomSet::default)
318                    .insert(*class_like_name);
319            }
320        }
321
322        for (parent_name, children) in &direct_classlike_descendants {
323            if let Some(parent_metadata) = codebase.class_likes.get_mut(parent_name) {
324                parent_metadata.child_class_likes = Some(children.clone());
325            }
326        }
327
328        codebase.all_class_like_descendants = all_classlike_descendants;
329        codebase.direct_classlike_descendants = direct_classlike_descendants;
330    }
331
332    if !incremental || !class_likes_to_repopulate.is_empty() {
333        let dirty_classes = if dirty_symbols.is_some() { Some(&class_likes_to_repopulate) } else { None };
334
335        docblock::inherit_method_docblocks(codebase, &safe_symbols, dirty_classes);
336    }
337
338    codebase.safe_symbols = safe_symbols;
339    codebase.safe_symbol_members = safe_symbol_members;
340}
341
342/// Populates a single constant's type metadata.
343fn populate_constant(
344    name: Atom,
345    constant: &mut ConstantMetadata,
346    symbols: &Symbols,
347    symbol_references: &mut SymbolReferences,
348    force_repopulation: bool,
349) {
350    for attribute_metadata in &constant.attributes {
351        symbol_references.add_symbol_reference_to_symbol(name, attribute_metadata.name, true);
352    }
353
354    if let Some(type_metadata) = &mut constant.type_metadata {
355        populate_union_type(
356            &mut type_metadata.type_union,
357            symbols,
358            Some(&ReferenceSource::Symbol(true, name)),
359            symbol_references,
360            force_repopulation,
361        );
362    }
363
364    if let Some(inferred_type) = &mut constant.inferred_type {
365        populate_union_type(
366            inferred_type,
367            symbols,
368            Some(&ReferenceSource::Symbol(true, name)),
369            symbol_references,
370            force_repopulation,
371        );
372    }
373
374    constant.flags |= MetadataFlags::POPULATED;
375}