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 file_id = interner.intern(position.file_id.to_string());
291 let closure_id = interner.intern(position.to_string());
292
293 codebase.function_likes.get(&(file_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!("class@anonymous:{}-{}:{}", span.start.file_id, span.start.offset, span.end.offset,))
363}
364
365pub fn get_anonymous_class<'a>(
370 codebase: &'a CodebaseMetadata,
371 interner: &ThreadedInterner,
372 span: Span,
373) -> Option<&'a ClassLikeMetadata> {
374 let name = get_anonymous_class_name(interner, span);
375
376 if class_exists(codebase, interner, &name) { codebase.class_likes.get(&name) } else { None }
377}
378
379pub fn get_class_like<'a>(
383 codebase: &'a CodebaseMetadata,
384 interner: &ThreadedInterner,
385 id: &StringIdentifier,
386) -> Option<&'a ClassLikeMetadata> {
387 let lowered_id = interner.lowered(id);
388 codebase.class_likes.get(&lowered_id)
389}
390
391pub fn get_declaring_class_for_property(
392 codebase: &CodebaseMetadata,
393 interner: &ThreadedInterner,
394 fqc_id: &StringIdentifier,
395 property_id: &StringIdentifier,
396) -> Option<StringIdentifier> {
397 let lowered_fqc_id = interner.lowered(fqc_id);
398
399 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
400
401 class_like.declaring_property_ids.get(property_id).copied()
402}
403
404pub fn get_declaring_property<'a>(
409 codebase: &'a CodebaseMetadata,
410 interner: &ThreadedInterner,
411 fqc_id: &StringIdentifier,
412 property_id: &StringIdentifier,
413) -> Option<&'a PropertyMetadata> {
414 let declaring_fqc_id = get_declaring_class_for_property(codebase, interner, fqc_id, property_id)?;
415 let declaring_class_like = codebase.class_likes.get(&declaring_fqc_id)?;
416
417 declaring_class_like.properties.get(property_id)
418}
419
420pub fn get_method_id(fqc_id: &StringIdentifier, method_name_id: &StringIdentifier) -> MethodIdentifier {
421 MethodIdentifier::new(*fqc_id, *method_name_id)
422}
423
424pub fn get_declaring_method_id(
425 codebase: &CodebaseMetadata,
426 interner: &ThreadedInterner,
427 method_id: &MethodIdentifier,
428) -> MethodIdentifier {
429 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
430 let lowered_method_id = interner.lowered(method_id.get_method_name());
431
432 let Some(class_like_metadata) = codebase.class_likes.get(&lowered_fqc_id) else {
433 return *method_id;
435 };
436
437 let declaring_method_ids = class_like_metadata.get_declaring_method_ids();
438 if let Some(declaring_fqcn) = declaring_method_ids.get(&lowered_method_id)
439 && let Some(declaring_class_metadata) = codebase.class_likes.get(declaring_fqcn)
440 {
441 return MethodIdentifier::new(declaring_class_metadata.original_name, *method_id.get_method_name());
442 };
443
444 if class_like_metadata.flags.is_abstract() {
445 let overridden_method_ids = class_like_metadata.get_overridden_method_ids();
446 if let Some(overridden_classes) = overridden_method_ids.get(&lowered_method_id)
447 && let Some(first_class) = overridden_classes.iter().next()
448 && let Some(first_class_metadata) = codebase.class_likes.get(first_class)
449 {
450 return MethodIdentifier::new(first_class_metadata.original_name, *method_id.get_method_name());
451 }
452 }
453
454 *method_id
456}
457
458pub fn get_declaring_method<'a>(
465 codebase: &'a CodebaseMetadata,
466 interner: &ThreadedInterner,
467 fqc_id: &StringIdentifier,
468 method_name_id: &StringIdentifier,
469) -> Option<&'a FunctionLikeMetadata> {
470 let method_id = MethodIdentifier::new(interner.lowered(fqc_id), interner.lowered(method_name_id));
471 let declaring_method_id = get_declaring_method_id(codebase, interner, &method_id);
472
473 get_method(codebase, interner, declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
474}
475
476pub fn get_method_by_id<'a>(
477 codebase: &'a CodebaseMetadata,
478 interner: &ThreadedInterner,
479 method_id: &MethodIdentifier,
480) -> Option<&'a FunctionLikeMetadata> {
481 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
482 let lowered_method_id = interner.lowered(method_id.get_method_name());
483
484 codebase.function_likes.get(&(lowered_fqc_id, lowered_method_id))
485}
486
487pub fn get_method<'a>(
488 codebase: &'a CodebaseMetadata,
489 interner: &ThreadedInterner,
490 fqc_id: &StringIdentifier,
491 method_name_id: &StringIdentifier,
492) -> Option<&'a FunctionLikeMetadata> {
493 let lowered_fqc_id = interner.lowered(fqc_id);
494 let lowered_method_id = interner.lowered(method_name_id);
495
496 codebase.function_likes.get(&(lowered_fqc_id, lowered_method_id))
497}
498
499pub fn get_property<'a>(
504 codebase: &'a CodebaseMetadata,
505 interner: &ThreadedInterner,
506 fqc_id: &StringIdentifier,
507 property_id: &StringIdentifier,
508) -> Option<&'a PropertyMetadata> {
509 let lowered_fqc_id = interner.lowered(fqc_id);
510
511 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
512
513 class_like.properties.get(property_id)
514}
515
516#[derive(Debug, PartialEq)]
518pub enum ClassConstantOrEnumCase<'a> {
519 Constant(&'a ClassLikeConstantMetadata),
520 EnumCase(&'a EnumCaseMetadata),
521}
522
523pub fn get_class_like_constant_or_enum_case<'a>(
527 codebase: &'a CodebaseMetadata,
528 interner: &ThreadedInterner,
529 fqc_id: &StringIdentifier,
530 constant_id: &StringIdentifier,
531) -> Option<ClassConstantOrEnumCase<'a>> {
532 let lowered_fqc_id = interner.lowered(fqc_id);
533 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
534
535 if let Some(constant_meta) = class_like.constants.get(constant_id) {
536 return Some(ClassConstantOrEnumCase::Constant(constant_meta));
537 }
538
539 if let Some(enum_case_meta) = class_like.enum_cases.get(constant_id) {
540 return Some(ClassConstantOrEnumCase::EnumCase(enum_case_meta));
541 }
542
543 None
544}
545
546pub fn is_instance_of(
551 codebase: &CodebaseMetadata,
552 interner: &ThreadedInterner,
553 child: &StringIdentifier,
554 parent: &StringIdentifier,
555) -> bool {
556 let lowered_child = interner.lowered(child);
557 let lowered_parent = interner.lowered(parent);
558
559 if lowered_child == lowered_parent {
560 return true;
561 }
562
563 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
564 return false;
565 };
566
567 child_meta.has_parent(&lowered_parent)
568}
569
570pub fn inherits_class(
571 codebase: &CodebaseMetadata,
572 interner: &ThreadedInterner,
573 child: &StringIdentifier,
574 parent: &StringIdentifier,
575) -> bool {
576 let lowered_child = interner.lowered(child);
577 let lowered_parent = interner.lowered(parent);
578
579 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
580 return false;
581 };
582
583 child_meta.all_parent_classes.contains(&lowered_parent)
584}
585
586pub fn directly_inherits_class(
587 codebase: &CodebaseMetadata,
588 interner: &ThreadedInterner,
589 child: &StringIdentifier,
590 parent: &StringIdentifier,
591) -> bool {
592 let lowered_child = interner.lowered(child);
593 let lowered_parent = interner.lowered(parent);
594
595 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
596 return false;
597 };
598
599 child_meta.direct_parent_class.as_ref().is_some_and(|parent_class| parent_class == &lowered_parent)
600}
601
602pub fn inherits_interface(
603 codebase: &CodebaseMetadata,
604 interner: &ThreadedInterner,
605 child: &StringIdentifier,
606 parent: &StringIdentifier,
607) -> bool {
608 let lowered_child = interner.lowered(child);
609 let lowered_parent = interner.lowered(parent);
610
611 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
612 return false;
613 };
614
615 child_meta.all_parent_interfaces.contains(&lowered_parent)
616}
617
618pub fn directly_inherits_interface(
619 codebase: &CodebaseMetadata,
620 interner: &ThreadedInterner,
621 child: &StringIdentifier,
622 parent: &StringIdentifier,
623) -> bool {
624 let lowered_child = interner.lowered(child);
625 let lowered_parent = interner.lowered(parent);
626
627 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
628 return false;
629 };
630
631 child_meta.direct_parent_interfaces.contains(&lowered_parent)
632}
633
634pub fn uses_trait(
635 codebase: &CodebaseMetadata,
636 interner: &ThreadedInterner,
637 child: &StringIdentifier,
638 trait_name: &StringIdentifier,
639) -> bool {
640 let lowered_child = interner.lowered(child);
641 let lowered_trait_name = interner.lowered(trait_name);
642
643 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
644 return false;
645 };
646
647 child_meta.used_traits.contains(&lowered_trait_name)
648}
649
650#[inline]
654pub fn get_all_descendants(
655 codebase: &CodebaseMetadata,
656 interner: &ThreadedInterner,
657 class_like_name: &StringIdentifier,
658) -> HashSet<StringIdentifier> {
659 let fqc_id = interner.lowered(class_like_name);
660
661 let mut all_descendants = HashSet::default();
663 let mut queue = vec![&fqc_id];
664 let mut visited = HashSet::default();
665 visited.insert(&fqc_id); while let Some(current_name) = queue.pop() {
668 if let Some(direct_descendants) = codebase.direct_classlike_descendants.get(current_name) {
669 for descendant in direct_descendants {
670 if visited.insert(descendant) {
671 all_descendants.insert(*descendant);
673 queue.push(descendant); }
675 }
676 }
677 }
678 all_descendants
679}
680
681pub fn is_method_overriding(
688 codebase: &CodebaseMetadata,
689 interner: &ThreadedInterner,
690 fqc_id: &StringIdentifier,
691 method_name: &StringIdentifier,
692) -> bool {
693 let lowered_method_name = interner.lowered(method_name);
694
695 get_class_like(codebase, interner, fqc_id)
696 .is_some_and(|metadata| metadata.overridden_method_ids.contains_key(&lowered_method_name))
697}
698
699#[inline]
702pub fn get_class_constant_type<'a>(
703 codebase: &'a CodebaseMetadata,
704 interner: &ThreadedInterner,
705 fq_class_name: &StringIdentifier,
706 constant_name: &StringIdentifier,
707) -> Option<Cow<'a, TUnion>> {
708 let class_metadata = get_class_like(codebase, interner, fq_class_name)?;
709
710 if class_metadata.kind.is_enum() && class_metadata.enum_cases.contains_key(constant_name) {
711 return Some(Cow::Owned(TUnion::new(vec![TAtomic::Object(TObject::new_enum_case(
712 class_metadata.original_name,
713 *constant_name,
714 ))])));
715 }
716
717 let constant_metadata = class_metadata.constants.get(constant_name)?;
719
720 if let Some(type_metadata) = constant_metadata.type_metadata.as_ref() {
722 return Some(Cow::Borrowed(&type_metadata.type_union));
726 }
727
728 constant_metadata.inferred_type.as_ref().map(|atomic_type| {
730 Cow::Owned(TUnion::new(vec![atomic_type.clone()]))
732 })
733}
734
735fn lower_constant_name(interner: &ThreadedInterner, name: &StringIdentifier) -> StringIdentifier {
740 let name_str = interner.lookup(name);
741 if !name_str.contains('\\') {
742 return *name;
743 }
744
745 let mut parts: Vec<_> = name_str.split('\\').map(str::to_owned).collect();
746 let total_parts = parts.len();
747 if total_parts > 1 {
748 parts = parts
749 .into_iter()
750 .enumerate()
751 .map(|(i, part)| if i < total_parts - 1 { part.to_ascii_lowercase() } else { part })
752 .collect::<Vec<_>>();
753 }
754
755 interner.intern(parts.join("\\"))
756}