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
47pub 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
57pub 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
67pub 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
76pub 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
85pub 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
94pub 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
103pub 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
112pub 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
121pub 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
130pub 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
139pub 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
200pub 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
212pub 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
225pub 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
235pub 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
249pub 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
259pub 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
274pub 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
283pub 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
292pub 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
301pub 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
310pub 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 let mut buffer = [0u8; 64];
325
326 let mut writer = &mut buffer[..];
329
330 unsafe {
332 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset).unwrap_unchecked()
333 };
334
335 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
338
339 atom(
340 unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() },
343 )
344}
345
346pub 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
356pub 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
372pub 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 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 *method_identifier
421}
422
423pub 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
464pub 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#[derive(Debug, PartialEq)]
481pub enum ClassConstantOrEnumCase<'a> {
482 Constant(&'a ClassLikeConstantMetadata),
483 EnumCase(&'a EnumCaseMetadata),
484}
485
486pub 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
510pub 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#[inline]
592pub fn get_all_descendants(codebase: &CodebaseMetadata, fqcn: &str) -> AtomSet {
593 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
594
595 let mut all_descendants = AtomSet::default();
597 let mut queue = vec![&lowercase_fqcn];
598 let mut visited = AtomSet::default();
599 visited.insert(lowercase_fqcn); 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 all_descendants.insert(*descendant);
607 queue.push(descendant); }
609 }
610 }
611 }
612
613 all_descendants
614}
615
616pub 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#[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 let constant_metadata = class_metadata.constants.get(&constant_name)?;
686
687 if let Some(type_metadata) = constant_metadata.type_metadata.as_ref() {
689 return Some(Cow::Borrowed(&type_metadata.type_union));
693 }
694
695 constant_metadata.inferred_type.as_ref().map(|atomic_type| {
697 Cow::Owned(TUnion::from_atomic(atomic_type.clone()))
699 })
700}