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