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