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 method_exists(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
134 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
135 let lowercase_method_name = ascii_lowercase_atom(method_name);
136
137 codebase
138 .class_likes
139 .get(&lowercase_fqcn)
140 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method_name))
141}
142
143pub fn method_identifier_exists(codebase: &CodebaseMetadata, method_identifier: &MethodIdentifier) -> bool {
144 let lowercase_fqcn = ascii_lowercase_atom(method_identifier.get_class_name());
145 let lowercase_method_name = ascii_lowercase_atom(method_identifier.get_method_name());
146
147 let method_identifier = (lowercase_fqcn, lowercase_method_name);
148
149 codebase.function_likes.contains_key(&method_identifier)
150}
151
152pub fn is_method_abstract(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
153 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
154 let lowercase_method_name = ascii_lowercase_atom(method_name);
155
156 let method_identifier = (lowercase_fqcn, lowercase_method_name);
157
158 codebase
159 .function_likes
160 .get(&method_identifier)
161 .and_then(|meta| meta.method_metadata.as_ref())
162 .is_some_and(|method| method.is_abstract)
163}
164
165pub fn is_method_static(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
166 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
167 let lowercase_method_name = ascii_lowercase_atom(method_name);
168
169 let method_identifier = (lowercase_fqcn, lowercase_method_name);
170
171 codebase
172 .function_likes
173 .get(&method_identifier)
174 .and_then(|meta| meta.method_metadata.as_ref())
175 .is_some_and(|method| method.is_static)
176}
177
178pub fn is_method_final(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
179 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
180 let lowercase_method_name = ascii_lowercase_atom(method_name);
181
182 let method_identifier = (lowercase_fqcn, lowercase_method_name);
183
184 codebase
185 .function_likes
186 .get(&method_identifier)
187 .and_then(|meta| meta.method_metadata.as_ref())
188 .is_some_and(|method| method.is_final)
189}
190
191pub fn property_exists(codebase: &CodebaseMetadata, fqcn: &str, property_name: &str) -> bool {
195 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
196
197 codebase
198 .class_likes
199 .get(&lowercase_fqcn)
200 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&atom(property_name)))
201}
202
203pub fn declaring_method_exists(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
207 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
208 let lowercase_method_name = ascii_lowercase_atom(method_name);
209
210 codebase
211 .class_likes
212 .get(&lowercase_fqcn)
213 .is_some_and(|meta| meta.declaring_method_ids.contains_key(&lowercase_method_name))
214}
215
216pub fn declaring_property_exists(codebase: &CodebaseMetadata, fqcn: &str, property_name: &str) -> bool {
220 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
221 let property_name = atom(property_name);
222
223 codebase.class_likes.get(&lowercase_fqcn).is_some_and(|meta| meta.properties.contains_key(&property_name))
224}
225
226pub fn class_like_constant_or_enum_case_exists(codebase: &CodebaseMetadata, fqcn: &str, constant_name: &str) -> bool {
230 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
231 let constant_name = atom(constant_name);
232
233 if let Some(meta) = codebase.class_likes.get(&lowercase_fqcn) {
234 return meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name);
235 }
236
237 false
238}
239
240pub fn get_function<'a>(codebase: &'a CodebaseMetadata, function_name: &str) -> Option<&'a FunctionLikeMetadata> {
244 let lowercase_function_name = ascii_lowercase_atom(function_name);
245 let function_identifier = (empty_atom(), lowercase_function_name);
246
247 codebase.function_likes.get(&function_identifier)
248}
249
250pub fn get_closure<'a>(
254 codebase: &'a CodebaseMetadata,
255 file_id: &FileId,
256 position: &Position,
257) -> Option<&'a FunctionLikeMetadata> {
258 let file_ref = u64_atom(file_id.as_u64());
259 let closure_ref = u32_atom(position.offset);
260 let identifier = (file_ref, closure_ref);
261
262 codebase.function_likes.get(&identifier)
263}
264
265pub fn get_constant<'a>(codebase: &'a CodebaseMetadata, constant_name: &str) -> Option<&'a ConstantMetadata> {
269 let lowercase_constant_name = ascii_lowercase_constant_name_atom(constant_name);
270
271 codebase.constants.get(&lowercase_constant_name)
272}
273
274pub fn get_class<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
278 let lowercase_name = ascii_lowercase_atom(name);
279
280 if codebase.symbols.contains_class(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
281}
282
283pub fn get_interface<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
287 let lowercase_name = ascii_lowercase_atom(name);
288
289 if codebase.symbols.contains_interface(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
290}
291
292pub fn get_enum<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
296 let lowercase_name = ascii_lowercase_atom(name);
297
298 if codebase.symbols.contains_enum(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
299}
300
301pub fn get_trait<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
305 let lowercase_name = ascii_lowercase_atom(name);
306
307 if codebase.symbols.contains_trait(&lowercase_name) { codebase.class_likes.get(&lowercase_name) } else { None }
308}
309
310pub fn get_anonymous_class_name(span: Span) -> Atom {
311 use std::io::Write;
312
313 let mut buffer = [0u8; 64];
316
317 let mut writer = &mut buffer[..];
320
321 unsafe {
323 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset).unwrap_unchecked()
324 };
325
326 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
329
330 atom(
331 unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() },
334 )
335}
336
337pub fn get_anonymous_class(codebase: &CodebaseMetadata, span: Span) -> Option<&ClassLikeMetadata> {
342 let name = get_anonymous_class_name(span);
343
344 if class_exists(codebase, &name) { codebase.class_likes.get(&name) } else { None }
345}
346
347pub fn get_class_like<'a>(codebase: &'a CodebaseMetadata, name: &str) -> Option<&'a ClassLikeMetadata> {
351 let lowercase_name = ascii_lowercase_atom(name);
352
353 codebase.class_likes.get(&lowercase_name)
354}
355
356pub fn get_declaring_class_for_property(codebase: &CodebaseMetadata, fqcn: &str, property_name: &str) -> Option<Atom> {
357 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
358 let property_name = atom(property_name);
359
360 codebase.class_likes.get(&lowercase_fqcn)?.declaring_property_ids.get(&property_name).copied()
361}
362
363pub fn get_declaring_property<'a>(
368 codebase: &'a CodebaseMetadata,
369 fqcn: &str,
370 property_name: &str,
371) -> Option<&'a PropertyMetadata> {
372 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
373 let property_name = atom(property_name);
374
375 let declaring_fqcn = codebase.class_likes.get(&lowercase_fqcn)?.declaring_property_ids.get(&property_name)?;
376
377 codebase.class_likes.get(declaring_fqcn)?.properties.get(&property_name)
378}
379
380pub fn get_method_identifier(fqcn: &str, method_name: &str) -> MethodIdentifier {
381 MethodIdentifier::new(atom(fqcn), atom(method_name))
382}
383
384pub fn get_declaring_method_identifier(
385 codebase: &CodebaseMetadata,
386 method_identifier: &MethodIdentifier,
387) -> MethodIdentifier {
388 let lowercase_fqcn = ascii_lowercase_atom(method_identifier.get_class_name());
389 let lowercase_method_name = ascii_lowercase_atom(method_identifier.get_method_name());
390
391 let Some(class_like_metadata) = codebase.class_likes.get(&lowercase_fqcn) else {
392 return *method_identifier;
394 };
395
396 if let Some(declaring_fqcn) = class_like_metadata.declaring_method_ids.get(&lowercase_method_name)
397 && let Some(declaring_class_metadata) = codebase.class_likes.get(declaring_fqcn)
398 {
399 return MethodIdentifier::new(declaring_class_metadata.original_name, *method_identifier.get_method_name());
400 };
401
402 if class_like_metadata.flags.is_abstract()
403 && let Some(overridden_classes) = class_like_metadata.overridden_method_ids.get(&lowercase_method_name)
404 && let Some(first_class) = overridden_classes.iter().next()
405 && let Some(first_class_metadata) = codebase.class_likes.get(first_class)
406 {
407 return MethodIdentifier::new(first_class_metadata.original_name, *method_identifier.get_method_name());
408 }
409
410 *method_identifier
412}
413
414pub fn get_declaring_method<'a>(
421 codebase: &'a CodebaseMetadata,
422 fqcn: &str,
423 method_name: &str,
424) -> Option<&'a FunctionLikeMetadata> {
425 let method_id = MethodIdentifier::new(atom(fqcn), atom(method_name));
426 let declaring_method_id = get_declaring_method_identifier(codebase, &method_id);
427
428 get_method(codebase, declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
429}
430
431pub fn get_method_by_id<'a>(
432 codebase: &'a CodebaseMetadata,
433 method_identifier: &MethodIdentifier,
434) -> Option<&'a FunctionLikeMetadata> {
435 let lowercase_fqcn = ascii_lowercase_atom(method_identifier.get_class_name());
436 let lowercase_method_name = ascii_lowercase_atom(method_identifier.get_method_name());
437
438 let function_identifier = (lowercase_fqcn, lowercase_method_name);
439
440 codebase.function_likes.get(&function_identifier)
441}
442
443pub fn get_method<'a>(
444 codebase: &'a CodebaseMetadata,
445 fqcn: &str,
446 method_name: &str,
447) -> Option<&'a FunctionLikeMetadata> {
448 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
449 let lowercase_method_name = ascii_lowercase_atom(method_name);
450 let function_like_identifier = (lowercase_fqcn, lowercase_method_name);
451
452 codebase.function_likes.get(&function_like_identifier)
453}
454
455pub fn get_property<'a>(
460 codebase: &'a CodebaseMetadata,
461 fqcn: &str,
462 property_name: &str,
463) -> Option<&'a PropertyMetadata> {
464 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
465 let property_name = atom(property_name);
466
467 codebase.class_likes.get(&lowercase_fqcn)?.properties.get(&property_name)
468}
469
470#[derive(Debug, PartialEq)]
472pub enum ClassConstantOrEnumCase<'a> {
473 Constant(&'a ClassLikeConstantMetadata),
474 EnumCase(&'a EnumCaseMetadata),
475}
476
477pub fn get_class_like_constant_or_enum_case<'a>(
481 codebase: &'a CodebaseMetadata,
482 fqcn: &str,
483 constant_name: &str,
484) -> Option<ClassConstantOrEnumCase<'a>> {
485 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
486 let constant_name = atom(constant_name);
487
488 let class_like = codebase.class_likes.get(&lowercase_fqcn)?;
489
490 if let Some(constant_meta) = class_like.constants.get(&constant_name) {
491 return Some(ClassConstantOrEnumCase::Constant(constant_meta));
492 }
493
494 if let Some(enum_case_meta) = class_like.enum_cases.get(&constant_name) {
495 return Some(ClassConstantOrEnumCase::EnumCase(enum_case_meta));
496 }
497
498 None
499}
500
501pub fn is_instance_of(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
506 if child_name == parent_name {
507 return true;
508 }
509
510 let lowercase_child_name = ascii_lowercase_atom(child_name);
511 let lowercase_parent_name = ascii_lowercase_atom(parent_name);
512
513 if lowercase_child_name == lowercase_parent_name {
514 return true;
515 }
516
517 let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
518 return false;
519 };
520
521 child_meta.has_parent(&lowercase_parent_name)
522}
523
524pub fn inherits_class(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
525 let lowercase_child_name = ascii_lowercase_atom(child_name);
526 let lowercase_parent_name = ascii_lowercase_atom(parent_name);
527
528 let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
529 return false;
530 };
531
532 child_meta.all_parent_classes.contains(&lowercase_parent_name)
533}
534
535pub fn directly_inherits_class(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
536 let lowercase_child_name = ascii_lowercase_atom(child_name);
537 let lowercase_parent_name = ascii_lowercase_atom(parent_name);
538
539 let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
540 return false;
541 };
542
543 child_meta.direct_parent_class.as_ref().is_some_and(|parent_class| parent_class == &lowercase_parent_name)
544}
545
546pub fn inherits_interface(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
547 let lowercase_child_name = ascii_lowercase_atom(child_name);
548 let lowercase_parent_name = ascii_lowercase_atom(parent_name);
549
550 let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
551 return false;
552 };
553
554 child_meta.all_parent_interfaces.contains(&lowercase_parent_name)
555}
556
557pub fn directly_inherits_interface(codebase: &CodebaseMetadata, child_name: &str, parent_name: &str) -> bool {
558 let lowercase_child_name = ascii_lowercase_atom(child_name);
559 let lowercase_parent_name = ascii_lowercase_atom(parent_name);
560
561 let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
562 return false;
563 };
564
565 child_meta.direct_parent_interfaces.contains(&lowercase_parent_name)
566}
567
568pub fn uses_trait(codebase: &CodebaseMetadata, child_name: &str, trait_name: &str) -> bool {
569 let lowercase_child_name = ascii_lowercase_atom(child_name);
570 let lowercase_trait_name = ascii_lowercase_atom(trait_name);
571
572 let Some(child_meta) = codebase.class_likes.get(&lowercase_child_name) else {
573 return false;
574 };
575
576 child_meta.used_traits.contains(&lowercase_trait_name)
577}
578
579#[inline]
583pub fn get_all_descendants(codebase: &CodebaseMetadata, fqcn: &str) -> AtomSet {
584 let lowercase_fqcn = ascii_lowercase_atom(fqcn);
585
586 let mut all_descendants = AtomSet::default();
588 let mut queue = vec![&lowercase_fqcn];
589 let mut visited = AtomSet::default();
590 visited.insert(lowercase_fqcn); while let Some(current_name) = queue.pop() {
593 if let Some(direct_descendants) = codebase.direct_classlike_descendants.get(current_name) {
594 for descendant in direct_descendants {
595 if visited.insert(*descendant) {
596 all_descendants.insert(*descendant);
598 queue.push(descendant); }
600 }
601 }
602 }
603
604 all_descendants
605}
606
607pub fn is_method_overriding(codebase: &CodebaseMetadata, fqcn: &str, method_name: &str) -> bool {
614 let lowercase_method_name = ascii_lowercase_atom(method_name);
615
616 get_class_like(codebase, fqcn)
617 .is_some_and(|metadata| metadata.overridden_method_ids.contains_key(&lowercase_method_name))
618}
619
620pub fn get_function_like_thrown_types<'a>(
621 codebase: &'a CodebaseMetadata,
622 class_like: Option<&'a ClassLikeMetadata>,
623 function_like: &'a FunctionLikeMetadata,
624) -> &'a [TypeMetadata] {
625 if !function_like.thrown_types.is_empty() {
626 return function_like.thrown_types.as_slice();
627 }
628
629 if !function_like.kind.is_method() {
630 return &[];
631 }
632
633 let Some(class_like) = class_like else {
634 return &[];
635 };
636
637 let Some(method_name) = function_like.name.as_ref() else {
638 return &[];
639 };
640
641 for parent_class_name_id in class_like.overridden_method_ids.get(method_name).into_iter().flatten() {
642 let Some(parent_class) = codebase.class_likes.get(parent_class_name_id) else {
643 continue;
644 };
645
646 let parent_method_id = (*parent_class_name_id, *method_name);
647 if let Some(parent_method) = codebase.function_likes.get(&parent_method_id) {
648 let thrown = get_function_like_thrown_types(codebase, Some(parent_class), parent_method);
649 if !thrown.is_empty() {
650 return thrown;
651 }
652 }
653 }
654
655 &[]
656}
657
658#[inline]
661pub fn get_class_constant_type<'a>(
662 codebase: &'a CodebaseMetadata,
663 fq_class_name: &str,
664 constant_name: &str,
665) -> Option<Cow<'a, TUnion>> {
666 let class_metadata = get_class_like(codebase, fq_class_name)?;
667 let constant_name = atom(constant_name);
668
669 if class_metadata.kind.is_enum() && class_metadata.enum_cases.contains_key(&constant_name) {
670 let atomic = TAtomic::Object(TObject::new_enum_case(class_metadata.original_name, constant_name));
671
672 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
673 }
674
675 let constant_metadata = class_metadata.constants.get(&constant_name)?;
677
678 if let Some(type_metadata) = constant_metadata.type_metadata.as_ref() {
680 return Some(Cow::Borrowed(&type_metadata.type_union));
684 }
685
686 constant_metadata.inferred_type.as_ref().map(|atomic_type| {
688 Cow::Owned(TUnion::from_atomic(atomic_type.clone()))
690 })
691}