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