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