mago_codex/
lib.rs

1use std::borrow::Cow;
2
3use mago_atom::Atom;
4use mago_atom::AtomSet;
5use mago_atom::ascii_lowercase_atom;
6use mago_atom::ascii_lowercase_constant_name_atom;
7use mago_atom::atom;
8use mago_atom::empty_atom;
9use mago_atom::u32_atom;
10use mago_atom::u64_atom;
11use mago_database::file::FileId;
12use mago_span::Position;
13use mago_span::Span;
14
15use crate::identifier::method::MethodIdentifier;
16use crate::metadata::CodebaseMetadata;
17use crate::metadata::class_like::ClassLikeMetadata;
18use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
19use crate::metadata::constant::ConstantMetadata;
20use crate::metadata::enum_case::EnumCaseMetadata;
21use crate::metadata::function_like::FunctionLikeMetadata;
22use crate::metadata::property::PropertyMetadata;
23use crate::metadata::ttype::TypeMetadata;
24use crate::symbol::SymbolKind;
25use crate::ttype::atomic::TAtomic;
26use crate::ttype::atomic::object::TObject;
27use crate::ttype::union::TUnion;
28
29pub mod assertion;
30pub mod consts;
31pub mod context;
32pub mod diff;
33pub mod flags;
34pub mod identifier;
35pub mod issue;
36pub mod metadata;
37pub mod misc;
38pub mod populator;
39pub mod reference;
40pub mod scanner;
41pub mod symbol;
42pub mod ttype;
43pub mod visibility;
44
45mod utils;
46
47/// Checks if a global function exists in the codebase.
48///
49/// This lookup is case-insensitive, in line with PHP's behavior for function names.
50pub fn function_exists(codebase: &CodebaseMetadata, function_name: &str) -> bool {
51    let lowercase_function_name = ascii_lowercase_atom(function_name);
52    let function_identifier = (empty_atom(), lowercase_function_name);
53
54    codebase.function_likes.contains_key(&function_identifier)
55}
56
57/// Checks if a global constant exists in the codebase.
58///
59/// The lookup for the namespace part of the constant name is case-insensitive,
60/// but the constant name itself is case-sensitive, matching PHP's behavior.
61pub fn constant_exists(codebase: &CodebaseMetadata, constant_name: &str) -> bool {
62    let lowercase_constant_name = ascii_lowercase_constant_name_atom(constant_name);
63
64    codebase.constants.contains_key(&lowercase_constant_name)
65}
66
67/// Checks if a class exists in the codebase.
68///
69/// This lookup is case-insensitive, in line with PHP's behavior for class names.
70pub fn class_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
71    let lowercase_name = ascii_lowercase_atom(name);
72
73    matches!(codebase.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
74}
75
76/// Checks if a class or trait exists in the codebase.
77///
78/// This lookup is case-insensitive, in line with PHP's behavior for class names.
79pub fn class_or_trait_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
80    let lowercase_name = ascii_lowercase_atom(name);
81
82    matches!(codebase.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
83}
84
85/// Checks if an interface exists in the codebase.
86///
87/// This lookup is case-insensitive, in line with PHP's behavior for interface names.
88pub fn interface_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
89    let lowercase_name = ascii_lowercase_atom(name);
90
91    matches!(codebase.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
92}
93
94/// Checks if a class or interface exists in the codebase.
95///
96/// This lookup is case-insensitive, in line with PHP's behavior for class names.
97pub fn class_or_interface_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
98    let lowercase_name = ascii_lowercase_atom(name);
99
100    matches!(codebase.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
101}
102
103/// Checks if an enum exists in the codebase.
104///
105/// This lookup is case-insensitive, in line with PHP's behavior for enum names.
106pub fn enum_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
107    let lowercase_name = ascii_lowercase_atom(name);
108
109    matches!(codebase.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
110}
111
112/// Checks if a trait exists in the codebase.
113///
114/// This lookup is case-insensitive, in line with PHP's behavior for trait names.
115pub fn trait_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
116    let lowercase_name = ascii_lowercase_atom(name);
117
118    matches!(codebase.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
119}
120
121/// Checks if a class-like (class, interface, enum, or trait) exists in the codebase.
122///
123/// This lookup is case-insensitive.
124pub fn class_like_exists(codebase: &CodebaseMetadata, name: &str) -> bool {
125    let lowercase_name = ascii_lowercase_atom(name);
126
127    codebase.symbols.contains(&lowercase_name)
128}
129
130/// Checks if the given name corresponds to an enum or a final class.
131///
132/// This lookup is case-insensitive.
133pub fn is_enum_or_final_class(codebase: &CodebaseMetadata, name: &str) -> bool {
134    let lowercase_name = ascii_lowercase_atom(name);
135
136    codebase.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
137}
138
139/// Checks if a method exists on a given class-like (including inherited methods).
140///
141/// The lookup for both the class-like name and the method name is case-insensitive.
142pub fn method_exists(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
143    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
144    let lowercase_method_name = ascii_lowercase_atom(method_name);
145
146    codebase
147        .class_likes
148        .get(&lowercase_fqcn)
149        .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method_name))
150}
151
152pub fn method_identifier_exists(codebase: &CodebaseMetadata, method_identifier: &MethodIdentifier) -> bool {
153    let lowercase_fqcn = ascii_lowercase_atom(method_identifier.get_class_name());
154    let lowercase_method_name = ascii_lowercase_atom(method_identifier.get_method_name());
155
156    let method_identifier = (lowercase_fqcn, lowercase_method_name);
157
158    codebase.function_likes.contains_key(&method_identifier)
159}
160
161pub fn is_method_abstract(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
162    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
163    let lowercase_method_name = ascii_lowercase_atom(method_name);
164
165    let method_identifier = (lowercase_fqcn, lowercase_method_name);
166
167    codebase
168        .function_likes
169        .get(&method_identifier)
170        .and_then(|meta| meta.method_metadata.as_ref())
171        .is_some_and(|method| method.is_abstract)
172}
173
174pub fn is_method_static(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
175    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
176    let lowercase_method_name = ascii_lowercase_atom(method_name);
177
178    let method_identifier = (lowercase_fqcn, lowercase_method_name);
179
180    codebase
181        .function_likes
182        .get(&method_identifier)
183        .and_then(|meta| meta.method_metadata.as_ref())
184        .is_some_and(|method| method.is_static)
185}
186
187pub fn is_method_final(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
188    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
189    let lowercase_method_name = ascii_lowercase_atom(method_name);
190
191    let method_identifier = (lowercase_fqcn, lowercase_method_name);
192
193    codebase
194        .function_likes
195        .get(&method_identifier)
196        .and_then(|meta| meta.method_metadata.as_ref())
197        .is_some_and(|method| method.is_final)
198}
199
200/// Checks if a property exists on a given class-like (including inherited properties).
201///
202/// The lookup for the class-like name is case-insensitive, but the property name is case-sensitive.
203pub fn property_exists(codebase: &CodebaseMetadata, fqcn: &str, property_name: &str) -> bool {
204    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
205
206    codebase
207        .class_likes
208        .get(&lowercase_fqcn)
209        .is_some_and(|meta| meta.appearing_property_ids.contains_key(&atom(property_name)))
210}
211
212/// Checks if a method is declared directly on a given class-like (not inherited).
213///
214/// The lookup for both the class-like name and the method name is case-insensitive.
215pub fn declaring_method_exists(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
216    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
217    let lowercase_method_name = ascii_lowercase_atom(method_name);
218
219    codebase
220        .class_likes
221        .get(&lowercase_fqcn)
222        .is_some_and(|meta| meta.declaring_method_ids.contains_key(&lowercase_method_name))
223}
224
225/// Checks if a property is declared directly on a given class-like (not inherited).
226///
227/// The lookup for the class-like name is case-insensitive, but the property name is case-sensitive.
228pub fn declaring_property_exists(codebase: &CodebaseMetadata, fqcn: &str, property_name: &str) -> bool {
229    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
230    let property_name = atom(property_name);
231
232    codebase.class_likes.get(&lowercase_fqcn).is_some_and(|meta| meta.properties.contains_key(&property_name))
233}
234
235/// Checks if a constant or enum case exists on a given class-like.
236///
237/// The lookup for the class-like name is case-insensitive, but the constant/case name is case-sensitive.
238pub fn class_like_constant_or_enum_case_exists(codebase: &CodebaseMetadata, fqcn: &str, constant_name: &str) -> bool {
239    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
240    let constant_name = atom(constant_name);
241
242    if let Some(meta) = codebase.class_likes.get(&lowercase_fqcn) {
243        return meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name);
244    }
245
246    false
247}
248
249/// Retrieves the metadata for a global function.
250///
251/// This lookup is case-insensitive.
252pub fn get_function<'a>(codebase: &'a CodebaseMetadata, function_name: &str) -> Option<&'a FunctionLikeMetadata> {
253    let lowercase_function_name = ascii_lowercase_atom(function_name);
254    let function_identifier = (empty_atom(), lowercase_function_name);
255
256    codebase.function_likes.get(&function_identifier)
257}
258
259/// Retrieves the metadata for a closure based on its position in the source code.
260///
261/// This function uses the source ID and the closure's position to uniquely identify it.
262pub fn get_closure<'a>(
263    codebase: &'a CodebaseMetadata,
264    file_id: &FileId,
265    position: &Position,
266) -> Option<&'a FunctionLikeMetadata> {
267    let file_ref = u64_atom(file_id.as_u64());
268    let closure_ref = u32_atom(position.offset);
269    let identifier = (file_ref, closure_ref);
270
271    codebase.function_likes.get(&identifier)
272}
273
274/// Retrieves the metadata for a global constant.
275///
276/// The namespace lookup is case-insensitive, but the constant name itself is case-sensitive.
277pub fn get_constant<'a>(codebase: &'a CodebaseMetadata, constant_name: &str) -> Option<&'a ConstantMetadata> {
278    let lowercase_constant_name = ascii_lowercase_constant_name_atom(constant_name);
279
280    codebase.constants.get(&lowercase_constant_name)
281}
282
283/// Retrieves the metadata for a class.
284///
285/// This lookup is case-insensitive.
286pub fn get_class<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
287    let lowercase_name = ascii_lowercase_atom(name);
288
289    if codebase.symbols.contains_class(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
290}
291
292/// Retrieves the metadata for an interface.
293///
294/// This lookup is case-insensitive.
295pub fn get_interface<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
296    let lowercase_name = ascii_lowercase_atom(name);
297
298    if codebase.symbols.contains_interface(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
299}
300
301/// Retrieves the metadata for an enum.
302///
303/// This lookup is case-insensitive.
304pub fn get_enum<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
305    let lowercase_name = ascii_lowercase_atom(name);
306
307    if codebase.symbols.contains_enum(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
308}
309
310/// Retrieves the metadata for a trait.
311///
312/// This lookup is case-insensitive.
313pub fn get_trait<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
314    let lowercase_name = ascii_lowercase_atom(name);
315
316    if codebase.symbols.contains_trait(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
317}
318
319pub fn get_anonymous_class_name(span: Span) -> Atom {
320    use std::io::Write;
321
322    // A 64-byte buffer on the stack. This is ample space for the prefix,
323    // u64 file id, and 2 u32 integers, preventing any chance of a heap allocation.
324    let mut buffer = [0u8; 64];
325
326    // Use a block to limit the scope of the mutable writer
327    // `writer` is a mutable slice that implements `std::io::Write`.
328    let mut writer = &mut buffer[..];
329
330    // SAFETY: We use `unwrap_unchecked` here because we are writing to a fixed-size buffer
331    unsafe {
332        write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset).unwrap_unchecked()
333    };
334
335    // Determine how many bytes were written by checking the length of the original buffer
336    // against what the `writer` had left. This is a common pattern for `io::Write` on slices.
337    let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
338
339    atom(
340        // SAFETY: We use `unwrap_unchecked` here because we are certain the bytes
341        // up to `written_len` are valid UTF-8.
342        unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() },
343    )
344}
345
346/// Retrieves the metadata for an anonymous class based on its span.
347///
348/// This function generates a unique name for the anonymous class based on its span,
349/// which includes the source file and the start and end offsets.
350pub fn get_anonymous_class(codebase: &CodebaseMetadata, span: Span) -> Option<&ClassLikeMetadata> {
351    let name = get_anonymous_class_name(span);
352
353    if class_exists(codebase, &name) { codebase.class_likes.get(&name) } else { None }
354}
355
356/// Retrieves the metadata for any class-like (class, interface, enum, or trait).
357///
358/// This lookup is case-insensitive.
359pub fn get_class_like<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
360    let lowercase_name = ascii_lowercase_atom(name);
361
362    codebase.class_likes.get(&lowercase_name)
363}
364
365pub fn get_declaring_class_for_property(codebase: &CodebaseMetadata, fqcn: &str, property_name: &str) -> Option<Atom> {
366    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
367    let property_name = atom(property_name);
368
369    codebase.class_likes.get(&lowercase_fqcn)?.declaring_property_ids.get(&property_name).copied()
370}
371
372/// Retrieves the metadata for a property, searching the inheritance hierarchy.
373///
374/// This function finds where the property was originally declared and returns its metadata.
375/// The lookup for the class-like name is case-insensitive, but the property name is case-sensitive.
376pub fn get_declaring_property<'a>(
377    codebase: &'a CodebaseMetadata,
378    fqcn: &str,
379    property_name: &str,
380) -> Option<&'a PropertyMetadata> {
381    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
382    let property_name = atom(property_name);
383
384    let declaring_fqcn = codebase.class_likes.get(&lowercase_fqcn)?.declaring_property_ids.get(&property_name)?;
385
386    codebase.class_likes.get(declaring_fqcn)?.properties.get(&property_name)
387}
388
389pub fn get_method_identifier(fqcn: &str, method_name: &str) -> MethodIdentifier {
390    MethodIdentifier::new(atom(fqcn), atom(method_name))
391}
392
393pub fn get_declaring_method_identifier(
394    codebase: &CodebaseMetadata,
395    method_identifier: &MethodIdentifier,
396) -> MethodIdentifier {
397    let lowercase_fqcn = ascii_lowercase_atom(method_identifier.get_class_name());
398    let lowercase_method_name = ascii_lowercase_atom(method_identifier.get_method_name());
399
400    let Some(class_like_metadata) = codebase.class_likes.get(&lowercase_fqcn) else {
401        // If the class-like doesn't exist, return the method ID as is
402        return *method_identifier;
403    };
404
405    if let Some(declaring_fqcn) = class_like_metadata.declaring_method_ids.get(&lowercase_method_name)
406        && let Some(declaring_class_metadata) = codebase.class_likes.get(declaring_fqcn)
407    {
408        return MethodIdentifier::new(declaring_class_metadata.original_name, *method_identifier.get_method_name());
409    };
410
411    if class_like_metadata.flags.is_abstract()
412        && let Some(overridden_classes) = class_like_metadata.overridden_method_ids.get(&lowercase_method_name)
413        && let Some(first_class) = overridden_classes.iter().next()
414        && let Some(first_class_metadata) = codebase.class_likes.get(first_class)
415    {
416        return MethodIdentifier::new(first_class_metadata.original_name, *method_identifier.get_method_name());
417    }
418
419    // If the method isn't declared in this class, return the method ID as is
420    *method_identifier
421}
422
423/// Retrieves the metadata for a method, searching the inheritance hierarchy.
424///
425/// This function finds where the method is declared (which could be an ancestor class/trait)
426/// and returns the metadata from there.
427///
428/// The lookup for both the class-like name and the method name is case-insensitive.
429pub fn get_declaring_method<'a>(
430    codebase: &'a CodebaseMetadata,
431    fqcn: &str,
432    method_name: &str,
433) -> Option<&'a FunctionLikeMetadata> {
434    let method_id = MethodIdentifier::new(atom(fqcn), atom(method_name));
435    let declaring_method_id = get_declaring_method_identifier(codebase, &method_id);
436
437    get_method(codebase, declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
438}
439
440pub fn get_method_by_id<'a>(
441    codebase: &'a CodebaseMetadata,
442    method_identifier: &MethodIdentifier,
443) -> Option<&'a FunctionLikeMetadata> {
444    let lowercase_fqcn = ascii_lowercase_atom(method_identifier.get_class_name());
445    let lowercase_method_name = ascii_lowercase_atom(method_identifier.get_method_name());
446
447    let function_identifier = (lowercase_fqcn, lowercase_method_name);
448
449    codebase.function_likes.get(&function_identifier)
450}
451
452pub fn get_method<'a>(
453    codebase: &'a CodebaseMetadata,
454    fqcn: &str,
455    method_name: &str,
456) -> Option<&'a FunctionLikeMetadata> {
457    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
458    let lowercase_method_name = ascii_lowercase_atom(method_name);
459    let function_like_identifier = (lowercase_fqcn, lowercase_method_name);
460
461    codebase.function_likes.get(&function_like_identifier)
462}
463
464/// Retrieves the metadata for a property that is declared directly on the given class-like.
465///
466/// This does not search the inheritance hierarchy.
467/// The lookup for the class-like name is case-insensitive, but the property name is case-sensitive.
468pub fn get_property<'a>(
469    codebase: &'a CodebaseMetadata,
470    fqcn: &str,
471    property_name: &str,
472) -> Option<&'a PropertyMetadata> {
473    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
474    let property_name = atom(property_name);
475
476    codebase.class_likes.get(&lowercase_fqcn)?.properties.get(&property_name)
477}
478
479/// An enum to represent either a class constant or an enum case.
480#[derive(Debug, PartialEq)]
481pub enum ClassConstantOrEnumCase<'a> {
482    Constant(&'a ClassLikeConstantMetadata),
483    EnumCase(&'a EnumCaseMetadata),
484}
485
486/// Retrieves the metadata for a class constant or an enum case from a class-like.
487///
488/// The lookup for the class-like name is case-insensitive, but the constant/case name is case-sensitive.
489pub fn get_class_like_constant_or_enum_case<'a>(
490    codebase: &'a CodebaseMetadata,
491    fqcn: &str,
492    constant_name: &str,
493) -> Option<ClassConstantOrEnumCase<'a>> {
494    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
495    let constant_name = atom(constant_name);
496
497    let class_like = codebase.class_likes.get(&lowercase_fqcn)?;
498
499    if let Some(constant_meta) = class_like.constants.get(&constant_name) {
500        return Some(ClassConstantOrEnumCase::Constant(constant_meta));
501    }
502
503    if let Some(enum_case_meta) = class_like.enum_cases.get(&constant_name) {
504        return Some(ClassConstantOrEnumCase::EnumCase(enum_case_meta));
505    }
506
507    None
508}
509
510/// Checks if a class-like is an instance of another class-like.
511///
512/// This function checks if the `child` class-like is an instance of the `parent` class-like
513/// by looking up their metadata in the codebase.
514pub fn is_instance_of(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
515    if child_name == parent_name {
516        return true;
517    }
518
519    let lowercase_child_name = ascii_lowercase_atom(child_name);
520    let lowercase_parent_name = ascii_lowercase_atom(parent_name);
521
522    if lowercase_child_name == lowercase_parent_name {
523        return true;
524    }
525
526    let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
527        return false;
528    };
529
530    child_meta.has_parent(&lowercase_parent_name)
531}
532
533pub fn inherits_class(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
534    let lowercase_child_name = ascii_lowercase_atom(child_name);
535    let lowercase_parent_name = ascii_lowercase_atom(parent_name);
536
537    let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
538        return false;
539    };
540
541    child_meta.all_parent_classes.contains(&lowercase_parent_name)
542}
543
544pub fn directly_inherits_class(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
545    let lowercase_child_name = ascii_lowercase_atom(child_name);
546    let lowercase_parent_name = ascii_lowercase_atom(parent_name);
547
548    let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
549        return false;
550    };
551
552    child_meta.direct_parent_class.as_ref().is_some_and(|parent_class| parent_class == &lowercase_parent_name)
553}
554
555pub fn inherits_interface(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
556    let lowercase_child_name = ascii_lowercase_atom(child_name);
557    let lowercase_parent_name = ascii_lowercase_atom(parent_name);
558
559    let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
560        return false;
561    };
562
563    child_meta.all_parent_interfaces.contains(&lowercase_parent_name)
564}
565
566pub fn directly_inherits_interface(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
567    let lowercase_child_name = ascii_lowercase_atom(child_name);
568    let lowercase_parent_name = ascii_lowercase_atom(parent_name);
569
570    let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
571        return false;
572    };
573
574    child_meta.direct_parent_interfaces.contains(&lowercase_parent_name)
575}
576
577pub fn uses_trait(codebase: &CodebaseMetadata, child_name: &str, trait_name: &str) -> bool {
578    let lowercase_child_name = ascii_lowercase_atom(child_name);
579    let lowercase_trait_name = ascii_lowercase_atom(trait_name);
580
581    let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
582        return false;
583    };
584
585    child_meta.used_traits.contains(&lowercase_trait_name)
586}
587
588/// Recursively collects all descendant class/interface/enum FQCNs for a given class-like structure.
589/// Uses the pre-computed `all_classlike_descendants` map if available, otherwise might be empty.
590/// Warning: Recursive; could stack overflow on extremely deep hierarchies if map isn't precomputed well.
591#[inline]
592pub fn get_all_descendants(codebase: &CodebaseMetadata, fqcn: &str) -> AtomSet {
593    let lowercase_fqcn = ascii_lowercase_atom(fqcn);
594
595    // This implementation assumes direct_classlike_descendants is populated correctly.
596    let mut all_descendants = AtomSet::default();
597    let mut queue = vec![&lowercase_fqcn];
598    let mut visited = AtomSet::default();
599    visited.insert(lowercase_fqcn); // Don't include self in descendants
600
601    while let Some(current_name) = queue.pop() {
602        if let Some(direct_descendants) = codebase.direct_classlike_descendants.get(current_name) {
603            for descendant in direct_descendants {
604                if visited.insert(*descendant) {
605                    // Add to results only if not visited before
606                    all_descendants.insert(*descendant);
607                    queue.push(descendant); // Add to queue for further exploration
608                }
609            }
610        }
611    }
612
613    all_descendants
614}
615
616/// Checks if a method is overridden from a parent class-like.
617///
618/// This function checks if the method with the given name in the specified class-like
619/// is overridden from a parent class-like by looking up the metadata in the codebase.
620///
621/// The lookup for both the class-like name and the method name is case-insensitive.
622pub fn is_method_overriding(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
623    let lowercase_method_name = ascii_lowercase_atom(method_name);
624
625    get_class_like(codebase, fqcn)
626        .is_some_and(|metadata| metadata.overridden_method_ids.contains_key(&lowercase_method_name))
627}
628
629pub fn get_function_like_thrown_types<'a>(
630    codebase: &'a CodebaseMetadata,
631    class_like: Option<&'a ClassLikeMetadata>,
632    function_like: &'a FunctionLikeMetadata,
633) -> &'a [TypeMetadata] {
634    if !function_like.thrown_types.is_empty() {
635        return function_like.thrown_types.as_slice();
636    }
637
638    if !function_like.kind.is_method() {
639        return &[];
640    }
641
642    let Some(class_like) = class_like else {
643        return &[];
644    };
645
646    let Some(method_name) = function_like.name.as_ref() else {
647        return &[];
648    };
649
650    for parent_class_name_id in class_like.overridden_method_ids.get(method_name).into_iter().flatten() {
651        let Some(parent_class) = codebase.class_likes.get(parent_class_name_id) else {
652            continue;
653        };
654
655        let parent_method_id = (*parent_class_name_id, *method_name);
656        if let Some(parent_method) = codebase.function_likes.get(&parent_method_id) {
657            let thrown = get_function_like_thrown_types(codebase, Some(parent_class), parent_method);
658            if !thrown.is_empty() {
659                return thrown;
660            }
661        }
662    }
663
664    &[]
665}
666
667/// Retrieves the type of a class constant, considering type hints and inferred types.
668/// Returns `None` if the class or constant doesn't exist, or type cannot be determined.
669#[inline]
670pub fn get_class_constant_type<'a>(
671    codebase: &'a CodebaseMetadata,
672    fq_class_name: &str,
673    constant_name: &str,
674) -> Option<Cow<'a, TUnion>> {
675    let class_metadata = get_class_like(codebase, fq_class_name)?;
676    let constant_name = atom(constant_name);
677
678    if class_metadata.kind.is_enum() && class_metadata.enum_cases.contains_key(&constant_name) {
679        let atomic = TAtomic::Object(TObject::new_enum_case(class_metadata.original_name, constant_name));
680
681        return Some(Cow::Owned(TUnion::from_atomic(atomic)));
682    }
683
684    // It's a regular class constant
685    let constant_metadata = class_metadata.constants.get(&constant_name)?;
686
687    // Prefer the type signature if available
688    if let Some(type_metadata) = constant_metadata.type_metadata.as_ref() {
689        // Return borrowed signature type directly
690        // (Original logic about boring scalars/is_this seemed complex and possibly specific
691        //  to a particular analysis stage; simplifying here to return declared type if present)
692        return Some(Cow::Borrowed(&type_metadata.type_union));
693    }
694
695    // Fall back to inferred type if no signature
696    constant_metadata.inferred_type.as_ref().map(|atomic_type| {
697        // Wrap the atomic type in a TUnion if returning inferred type
698        Cow::Owned(TUnion::from_atomic(atomic_type.clone()))
699    })
700}