mago_codex/metadata/
mod.rs

1use std::borrow::Cow;
2use std::collections::hash_map::Entry;
3
4use ahash::HashMap;
5use ahash::HashSet;
6use serde::Deserialize;
7use serde::Serialize;
8
9use mago_atom::Atom;
10use mago_atom::AtomMap;
11use mago_atom::AtomSet;
12use mago_atom::ascii_lowercase_atom;
13use mago_atom::ascii_lowercase_constant_name_atom;
14use mago_atom::atom;
15use mago_atom::empty_atom;
16use mago_atom::u32_atom;
17use mago_atom::u64_atom;
18use mago_database::file::FileId;
19use mago_reporting::IssueCollection;
20use mago_span::Position;
21
22use crate::identifier::method::MethodIdentifier;
23use crate::metadata::class_like::ClassLikeMetadata;
24use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
25use crate::metadata::constant::ConstantMetadata;
26use crate::metadata::enum_case::EnumCaseMetadata;
27use crate::metadata::function_like::FunctionLikeMetadata;
28use crate::metadata::property::PropertyMetadata;
29use crate::metadata::ttype::TypeMetadata;
30use crate::signature::FileSignature;
31use crate::symbol::SymbolKind;
32use crate::symbol::Symbols;
33use crate::ttype::atomic::TAtomic;
34use crate::ttype::atomic::object::TObject;
35use crate::ttype::union::TUnion;
36use crate::visibility::Visibility;
37
38pub mod attribute;
39pub mod class_like;
40pub mod class_like_constant;
41pub mod constant;
42pub mod enum_case;
43pub mod flags;
44pub mod function_like;
45pub mod parameter;
46pub mod property;
47pub mod property_hook;
48pub mod ttype;
49
50/// Holds all analyzed information about the symbols, structures, and relationships within a codebase.
51///
52/// This acts as the central repository for metadata gathered during static analysis,
53/// including details about classes, interfaces, traits, enums, functions, constants,
54/// their members, inheritance, dependencies, and associated types.
55#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
56pub struct CodebaseMetadata {
57    /// Configuration flag: Should types be inferred based on usage patterns?
58    pub infer_types_from_usage: bool,
59    /// Map from class-like FQCN (`Atom`) to its detailed metadata (`ClassLikeMetadata`).
60    pub class_likes: AtomMap<ClassLikeMetadata>,
61    /// Map from a function/method identifier tuple `(scope_id, function_id)` to its metadata (`FunctionLikeMetadata`).
62    /// `scope_id` is the FQCN for methods or often `Atom::empty()` for global functions.
63    pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
64    /// Stores the kind (Class, Interface, etc.) for every known symbol FQCN.
65    pub symbols: Symbols,
66    /// Map from global constant FQN (`Atom`) to its metadata (`ConstantMetadata`).
67    pub constants: AtomMap<ConstantMetadata>,
68    /// Map from class/interface FQCN to the set of all its descendants (recursive).
69    pub all_class_like_descendants: AtomMap<AtomSet>,
70    /// Map from class/interface FQCN to the set of its direct descendants (children).
71    pub direct_classlike_descendants: AtomMap<AtomSet>,
72    /// Set of symbols (FQCNs) that are considered safe/validated.
73    pub safe_symbols: AtomSet,
74    /// Set of specific members `(SymbolFQCN, MemberName)` that are considered safe/validated.
75    pub safe_symbol_members: HashSet<(Atom, Atom)>,
76    /// Each `FileSignature` contains a hierarchical tree of `DefSignatureNode` representing
77    /// top-level symbols (classes, functions, constants) and their nested members (methods, properties).
78    pub file_signatures: HashMap<FileId, FileSignature>,
79}
80
81impl CodebaseMetadata {
82    // Construction
83
84    /// Creates a new, empty `CodebaseMetadata` with default values.
85    #[inline]
86    #[must_use]
87    pub fn new() -> Self {
88        Self::default()
89    }
90    // Symbol Existence Checks
91
92    /// Checks if a class exists in the codebase (case-insensitive).
93    ///
94    /// # Examples
95    /// ```ignore
96    /// if codebase.class_exists("MyClass") {
97    ///     // MyClass is a class
98    /// }
99    /// ```
100    #[inline]
101    #[must_use]
102    pub fn class_exists(&self, name: &str) -> bool {
103        let lowercase_name = ascii_lowercase_atom(name);
104        matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
105    }
106
107    /// Checks if an interface exists in the codebase (case-insensitive).
108    #[inline]
109    #[must_use]
110    pub fn interface_exists(&self, name: &str) -> bool {
111        let lowercase_name = ascii_lowercase_atom(name);
112        matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
113    }
114
115    /// Checks if a trait exists in the codebase (case-insensitive).
116    #[inline]
117    #[must_use]
118    pub fn trait_exists(&self, name: &str) -> bool {
119        let lowercase_name = ascii_lowercase_atom(name);
120        matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
121    }
122
123    /// Checks if an enum exists in the codebase (case-insensitive).
124    #[inline]
125    #[must_use]
126    pub fn enum_exists(&self, name: &str) -> bool {
127        let lowercase_name = ascii_lowercase_atom(name);
128        matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
129    }
130
131    /// Checks if a class-like (class, interface, trait, or enum) exists (case-insensitive).
132    #[inline]
133    #[must_use]
134    pub fn class_like_exists(&self, name: &str) -> bool {
135        let lowercase_name = ascii_lowercase_atom(name);
136        self.symbols.contains(&lowercase_name)
137    }
138
139    /// Checks if a class or trait exists in the codebase (case-insensitive).
140    #[inline]
141    #[must_use]
142    pub fn class_or_trait_exists(&self, name: &str) -> bool {
143        let lowercase_name = ascii_lowercase_atom(name);
144        matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
145    }
146
147    /// Checks if a class or interface exists in the codebase (case-insensitive).
148    #[inline]
149    #[must_use]
150    pub fn class_or_interface_exists(&self, name: &str) -> bool {
151        let lowercase_name = ascii_lowercase_atom(name);
152        matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
153    }
154
155    /// Checks if a method identifier exists in the codebase.
156    #[inline]
157    #[must_use]
158    pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
159        let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
160        let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
161        let identifier = (lowercase_class, lowercase_method);
162        self.function_likes.contains_key(&identifier)
163    }
164
165    /// Checks if a global function exists in the codebase (case-insensitive).
166    #[inline]
167    #[must_use]
168    pub fn function_exists(&self, name: &str) -> bool {
169        let lowercase_name = ascii_lowercase_atom(name);
170        let identifier = (empty_atom(), lowercase_name);
171        self.function_likes.contains_key(&identifier)
172    }
173
174    /// Checks if a global constant exists in the codebase.
175    /// The namespace part is case-insensitive, but the constant name is case-sensitive.
176    #[inline]
177    #[must_use]
178    pub fn constant_exists(&self, name: &str) -> bool {
179        let lowercase_name = ascii_lowercase_constant_name_atom(name);
180        self.constants.contains_key(&lowercase_name)
181    }
182
183    /// Checks if a method exists on a class-like, including inherited methods (case-insensitive).
184    #[inline]
185    #[must_use]
186    pub fn method_exists(&self, class: &str, method: &str) -> bool {
187        let lowercase_class = ascii_lowercase_atom(class);
188        let lowercase_method = ascii_lowercase_atom(method);
189        self.class_likes
190            .get(&lowercase_class)
191            .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
192    }
193
194    /// Checks if a property exists on a class-like, including inherited properties.
195    /// Class name is case-insensitive, property name is case-sensitive.
196    #[inline]
197    #[must_use]
198    pub fn property_exists(&self, class: &str, property: &str) -> bool {
199        let lowercase_class = ascii_lowercase_atom(class);
200        let property_name = atom(property);
201        self.class_likes
202            .get(&lowercase_class)
203            .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
204    }
205
206    /// Checks if a class constant or enum case exists on a class-like.
207    /// Class name is case-insensitive, constant/case name is case-sensitive.
208    #[inline]
209    #[must_use]
210    pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
211        let lowercase_class = ascii_lowercase_atom(class);
212        let constant_name = atom(constant);
213        self.class_likes.get(&lowercase_class).is_some_and(|meta| {
214            meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
215        })
216    }
217
218    /// Checks if a method is declared directly in a class (not inherited).
219    #[inline]
220    #[must_use]
221    pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
222        let lowercase_class = ascii_lowercase_atom(class);
223        let lowercase_method = ascii_lowercase_atom(method);
224        self.class_likes
225            .get(&lowercase_class)
226            .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
227            .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
228    }
229
230    /// Checks if a property is declared directly in a class (not inherited).
231    #[inline]
232    #[must_use]
233    pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
234        let lowercase_class = ascii_lowercase_atom(class);
235        let property_name = atom(property);
236        self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
237    }
238    // Metadata Retrieval - Class-likes
239
240    /// Retrieves metadata for a class (case-insensitive).
241    /// Returns `None` if the name doesn't correspond to a class.
242    #[inline]
243    #[must_use]
244    pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
245        let lowercase_name = ascii_lowercase_atom(name);
246        if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
247    }
248
249    /// Retrieves metadata for an interface (case-insensitive).
250    #[inline]
251    #[must_use]
252    pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
253        let lowercase_name = ascii_lowercase_atom(name);
254        if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
255    }
256
257    /// Retrieves metadata for a trait (case-insensitive).
258    #[inline]
259    #[must_use]
260    pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
261        let lowercase_name = ascii_lowercase_atom(name);
262        if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
263    }
264
265    /// Retrieves metadata for an enum (case-insensitive).
266    #[inline]
267    #[must_use]
268    pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
269        let lowercase_name = ascii_lowercase_atom(name);
270        if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
271    }
272
273    /// Retrieves metadata for any class-like structure (case-insensitive).
274    #[inline]
275    #[must_use]
276    pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
277        let lowercase_name = ascii_lowercase_atom(name);
278        self.class_likes.get(&lowercase_name)
279    }
280    // Metadata Retrieval - Functions & Methods
281
282    /// Retrieves metadata for a global function (case-insensitive).
283    #[inline]
284    #[must_use]
285    pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
286        let lowercase_name = ascii_lowercase_atom(name);
287        let identifier = (empty_atom(), lowercase_name);
288        self.function_likes.get(&identifier)
289    }
290
291    /// Retrieves metadata for a method (case-insensitive for both class and method names).
292    #[inline]
293    #[must_use]
294    pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
295        let lowercase_class = ascii_lowercase_atom(class);
296        let lowercase_method = ascii_lowercase_atom(method);
297        let identifier = (lowercase_class, lowercase_method);
298        self.function_likes.get(&identifier)
299    }
300
301    /// Retrieves metadata for a closure based on its file and position.
302    #[inline]
303    #[must_use]
304    pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
305        let file_ref = u64_atom(file_id.as_u64());
306        let closure_ref = u32_atom(position.offset);
307        let identifier = (file_ref, closure_ref);
308        self.function_likes.get(&identifier)
309    }
310
311    /// Retrieves method metadata by `MethodIdentifier`.
312    #[inline]
313    #[must_use]
314    pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
315        let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
316        let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
317        let identifier = (lowercase_class, lowercase_method);
318        self.function_likes.get(&identifier)
319    }
320
321    /// Retrieves the declaring method metadata, following the inheritance chain.
322    /// This finds where the method is actually implemented.
323    #[inline]
324    #[must_use]
325    pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
326        let method_id = MethodIdentifier::new(atom(class), atom(method));
327        let declaring_method_id = self.get_declaring_method_identifier(&method_id);
328        self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
329    }
330
331    /// Retrieves metadata for any function-like construct (function, method, or closure).
332    /// This is a convenience method that delegates to the appropriate getter based on the identifier type.
333    #[inline]
334    #[must_use]
335    pub fn get_function_like(
336        &self,
337        identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
338    ) -> Option<&FunctionLikeMetadata> {
339        use crate::identifier::function_like::FunctionLikeIdentifier;
340        match identifier {
341            FunctionLikeIdentifier::Function(name) => self.get_function(name),
342            FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
343            FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
344        }
345    }
346    // Metadata Retrieval - Constants
347
348    /// Retrieves metadata for a global constant.
349    /// Namespace lookup is case-insensitive, constant name is case-sensitive.
350    #[inline]
351    #[must_use]
352    pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
353        let lowercase_name = ascii_lowercase_constant_name_atom(name);
354        self.constants.get(&lowercase_name)
355    }
356
357    /// Retrieves metadata for a class constant.
358    /// Class name is case-insensitive, constant name is case-sensitive.
359    #[inline]
360    #[must_use]
361    pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
362        let lowercase_class = ascii_lowercase_atom(class);
363        let constant_name = atom(constant);
364        self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
365    }
366
367    /// Retrieves metadata for an enum case.
368    #[inline]
369    #[must_use]
370    pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
371        let lowercase_class = ascii_lowercase_atom(class);
372        let case_name = atom(case);
373        self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
374    }
375    // Metadata Retrieval - Properties
376
377    /// Retrieves metadata for a property directly from the class where it's declared.
378    /// Class name is case-insensitive, property name is case-sensitive.
379    #[inline]
380    #[must_use]
381    pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
382        let lowercase_class = ascii_lowercase_atom(class);
383        let property_name = atom(property);
384        self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
385    }
386
387    /// Retrieves the property metadata, potentially from a parent class if inherited.
388    #[inline]
389    #[must_use]
390    pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
391        let lowercase_class = ascii_lowercase_atom(class);
392        let property_name = atom(property);
393        let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
394        self.class_likes.get(declaring_class)?.properties.get(&property_name)
395    }
396    // Type Resolution
397
398    /// Gets the type of a property, resolving it from the declaring class if needed.
399    #[inline]
400    #[must_use]
401    pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
402        let lowercase_class = ascii_lowercase_atom(class);
403        let property_name = atom(property);
404        let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
405        let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
406        property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
407    }
408
409    /// Gets the type of a class constant, considering both type hints and inferred types.
410    #[must_use]
411    pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
412        let lowercase_class = ascii_lowercase_atom(class);
413        let constant_name = atom(constant);
414        let class_meta = self.class_likes.get(&lowercase_class)?;
415
416        // Check if it's an enum case
417        if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
418            let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
419            return Some(Cow::Owned(TUnion::from_atomic(atomic)));
420        }
421
422        // It's a regular class constant
423        let constant_meta = class_meta.constants.get(&constant_name)?;
424
425        // Prefer the type signature if available
426        if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
427            return Some(Cow::Borrowed(&type_meta.type_union));
428        }
429
430        // Fall back to inferred type
431        constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
432    }
433
434    /// Gets the literal value of a class constant if it was inferred.
435    #[inline]
436    #[must_use]
437    pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
438        let lowercase_class = ascii_lowercase_atom(class);
439        let constant_name = atom(constant);
440        self.class_likes
441            .get(&lowercase_class)
442            .and_then(|meta| meta.constants.get(&constant_name))
443            .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
444    }
445    // Inheritance Queries
446
447    /// Checks if a child class extends a parent class (case-insensitive).
448    #[inline]
449    #[must_use]
450    pub fn class_extends(&self, child: &str, parent: &str) -> bool {
451        let lowercase_child = ascii_lowercase_atom(child);
452        let lowercase_parent = ascii_lowercase_atom(parent);
453        self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
454    }
455
456    /// Checks if a class directly extends a parent class (case-insensitive).
457    #[inline]
458    #[must_use]
459    pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
460        let lowercase_child = ascii_lowercase_atom(child);
461        let lowercase_parent = ascii_lowercase_atom(parent);
462        self.class_likes
463            .get(&lowercase_child)
464            .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
465    }
466
467    /// Checks if a class implements an interface (case-insensitive).
468    #[inline]
469    #[must_use]
470    pub fn class_implements(&self, class: &str, interface: &str) -> bool {
471        let lowercase_class = ascii_lowercase_atom(class);
472        let lowercase_interface = ascii_lowercase_atom(interface);
473        self.class_likes
474            .get(&lowercase_class)
475            .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
476    }
477
478    /// Checks if a class directly implements an interface (case-insensitive).
479    #[inline]
480    #[must_use]
481    pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
482        let lowercase_class = ascii_lowercase_atom(class);
483        let lowercase_interface = ascii_lowercase_atom(interface);
484        self.class_likes
485            .get(&lowercase_class)
486            .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
487    }
488
489    /// Checks if a class uses a trait (case-insensitive).
490    #[inline]
491    #[must_use]
492    pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
493        let lowercase_class = ascii_lowercase_atom(class);
494        let lowercase_trait = ascii_lowercase_atom(trait_name);
495        self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
496    }
497
498    /// Checks if a trait has `@require-extends` for a class (case-insensitive).
499    /// Returns true if the trait requires extending the specified class or any of its parents.
500    #[inline]
501    #[must_use]
502    pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
503        let lowercase_trait = ascii_lowercase_atom(trait_name);
504
505        self.class_likes
506            .get(&lowercase_trait)
507            .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
508    }
509
510    /// Checks if child is an instance of parent (via extends or implements).
511    #[inline]
512    #[must_use]
513    pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
514        if child == parent {
515            return true;
516        }
517
518        let lowercase_child = ascii_lowercase_atom(child);
519        let lowercase_parent = ascii_lowercase_atom(parent);
520
521        if lowercase_child == lowercase_parent {
522            return true;
523        }
524
525        self.class_likes.get(&lowercase_child).is_some_and(|meta| {
526            meta.all_parent_classes.contains(&lowercase_parent)
527                || meta.all_parent_interfaces.contains(&lowercase_parent)
528                || meta.used_traits.contains(&lowercase_parent)
529                || meta.require_extends.contains(&lowercase_parent)
530                || meta.require_implements.contains(&lowercase_parent)
531        })
532    }
533
534    /// Checks if the given name is an enum or final class.
535    #[inline]
536    #[must_use]
537    pub fn is_enum_or_final_class(&self, name: &str) -> bool {
538        let lowercase_name = ascii_lowercase_atom(name);
539        self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
540    }
541
542    /// Checks if a class-like can be part of an intersection.
543    /// Generally, only final classes and enums cannot be intersected.
544    #[inline]
545    #[must_use]
546    pub fn is_inheritable(&self, name: &str) -> bool {
547        let lowercase_name = ascii_lowercase_atom(name);
548        match self.symbols.get_kind(&lowercase_name) {
549            Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
550            Some(SymbolKind::Enum) => false,
551            Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
552        }
553    }
554
555    /// Gets all descendants of a class (recursive).
556    #[inline]
557    #[must_use]
558    pub fn get_class_descendants(&self, class: &str) -> AtomSet {
559        let lowercase_class = ascii_lowercase_atom(class);
560        let mut all_descendants = AtomSet::default();
561        let mut queue = vec![&lowercase_class];
562        let mut visited = AtomSet::default();
563        visited.insert(lowercase_class);
564
565        while let Some(current_name) = queue.pop() {
566            if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
567                for descendant in direct_descendants {
568                    if visited.insert(*descendant) {
569                        all_descendants.insert(*descendant);
570                        queue.push(descendant);
571                    }
572                }
573            }
574        }
575
576        all_descendants
577    }
578
579    /// Gets all ancestors of a class (parents + interfaces).
580    #[inline]
581    #[must_use]
582    pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
583        let lowercase_class = ascii_lowercase_atom(class);
584        let mut ancestors = AtomSet::default();
585        if let Some(meta) = self.class_likes.get(&lowercase_class) {
586            ancestors.extend(meta.all_parent_classes.iter().copied());
587            ancestors.extend(meta.all_parent_interfaces.iter().copied());
588        }
589        ancestors
590    }
591    // Method Resolution
592
593    /// Gets the class where a method is declared (following inheritance).
594    #[inline]
595    #[must_use]
596    pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
597        let lowercase_class = ascii_lowercase_atom(class);
598        let lowercase_method = ascii_lowercase_atom(method);
599
600        self.class_likes
601            .get(&lowercase_class)?
602            .declaring_method_ids
603            .get(&lowercase_method)
604            .map(|method_id| *method_id.get_class_name())
605    }
606
607    /// Gets the class where a method appears (could be the declaring class or child class).
608    #[inline]
609    #[must_use]
610    pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
611        let lowercase_class = ascii_lowercase_atom(class);
612        let lowercase_method = ascii_lowercase_atom(method);
613        self.class_likes
614            .get(&lowercase_class)?
615            .appearing_method_ids
616            .get(&lowercase_method)
617            .map(|method_id| *method_id.get_class_name())
618    }
619
620    /// Gets the declaring method identifier for a method.
621    #[must_use]
622    pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
623        let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
624        let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
625
626        let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
627            return *method_id;
628        };
629
630        if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
631            return *declaring_method_id;
632        }
633
634        if class_meta.flags.is_abstract()
635            && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
636            && let Some((_, first_method_id)) = overridden_map.iter().next()
637        {
638            return *first_method_id;
639        }
640
641        *method_id
642    }
643
644    /// Checks if a method is overriding a parent method.
645    #[inline]
646    #[must_use]
647    pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
648        let lowercase_class = ascii_lowercase_atom(class);
649        let lowercase_method = ascii_lowercase_atom(method);
650        self.class_likes
651            .get(&lowercase_class)
652            .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
653    }
654
655    /// Checks if a method is abstract.
656    #[inline]
657    #[must_use]
658    pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
659        let lowercase_class = ascii_lowercase_atom(class);
660        let lowercase_method = ascii_lowercase_atom(method);
661        let identifier = (lowercase_class, lowercase_method);
662        self.function_likes
663            .get(&identifier)
664            .and_then(|meta| meta.method_metadata.as_ref())
665            .is_some_and(|method_meta| method_meta.is_abstract)
666    }
667
668    /// Checks if a method is static.
669    #[inline]
670    #[must_use]
671    pub fn method_is_static(&self, class: &str, method: &str) -> bool {
672        let lowercase_class = ascii_lowercase_atom(class);
673        let lowercase_method = ascii_lowercase_atom(method);
674        let identifier = (lowercase_class, lowercase_method);
675        self.function_likes
676            .get(&identifier)
677            .and_then(|meta| meta.method_metadata.as_ref())
678            .is_some_and(|method_meta| method_meta.is_static)
679    }
680
681    /// Checks if a method is final.
682    #[inline]
683    #[must_use]
684    pub fn method_is_final(&self, class: &str, method: &str) -> bool {
685        let lowercase_class = ascii_lowercase_atom(class);
686        let lowercase_method = ascii_lowercase_atom(method);
687        let identifier = (lowercase_class, lowercase_method);
688        self.function_likes
689            .get(&identifier)
690            .and_then(|meta| meta.method_metadata.as_ref())
691            .is_some_and(|method_meta| method_meta.is_final)
692    }
693
694    /// Gets the effective visibility of a method, taking into account trait alias visibility overrides.
695    ///
696    /// When a trait method is aliased with a visibility modifier (e.g., `use Trait { method as public aliasedMethod; }`),
697    /// the visibility is stored in the class's `trait_visibility_map`. This method checks that map first,
698    /// then falls back to the method's declared visibility.
699    #[inline]
700    #[must_use]
701    pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
702        let lowercase_class = ascii_lowercase_atom(class);
703        let lowercase_method = ascii_lowercase_atom(method);
704
705        // First check if there's a trait visibility override for this method
706        if let Some(class_meta) = self.class_likes.get(&lowercase_class)
707            && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
708        {
709            return Some(*overridden_visibility);
710        }
711
712        // Fall back to the method's declared visibility
713        let declaring_class = self.get_declaring_method_class(class, method)?;
714        let identifier = (declaring_class, lowercase_method);
715
716        self.function_likes
717            .get(&identifier)
718            .and_then(|meta| meta.method_metadata.as_ref())
719            .map(|method_meta| method_meta.visibility)
720    }
721
722    /// Gets thrown types for a function-like, including inherited throws.
723    #[must_use]
724    pub fn get_function_like_thrown_types<'a>(
725        &'a self,
726        class_like: Option<&'a ClassLikeMetadata>,
727        function_like: &'a FunctionLikeMetadata,
728    ) -> &'a [TypeMetadata] {
729        if !function_like.thrown_types.is_empty() {
730            return function_like.thrown_types.as_slice();
731        }
732
733        if !function_like.kind.is_method() {
734            return &[];
735        }
736
737        let Some(class_like) = class_like else {
738            return &[];
739        };
740
741        let Some(method_name) = function_like.name.as_ref() else {
742            return &[];
743        };
744
745        if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
746            for (parent_class_name, parent_method_id) in overridden_map {
747                let Some(parent_class) = self.class_likes.get(parent_class_name) else {
748                    continue;
749                };
750
751                let parent_method_key = (*parent_method_id.get_class_name(), *parent_method_id.get_method_name());
752                if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
753                    let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
754                    if !thrown.is_empty() {
755                        return thrown;
756                    }
757                }
758            }
759        }
760
761        &[]
762    }
763    // Property Resolution
764
765    /// Gets the class where a property is declared.
766    #[inline]
767    #[must_use]
768    pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
769        let lowercase_class = ascii_lowercase_atom(class);
770        let property_name = atom(property);
771        self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
772    }
773
774    /// Gets the class where a property appears.
775    #[inline]
776    #[must_use]
777    pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
778        let lowercase_class = ascii_lowercase_atom(class);
779        let property_name = atom(property);
780        self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
781    }
782
783    /// Gets all descendants of a class (recursive).
784    #[must_use]
785    pub fn get_all_descendants(&self, class: &str) -> AtomSet {
786        let lowercase_class = ascii_lowercase_atom(class);
787        let mut all_descendants = AtomSet::default();
788        let mut queue = vec![&lowercase_class];
789        let mut visited = AtomSet::default();
790        visited.insert(lowercase_class);
791
792        while let Some(current_name) = queue.pop() {
793            if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
794                for descendant in direct_descendants {
795                    if visited.insert(*descendant) {
796                        all_descendants.insert(*descendant);
797                        queue.push(descendant);
798                    }
799                }
800            }
801        }
802
803        all_descendants
804    }
805
806    /// Generates a unique name for an anonymous class based on its span.
807    #[must_use]
808    pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
809        use std::io::Write;
810
811        let mut buffer = [0u8; 64];
812        let mut writer = &mut buffer[..];
813
814        unsafe {
815            write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
816                .unwrap_unchecked();
817        };
818
819        let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
820
821        atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
822    }
823
824    /// Retrieves the metadata for an anonymous class based on its span.
825    #[must_use]
826    pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
827        let name = Self::get_anonymous_class_name(span);
828        if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
829    }
830
831    /// Gets the file signature for a given file ID.
832    ///
833    /// # Arguments
834    ///
835    /// * `file_id` - The file identifier
836    ///
837    /// # Returns
838    ///
839    /// A reference to the `FileSignature` if it exists, or `None` if the file has no signature.
840    #[inline]
841    #[must_use]
842    pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
843        self.file_signatures.get(file_id)
844    }
845
846    /// Adds or updates a file signature for a given file ID.
847    ///
848    /// # Arguments
849    ///
850    /// * `file_id` - The file identifier
851    /// * `signature` - The file signature
852    ///
853    /// # Returns
854    ///
855    /// The previous `FileSignature` if it existed.
856    #[inline]
857    pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
858        self.file_signatures.insert(file_id, signature)
859    }
860
861    /// Removes the file signature for a given file ID.
862    ///
863    /// # Arguments
864    ///
865    /// * `file_id` - The file identifier
866    ///
867    /// # Returns
868    ///
869    /// The removed `FileSignature` if it existed.
870    #[inline]
871    pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
872        self.file_signatures.remove(file_id)
873    }
874
875    // Utility Methods
876
877    /// Merges information from another `CodebaseMetadata` into this one.
878    pub fn extend(&mut self, other: CodebaseMetadata) {
879        for (k, v) in other.class_likes {
880            let metadata_to_keep = match self.class_likes.entry(k) {
881                Entry::Occupied(entry) => {
882                    let existing = entry.remove();
883                    if v.flags.is_user_defined() {
884                        v
885                    } else if existing.flags.is_user_defined() {
886                        existing
887                    } else if v.flags.is_built_in() {
888                        v
889                    } else if existing.flags.is_built_in() {
890                        existing
891                    } else {
892                        v
893                    }
894                }
895                Entry::Vacant(_) => v,
896            };
897            self.class_likes.insert(k, metadata_to_keep);
898        }
899
900        for (k, v) in other.function_likes {
901            let metadata_to_keep = match self.function_likes.entry(k) {
902                Entry::Occupied(entry) => {
903                    let existing = entry.remove();
904                    if v.flags.is_user_defined() {
905                        v
906                    } else if existing.flags.is_user_defined() {
907                        existing
908                    } else if v.flags.is_built_in() {
909                        v
910                    } else if existing.flags.is_built_in() {
911                        existing
912                    } else {
913                        v
914                    }
915                }
916                Entry::Vacant(_) => v,
917            };
918            self.function_likes.insert(k, metadata_to_keep);
919        }
920
921        for (k, v) in other.constants {
922            let metadata_to_keep = match self.constants.entry(k) {
923                Entry::Occupied(entry) => {
924                    let existing = entry.remove();
925                    if v.flags.is_user_defined() {
926                        v
927                    } else if existing.flags.is_user_defined() {
928                        existing
929                    } else if v.flags.is_built_in() {
930                        v
931                    } else if existing.flags.is_built_in() {
932                        existing
933                    } else {
934                        v
935                    }
936                }
937                Entry::Vacant(_) => v,
938            };
939            self.constants.insert(k, metadata_to_keep);
940        }
941
942        self.symbols.extend(other.symbols);
943
944        for (k, v) in other.all_class_like_descendants {
945            self.all_class_like_descendants.entry(k).or_default().extend(v);
946        }
947
948        for (k, v) in other.direct_classlike_descendants {
949            self.direct_classlike_descendants.entry(k).or_default().extend(v);
950        }
951
952        self.safe_symbols.extend(other.safe_symbols);
953        self.safe_symbol_members.extend(other.safe_symbol_members);
954        self.infer_types_from_usage |= other.infer_types_from_usage;
955    }
956
957    /// Takes all issues from the codebase metadata.
958    pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
959        let mut issues = IssueCollection::new();
960
961        for meta in self.class_likes.values_mut() {
962            if user_defined && !meta.flags.is_user_defined() {
963                continue;
964            }
965            issues.extend(meta.take_issues());
966        }
967
968        for meta in self.function_likes.values_mut() {
969            if user_defined && !meta.flags.is_user_defined() {
970                continue;
971            }
972            issues.extend(meta.take_issues());
973        }
974
975        for meta in self.constants.values_mut() {
976            if user_defined && !meta.flags.is_user_defined() {
977                continue;
978            }
979            issues.extend(meta.take_issues());
980        }
981
982        issues
983    }
984
985    /// Gets all file IDs that have signatures in this metadata.
986    ///
987    /// This is a helper method for incremental analysis to iterate over all files.
988    #[must_use]
989    pub fn get_all_file_ids(&self) -> Vec<FileId> {
990        self.file_signatures.keys().copied().collect()
991    }
992}
993
994impl Default for CodebaseMetadata {
995    #[inline]
996    fn default() -> Self {
997        Self {
998            class_likes: AtomMap::default(),
999            function_likes: HashMap::default(),
1000            symbols: Symbols::new(),
1001            infer_types_from_usage: false,
1002            constants: AtomMap::default(),
1003            all_class_like_descendants: AtomMap::default(),
1004            direct_classlike_descendants: AtomMap::default(),
1005            safe_symbols: AtomSet::default(),
1006            safe_symbol_members: HashSet::default(),
1007            file_signatures: HashMap::default(),
1008        }
1009    }
1010}