Skip to main content

mago_codex/populator/
mod.rs

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