1use std::borrow::Cow;
2
3use ahash::HashSet;
4
5use mago_interner::StringIdentifier;
6use mago_interner::ThreadedInterner;
7use mago_span::Position;
8use mago_span::Span;
9
10use crate::identifier::method::MethodIdentifier;
11use crate::metadata::CodebaseMetadata;
12use crate::metadata::class_like::ClassLikeMetadata;
13use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
14use crate::metadata::constant::ConstantMetadata;
15use crate::metadata::enum_case::EnumCaseMetadata;
16use crate::metadata::function_like::FunctionLikeMetadata;
17use crate::metadata::property::PropertyMetadata;
18use crate::symbol::SymbolKind;
19use crate::ttype::atomic::TAtomic;
20use crate::ttype::atomic::object::TObject;
21use crate::ttype::union::TUnion;
22
23pub mod assertion;
24pub mod consts;
25pub mod context;
26pub mod diff;
27pub mod flags;
28pub mod identifier;
29pub mod issue;
30pub mod metadata;
31pub mod misc;
32pub mod populator;
33pub mod reference;
34pub mod scanner;
35pub mod symbol;
36pub mod ttype;
37pub mod visibility;
38
39mod utils;
40
41pub fn function_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
45 let lowered_id = interner.lowered(id);
46 codebase.function_likes.contains_key(&(StringIdentifier::empty(), lowered_id))
47}
48
49pub fn constant_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
54 let lowered_id = lower_constant_name(interner, id);
55 codebase.constants.contains_key(&lowered_id)
56}
57
58pub fn class_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
62 let lowered_id = interner.lowered(id);
63
64 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Class))
65}
66
67pub fn class_or_trait_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
71 let lowered_id = interner.lowered(id);
72
73 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Class | SymbolKind::Trait))
74}
75
76pub fn interface_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
80 let lowered_id = interner.lowered(id);
81
82 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Interface))
83}
84
85pub fn class_or_interface_exists(
89 codebase: &CodebaseMetadata,
90 interner: &ThreadedInterner,
91 id: &StringIdentifier,
92) -> bool {
93 let lowered_id = interner.lowered(id);
94
95 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Class | SymbolKind::Interface))
96}
97
98pub fn enum_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
102 let lowered_id = interner.lowered(id);
103
104 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Enum))
105}
106
107pub fn trait_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
111 let lowered_id = interner.lowered(id);
112
113 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Trait))
114}
115
116pub fn class_like_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
120 let lowered_id = interner.lowered(id);
121
122 matches!(
123 codebase.symbols.get_kind(&lowered_id),
124 Some(SymbolKind::Class | SymbolKind::Interface | SymbolKind::Enum | SymbolKind::Trait)
125 )
126}
127
128pub fn method_exists(
132 codebase: &CodebaseMetadata,
133 interner: &ThreadedInterner,
134 fqc_id: &StringIdentifier,
135 method_id: &StringIdentifier,
136) -> bool {
137 let lowered_fqc_id = interner.lowered(fqc_id);
138 let lowered_method_id = interner.lowered(method_id);
139
140 codebase
141 .class_likes
142 .get(&lowered_fqc_id)
143 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowered_method_id))
144}
145
146pub fn method_id_exists(
147 codebase: &CodebaseMetadata,
148 interner: &ThreadedInterner,
149 method_id: &MethodIdentifier,
150) -> bool {
151 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
152 let lowered_method_id = interner.lowered(method_id.get_method_name());
153
154 codebase.function_likes.contains_key(&(lowered_fqc_id, lowered_method_id))
155}
156
157pub fn is_method_abstract(
158 codebase: &CodebaseMetadata,
159 interner: &ThreadedInterner,
160 fqc_id: &StringIdentifier,
161 method_id: &StringIdentifier,
162) -> bool {
163 let lowered_fqc_id = interner.lowered(fqc_id);
164 let lowered_method_id = interner.lowered(method_id);
165
166 codebase
167 .function_likes
168 .get(&(lowered_fqc_id, lowered_method_id))
169 .and_then(|meta| meta.method_metadata.as_ref())
170 .is_some_and(|method| method.is_abstract)
171}
172
173pub fn is_method_static(
174 codebase: &CodebaseMetadata,
175 interner: &ThreadedInterner,
176 fqc_id: &StringIdentifier,
177 method_id: &StringIdentifier,
178) -> bool {
179 let lowered_fqc_id = interner.lowered(fqc_id);
180 let lowered_method_id = interner.lowered(method_id);
181
182 codebase
183 .function_likes
184 .get(&(lowered_fqc_id, lowered_method_id))
185 .and_then(|meta| meta.method_metadata.as_ref())
186 .is_some_and(|method| method.is_static)
187}
188
189pub fn is_method_final(
190 codebase: &CodebaseMetadata,
191 interner: &ThreadedInterner,
192 fqc_id: &StringIdentifier,
193 method_id: &StringIdentifier,
194) -> bool {
195 let lowered_fqc_id = interner.lowered(fqc_id);
196 let lowered_method_id = interner.lowered(method_id);
197
198 codebase
199 .function_likes
200 .get(&(lowered_fqc_id, lowered_method_id))
201 .and_then(|meta| meta.method_metadata.as_ref())
202 .is_some_and(|method| method.is_final)
203}
204
205pub fn property_exists(
209 codebase: &CodebaseMetadata,
210 interner: &ThreadedInterner,
211 fqc_id: &StringIdentifier,
212 property_id: &StringIdentifier,
213) -> bool {
214 let lowered_fqc_id = interner.lowered(fqc_id);
215
216 codebase.class_likes.get(&lowered_fqc_id).is_some_and(|meta| meta.appearing_property_ids.contains_key(property_id))
217}
218
219pub fn declaring_method_exists(
223 codebase: &CodebaseMetadata,
224 interner: &ThreadedInterner,
225 fqc_id: &StringIdentifier,
226 method_id: &StringIdentifier,
227) -> bool {
228 let lowered_fqc_id = interner.lowered(fqc_id);
229 let lowered_method_id = interner.lowered(method_id);
230
231 codebase
232 .class_likes
233 .get(&lowered_fqc_id)
234 .is_some_and(|meta| meta.declaring_method_ids.contains_key(&lowered_method_id))
235}
236
237pub fn declaring_property_exists(
241 codebase: &CodebaseMetadata,
242 interner: &ThreadedInterner,
243 fqc_id: &StringIdentifier,
244 property_id: &StringIdentifier,
245) -> bool {
246 let lowered_fqc_id = interner.lowered(fqc_id);
247
248 codebase.class_likes.get(&lowered_fqc_id).is_some_and(|meta| meta.properties.contains_key(property_id))
249}
250
251pub fn class_like_constant_or_enum_case_exists(
255 codebase: &CodebaseMetadata,
256 interner: &ThreadedInterner,
257 fqc_id: &StringIdentifier,
258 constant_id: &StringIdentifier,
259) -> bool {
260 let lowered_fqc_id = interner.lowered(fqc_id);
261
262 if let Some(meta) = codebase.class_likes.get(&lowered_fqc_id) {
263 return meta.constants.contains_key(constant_id) || meta.enum_cases.contains_key(constant_id);
264 }
265
266 false
267}
268
269pub fn get_function<'a>(
273 codebase: &'a CodebaseMetadata,
274 interner: &ThreadedInterner,
275 id: &StringIdentifier,
276) -> Option<&'a FunctionLikeMetadata> {
277 let lowered_id = interner.lowered(id);
278
279 codebase.function_likes.get(&(StringIdentifier::empty(), lowered_id))
280}
281
282pub fn get_closure<'a>(
286 codebase: &'a CodebaseMetadata,
287 interner: &ThreadedInterner,
288 position: &Position,
289) -> Option<&'a FunctionLikeMetadata> {
290 let source_id = position.source.value();
291 let closure_id = interner.intern(position.to_string());
292
293 codebase.function_likes.get(&(source_id, closure_id))
294}
295
296pub fn get_constant<'a>(
300 codebase: &'a CodebaseMetadata,
301 interner: &ThreadedInterner,
302 id: &StringIdentifier,
303) -> Option<&'a ConstantMetadata> {
304 let lowered_id = lower_constant_name(interner, id);
305
306 codebase.constants.get(&lowered_id)
307}
308
309pub fn get_class<'a>(
313 codebase: &'a CodebaseMetadata,
314 interner: &ThreadedInterner,
315 id: &StringIdentifier,
316) -> Option<&'a ClassLikeMetadata> {
317 let lowered_id = interner.lowered(id);
318
319 if class_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
320}
321
322pub fn get_interface<'a>(
326 codebase: &'a CodebaseMetadata,
327 interner: &ThreadedInterner,
328 id: &StringIdentifier,
329) -> Option<&'a ClassLikeMetadata> {
330 let lowered_id = interner.lowered(id);
331
332 if interface_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
333}
334
335pub fn get_enum<'a>(
339 codebase: &'a CodebaseMetadata,
340 interner: &ThreadedInterner,
341 id: &StringIdentifier,
342) -> Option<&'a ClassLikeMetadata> {
343 let lowered_id = interner.lowered(id);
344
345 if enum_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
346}
347
348pub fn get_trait<'a>(
352 codebase: &'a CodebaseMetadata,
353 interner: &ThreadedInterner,
354 id: &StringIdentifier,
355) -> Option<&'a ClassLikeMetadata> {
356 let lowered_id = interner.lowered(id);
357
358 if trait_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
359}
360
361pub fn get_anonymous_class_name(interner: &ThreadedInterner, span: Span) -> StringIdentifier {
362 interner.intern(format!(
363 "class@anonymous:{}-{}:{}",
364 span.start.source.0.value(),
365 span.start.offset,
366 span.end.offset,
367 ))
368}
369
370pub fn get_anonymous_class<'a>(
375 codebase: &'a CodebaseMetadata,
376 interner: &ThreadedInterner,
377 span: Span,
378) -> Option<&'a ClassLikeMetadata> {
379 let name = get_anonymous_class_name(interner, span);
380
381 if class_exists(codebase, interner, &name) { codebase.class_likes.get(&name) } else { None }
382}
383
384pub fn get_class_like<'a>(
388 codebase: &'a CodebaseMetadata,
389 interner: &ThreadedInterner,
390 id: &StringIdentifier,
391) -> Option<&'a ClassLikeMetadata> {
392 let lowered_id = interner.lowered(id);
393 codebase.class_likes.get(&lowered_id)
394}
395
396pub fn get_declaring_class_for_property(
397 codebase: &CodebaseMetadata,
398 interner: &ThreadedInterner,
399 fqc_id: &StringIdentifier,
400 property_id: &StringIdentifier,
401) -> Option<StringIdentifier> {
402 let lowered_fqc_id = interner.lowered(fqc_id);
403
404 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
405
406 class_like.declaring_property_ids.get(property_id).copied()
407}
408
409pub fn get_declaring_property<'a>(
414 codebase: &'a CodebaseMetadata,
415 interner: &ThreadedInterner,
416 fqc_id: &StringIdentifier,
417 property_id: &StringIdentifier,
418) -> Option<&'a PropertyMetadata> {
419 let declaring_fqc_id = get_declaring_class_for_property(codebase, interner, fqc_id, property_id)?;
420 let declaring_class_like = codebase.class_likes.get(&declaring_fqc_id)?;
421
422 declaring_class_like.properties.get(property_id)
423}
424
425pub fn get_method_id(fqc_id: &StringIdentifier, method_name_id: &StringIdentifier) -> MethodIdentifier {
426 MethodIdentifier::new(*fqc_id, *method_name_id)
427}
428
429pub fn get_declaring_method_id(
430 codebase: &CodebaseMetadata,
431 interner: &ThreadedInterner,
432 method_id: &MethodIdentifier,
433) -> MethodIdentifier {
434 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
435 let lowered_method_id = interner.lowered(method_id.get_method_name());
436
437 let Some(class_like_metadata) = codebase.class_likes.get(&lowered_fqc_id) else {
438 return *method_id;
440 };
441
442 let declaring_method_ids = class_like_metadata.get_declaring_method_ids();
443 if let Some(declaring_fqcn) = declaring_method_ids.get(&lowered_method_id)
444 && let Some(declaring_class_metadata) = codebase.class_likes.get(declaring_fqcn)
445 {
446 return MethodIdentifier::new(declaring_class_metadata.original_name, *method_id.get_method_name());
447 };
448
449 if class_like_metadata.is_abstract {
450 let overridden_method_ids = class_like_metadata.get_overridden_method_ids();
451 if let Some(overridden_classes) = overridden_method_ids.get(&lowered_method_id)
452 && let Some(first_class) = overridden_classes.iter().next()
453 && let Some(first_class_metadata) = codebase.class_likes.get(first_class)
454 {
455 return MethodIdentifier::new(first_class_metadata.original_name, *method_id.get_method_name());
456 }
457 }
458
459 *method_id
461}
462
463pub fn get_declaring_method<'a>(
470 codebase: &'a CodebaseMetadata,
471 interner: &ThreadedInterner,
472 fqc_id: &StringIdentifier,
473 method_name_id: &StringIdentifier,
474) -> Option<&'a FunctionLikeMetadata> {
475 let method_id = MethodIdentifier::new(interner.lowered(fqc_id), interner.lowered(method_name_id));
476 let declaring_method_id = get_declaring_method_id(codebase, interner, &method_id);
477
478 get_method(codebase, interner, declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
479}
480
481pub fn get_method_by_id<'a>(
482 codebase: &'a CodebaseMetadata,
483 interner: &ThreadedInterner,
484 method_id: &MethodIdentifier,
485) -> Option<&'a FunctionLikeMetadata> {
486 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
487 let lowered_method_id = interner.lowered(method_id.get_method_name());
488
489 codebase.function_likes.get(&(lowered_fqc_id, lowered_method_id))
490}
491
492pub fn get_method<'a>(
493 codebase: &'a CodebaseMetadata,
494 interner: &ThreadedInterner,
495 fqc_id: &StringIdentifier,
496 method_name_id: &StringIdentifier,
497) -> Option<&'a FunctionLikeMetadata> {
498 let lowered_fqc_id = interner.lowered(fqc_id);
499 let lowered_method_id = interner.lowered(method_name_id);
500
501 codebase.function_likes.get(&(lowered_fqc_id, lowered_method_id))
502}
503
504pub fn get_property<'a>(
509 codebase: &'a CodebaseMetadata,
510 interner: &ThreadedInterner,
511 fqc_id: &StringIdentifier,
512 property_id: &StringIdentifier,
513) -> Option<&'a PropertyMetadata> {
514 let lowered_fqc_id = interner.lowered(fqc_id);
515
516 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
517
518 class_like.properties.get(property_id)
519}
520
521#[derive(Debug, PartialEq)]
523pub enum ClassConstantOrEnumCase<'a> {
524 Constant(&'a ClassLikeConstantMetadata),
525 EnumCase(&'a EnumCaseMetadata),
526}
527
528pub fn get_class_like_constant_or_enum_case<'a>(
532 codebase: &'a CodebaseMetadata,
533 interner: &ThreadedInterner,
534 fqc_id: &StringIdentifier,
535 constant_id: &StringIdentifier,
536) -> Option<ClassConstantOrEnumCase<'a>> {
537 let lowered_fqc_id = interner.lowered(fqc_id);
538 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
539
540 if let Some(constant_meta) = class_like.constants.get(constant_id) {
541 return Some(ClassConstantOrEnumCase::Constant(constant_meta));
542 }
543
544 if let Some(enum_case_meta) = class_like.enum_cases.get(constant_id) {
545 return Some(ClassConstantOrEnumCase::EnumCase(enum_case_meta));
546 }
547
548 None
549}
550
551pub fn is_instance_of(
556 codebase: &CodebaseMetadata,
557 interner: &ThreadedInterner,
558 child: &StringIdentifier,
559 parent: &StringIdentifier,
560) -> bool {
561 let lowered_child = interner.lowered(child);
562 let lowered_parent = interner.lowered(parent);
563
564 if lowered_child == lowered_parent {
565 return true;
566 }
567
568 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
569 return false;
570 };
571
572 child_meta.has_parent(&lowered_parent)
573}
574
575pub fn inherits_class(
576 codebase: &CodebaseMetadata,
577 interner: &ThreadedInterner,
578 child: &StringIdentifier,
579 parent: &StringIdentifier,
580) -> bool {
581 let lowered_child = interner.lowered(child);
582 let lowered_parent = interner.lowered(parent);
583
584 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
585 return false;
586 };
587
588 child_meta.all_parent_classes.contains(&lowered_parent)
589}
590
591pub fn directly_inherits_class(
592 codebase: &CodebaseMetadata,
593 interner: &ThreadedInterner,
594 child: &StringIdentifier,
595 parent: &StringIdentifier,
596) -> bool {
597 let lowered_child = interner.lowered(child);
598 let lowered_parent = interner.lowered(parent);
599
600 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
601 return false;
602 };
603
604 child_meta.direct_parent_class.as_ref().is_some_and(|parent_class| parent_class == &lowered_parent)
605}
606
607pub fn inherits_interface(
608 codebase: &CodebaseMetadata,
609 interner: &ThreadedInterner,
610 child: &StringIdentifier,
611 parent: &StringIdentifier,
612) -> bool {
613 let lowered_child = interner.lowered(child);
614 let lowered_parent = interner.lowered(parent);
615
616 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
617 return false;
618 };
619
620 child_meta.all_parent_interfaces.contains(&lowered_parent)
621}
622
623pub fn directly_inherits_interface(
624 codebase: &CodebaseMetadata,
625 interner: &ThreadedInterner,
626 child: &StringIdentifier,
627 parent: &StringIdentifier,
628) -> bool {
629 let lowered_child = interner.lowered(child);
630 let lowered_parent = interner.lowered(parent);
631
632 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
633 return false;
634 };
635
636 child_meta.direct_parent_interfaces.contains(&lowered_parent)
637}
638
639pub fn uses_trait(
640 codebase: &CodebaseMetadata,
641 interner: &ThreadedInterner,
642 child: &StringIdentifier,
643 trait_name: &StringIdentifier,
644) -> bool {
645 let lowered_child = interner.lowered(child);
646 let lowered_trait_name = interner.lowered(trait_name);
647
648 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
649 return false;
650 };
651
652 child_meta.used_traits.contains(&lowered_trait_name)
653}
654
655#[inline]
659pub fn get_all_descendants(
660 codebase: &CodebaseMetadata,
661 interner: &ThreadedInterner,
662 class_like_name: &StringIdentifier,
663) -> HashSet<StringIdentifier> {
664 let fqc_id = interner.lowered(class_like_name);
665
666 let mut all_descendants = HashSet::default();
668 let mut queue = vec![&fqc_id];
669 let mut visited = HashSet::default();
670 visited.insert(&fqc_id); while let Some(current_name) = queue.pop() {
673 if let Some(direct_descendants) = codebase.direct_classlike_descendants.get(current_name) {
674 for descendant in direct_descendants {
675 if visited.insert(descendant) {
676 all_descendants.insert(*descendant);
678 queue.push(descendant); }
680 }
681 }
682 }
683 all_descendants
684}
685
686pub fn is_method_overriding(
693 codebase: &CodebaseMetadata,
694 interner: &ThreadedInterner,
695 fqc_id: &StringIdentifier,
696 method_name: &StringIdentifier,
697) -> bool {
698 let lowered_method_name = interner.lowered(method_name);
699
700 get_class_like(codebase, interner, fqc_id)
701 .is_some_and(|metadata| metadata.overridden_method_ids.contains_key(&lowered_method_name))
702}
703
704#[inline]
707pub fn get_class_constant_type<'a>(
708 codebase: &'a CodebaseMetadata,
709 interner: &ThreadedInterner,
710 fq_class_name: &StringIdentifier,
711 constant_name: &StringIdentifier,
712) -> Option<Cow<'a, TUnion>> {
713 let class_metadata = get_class_like(codebase, interner, fq_class_name)?;
714
715 if class_metadata.kind.is_enum() && class_metadata.enum_cases.contains_key(constant_name) {
716 return Some(Cow::Owned(TUnion::new(vec![TAtomic::Object(TObject::new_enum_case(
717 class_metadata.original_name,
718 *constant_name,
719 ))])));
720 }
721
722 let constant_metadata = class_metadata.constants.get(constant_name)?;
724
725 if let Some(type_metadata) = constant_metadata.type_metadata.as_ref() {
727 return Some(Cow::Borrowed(&type_metadata.type_union));
731 }
732
733 constant_metadata.inferred_type.as_ref().map(|atomic_type| {
735 Cow::Owned(TUnion::new(vec![atomic_type.clone()]))
737 })
738}
739
740fn lower_constant_name(interner: &ThreadedInterner, name: &StringIdentifier) -> StringIdentifier {
745 let name_str = interner.lookup(name);
746 if !name_str.contains('\\') {
747 return *name;
748 }
749
750 let mut parts: Vec<_> = name_str.split('\\').map(str::to_owned).collect();
751 let total_parts = parts.len();
752 if total_parts > 1 {
753 parts = parts
754 .into_iter()
755 .enumerate()
756 .map(|(i, part)| if i < total_parts - 1 { part.to_ascii_lowercase() } else { part })
757 .collect::<Vec<_>>();
758 }
759
760 interner.intern(parts.join("\\"))
761}