1use std::borrow::Cow;
2use std::collections::hash_map::Entry;
3
4use ahash::HashMap;
5use ahash::HashSet;
6use serde::Deserialize;
7use serde::Serialize;
8
9use mago_atom::Atom;
10use mago_atom::AtomMap;
11use mago_atom::AtomSet;
12use mago_atom::ascii_lowercase_atom;
13use mago_atom::ascii_lowercase_constant_name_atom;
14use mago_atom::atom;
15use mago_atom::empty_atom;
16use mago_atom::u32_atom;
17use mago_atom::u64_atom;
18use mago_database::file::FileId;
19use mago_reporting::IssueCollection;
20use mago_span::Position;
21
22use crate::identifier::method::MethodIdentifier;
23use crate::metadata::class_like::ClassLikeMetadata;
24use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
25use crate::metadata::constant::ConstantMetadata;
26use crate::metadata::enum_case::EnumCaseMetadata;
27use crate::metadata::function_like::FunctionLikeMetadata;
28use crate::metadata::property::PropertyMetadata;
29use crate::metadata::ttype::TypeMetadata;
30use crate::signature::FileSignature;
31use crate::symbol::SymbolKind;
32use crate::symbol::Symbols;
33use crate::ttype::atomic::TAtomic;
34use crate::ttype::atomic::object::TObject;
35use crate::ttype::union::TUnion;
36use crate::visibility::Visibility;
37
38pub mod attribute;
39pub mod class_like;
40pub mod class_like_constant;
41pub mod constant;
42pub mod enum_case;
43pub mod flags;
44pub mod function_like;
45pub mod parameter;
46pub mod property;
47pub mod property_hook;
48pub mod ttype;
49
50#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
56#[non_exhaustive]
57pub struct CodebaseMetadata {
58 pub infer_types_from_usage: bool,
60 pub class_likes: AtomMap<ClassLikeMetadata>,
62 pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
65 pub symbols: Symbols,
67 pub constants: AtomMap<ConstantMetadata>,
69 pub all_class_like_descendants: AtomMap<AtomSet>,
71 pub direct_classlike_descendants: AtomMap<AtomSet>,
73 pub safe_symbols: AtomSet,
75 pub safe_symbol_members: HashSet<(Atom, Atom)>,
77 pub file_signatures: HashMap<FileId, FileSignature>,
80}
81
82impl CodebaseMetadata {
83 #[inline]
87 #[must_use]
88 pub fn new() -> Self {
89 Self::default()
90 }
91 #[inline]
102 #[must_use]
103 pub fn class_exists(&self, name: &str) -> bool {
104 let lowercase_name = ascii_lowercase_atom(name);
105 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
106 }
107
108 #[inline]
110 #[must_use]
111 pub fn interface_exists(&self, name: &str) -> bool {
112 let lowercase_name = ascii_lowercase_atom(name);
113 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
114 }
115
116 #[inline]
118 #[must_use]
119 pub fn trait_exists(&self, name: &str) -> bool {
120 let lowercase_name = ascii_lowercase_atom(name);
121 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
122 }
123
124 #[inline]
126 #[must_use]
127 pub fn enum_exists(&self, name: &str) -> bool {
128 let lowercase_name = ascii_lowercase_atom(name);
129 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
130 }
131
132 #[inline]
134 #[must_use]
135 pub fn class_like_exists(&self, name: &str) -> bool {
136 let lowercase_name = ascii_lowercase_atom(name);
137 self.symbols.contains(&lowercase_name)
138 }
139
140 #[inline]
142 #[must_use]
143 pub fn class_or_trait_exists(&self, name: &str) -> bool {
144 let lowercase_name = ascii_lowercase_atom(name);
145 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn class_or_interface_exists(&self, name: &str) -> bool {
152 let lowercase_name = ascii_lowercase_atom(name);
153 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
154 }
155
156 #[inline]
158 #[must_use]
159 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
160 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
161 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
162 let identifier = (lowercase_class, lowercase_method);
163 self.function_likes.contains_key(&identifier)
164 }
165
166 #[inline]
168 #[must_use]
169 pub fn function_exists(&self, name: &str) -> bool {
170 let lowercase_name = ascii_lowercase_atom(name);
171 let identifier = (empty_atom(), lowercase_name);
172 self.function_likes.contains_key(&identifier)
173 }
174
175 #[inline]
178 #[must_use]
179 pub fn constant_exists(&self, name: &str) -> bool {
180 let lowercase_name = ascii_lowercase_constant_name_atom(name);
181 self.constants.contains_key(&lowercase_name)
182 }
183
184 #[inline]
186 #[must_use]
187 pub fn method_exists(&self, class: &str, method: &str) -> bool {
188 let lowercase_class = ascii_lowercase_atom(class);
189 let lowercase_method = ascii_lowercase_atom(method);
190 self.class_likes
191 .get(&lowercase_class)
192 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
193 }
194
195 #[inline]
198 #[must_use]
199 pub fn property_exists(&self, class: &str, property: &str) -> bool {
200 let lowercase_class = ascii_lowercase_atom(class);
201 let property_name = atom(property);
202 self.class_likes
203 .get(&lowercase_class)
204 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
205 }
206
207 #[inline]
210 #[must_use]
211 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
212 let lowercase_class = ascii_lowercase_atom(class);
213 let constant_name = atom(constant);
214 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
215 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
216 })
217 }
218
219 #[inline]
221 #[must_use]
222 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
223 let lowercase_class = ascii_lowercase_atom(class);
224 let lowercase_method = ascii_lowercase_atom(method);
225 self.class_likes
226 .get(&lowercase_class)
227 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
228 .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
229 }
230
231 #[inline]
233 #[must_use]
234 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
235 let lowercase_class = ascii_lowercase_atom(class);
236 let property_name = atom(property);
237 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
238 }
239 #[inline]
244 #[must_use]
245 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
246 let lowercase_name = ascii_lowercase_atom(name);
247 if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
248 }
249
250 #[inline]
252 #[must_use]
253 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
254 let lowercase_name = ascii_lowercase_atom(name);
255 if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
256 }
257
258 #[inline]
260 #[must_use]
261 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
262 let lowercase_name = ascii_lowercase_atom(name);
263 if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
264 }
265
266 #[inline]
268 #[must_use]
269 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
270 let lowercase_name = ascii_lowercase_atom(name);
271 if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
272 }
273
274 #[inline]
276 #[must_use]
277 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
278 let lowercase_name = ascii_lowercase_atom(name);
279 self.class_likes.get(&lowercase_name)
280 }
281 #[inline]
285 #[must_use]
286 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
287 let lowercase_name = ascii_lowercase_atom(name);
288 let identifier = (empty_atom(), lowercase_name);
289 self.function_likes.get(&identifier)
290 }
291
292 #[inline]
294 #[must_use]
295 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
296 let lowercase_class = ascii_lowercase_atom(class);
297 let lowercase_method = ascii_lowercase_atom(method);
298 let identifier = (lowercase_class, lowercase_method);
299 self.function_likes.get(&identifier)
300 }
301
302 #[inline]
304 #[must_use]
305 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
306 let file_ref = u64_atom(file_id.as_u64());
307 let closure_ref = u32_atom(position.offset);
308 let identifier = (file_ref, closure_ref);
309 self.function_likes.get(&identifier)
310 }
311
312 #[inline]
314 #[must_use]
315 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
316 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
317 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
318 let identifier = (lowercase_class, lowercase_method);
319 self.function_likes.get(&identifier)
320 }
321
322 #[inline]
325 #[must_use]
326 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
327 let method_id = MethodIdentifier::new(atom(class), atom(method));
328 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
329 self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
330 }
331
332 #[inline]
335 #[must_use]
336 pub fn get_function_like(
337 &self,
338 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
339 ) -> Option<&FunctionLikeMetadata> {
340 use crate::identifier::function_like::FunctionLikeIdentifier;
341 match identifier {
342 FunctionLikeIdentifier::Function(name) => self.get_function(name),
343 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
344 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
345 }
346 }
347 #[inline]
352 #[must_use]
353 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
354 let lowercase_name = ascii_lowercase_constant_name_atom(name);
355 self.constants.get(&lowercase_name)
356 }
357
358 #[inline]
361 #[must_use]
362 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
363 let lowercase_class = ascii_lowercase_atom(class);
364 let constant_name = atom(constant);
365 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
366 }
367
368 #[inline]
370 #[must_use]
371 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
372 let lowercase_class = ascii_lowercase_atom(class);
373 let case_name = atom(case);
374 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
375 }
376 #[inline]
381 #[must_use]
382 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
383 let lowercase_class = ascii_lowercase_atom(class);
384 let property_name = atom(property);
385 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
386 }
387
388 #[inline]
390 #[must_use]
391 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
392 let lowercase_class = ascii_lowercase_atom(class);
393 let property_name = atom(property);
394 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
395 self.class_likes.get(declaring_class)?.properties.get(&property_name)
396 }
397 #[inline]
401 #[must_use]
402 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
403 let lowercase_class = ascii_lowercase_atom(class);
404 let property_name = atom(property);
405 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
406 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
407 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
408 }
409
410 #[must_use]
412 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
413 let lowercase_class = ascii_lowercase_atom(class);
414 let constant_name = atom(constant);
415 let class_meta = self.class_likes.get(&lowercase_class)?;
416
417 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
419 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
420 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
421 }
422
423 let constant_meta = class_meta.constants.get(&constant_name)?;
425
426 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
428 return Some(Cow::Borrowed(&type_meta.type_union));
429 }
430
431 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
433 }
434
435 #[inline]
437 #[must_use]
438 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
439 let lowercase_class = ascii_lowercase_atom(class);
440 let constant_name = atom(constant);
441 self.class_likes
442 .get(&lowercase_class)
443 .and_then(|meta| meta.constants.get(&constant_name))
444 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
445 }
446 #[inline]
450 #[must_use]
451 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
452 let lowercase_child = ascii_lowercase_atom(child);
453 let lowercase_parent = ascii_lowercase_atom(parent);
454 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
455 }
456
457 #[inline]
459 #[must_use]
460 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
461 let lowercase_child = ascii_lowercase_atom(child);
462 let lowercase_parent = ascii_lowercase_atom(parent);
463 self.class_likes
464 .get(&lowercase_child)
465 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
466 }
467
468 #[inline]
470 #[must_use]
471 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
472 let lowercase_class = ascii_lowercase_atom(class);
473 let lowercase_interface = ascii_lowercase_atom(interface);
474 self.class_likes
475 .get(&lowercase_class)
476 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
477 }
478
479 #[inline]
481 #[must_use]
482 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
483 let lowercase_class = ascii_lowercase_atom(class);
484 let lowercase_interface = ascii_lowercase_atom(interface);
485 self.class_likes
486 .get(&lowercase_class)
487 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
488 }
489
490 #[inline]
492 #[must_use]
493 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
494 let lowercase_class = ascii_lowercase_atom(class);
495 let lowercase_trait = ascii_lowercase_atom(trait_name);
496 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
497 }
498
499 #[inline]
502 #[must_use]
503 pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
504 let lowercase_trait = ascii_lowercase_atom(trait_name);
505
506 self.class_likes
507 .get(&lowercase_trait)
508 .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
509 }
510
511 #[inline]
513 #[must_use]
514 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
515 if child == parent {
516 return true;
517 }
518
519 let lowercase_child = ascii_lowercase_atom(child);
520 let lowercase_parent = ascii_lowercase_atom(parent);
521
522 if lowercase_child == lowercase_parent {
523 return true;
524 }
525
526 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
527 meta.all_parent_classes.contains(&lowercase_parent)
528 || meta.all_parent_interfaces.contains(&lowercase_parent)
529 || meta.used_traits.contains(&lowercase_parent)
530 || meta.require_extends.contains(&lowercase_parent)
531 || meta.require_implements.contains(&lowercase_parent)
532 })
533 }
534
535 #[inline]
537 #[must_use]
538 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
539 let lowercase_name = ascii_lowercase_atom(name);
540 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
541 }
542
543 #[inline]
546 #[must_use]
547 pub fn is_inheritable(&self, name: &str) -> bool {
548 let lowercase_name = ascii_lowercase_atom(name);
549 match self.symbols.get_kind(&lowercase_name) {
550 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
551 Some(SymbolKind::Enum) => false,
552 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
553 }
554 }
555
556 #[inline]
558 #[must_use]
559 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
560 let lowercase_class = ascii_lowercase_atom(class);
561 let mut all_descendants = AtomSet::default();
562 let mut queue = vec![&lowercase_class];
563 let mut visited = AtomSet::default();
564 visited.insert(lowercase_class);
565
566 while let Some(current_name) = queue.pop() {
567 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
568 for descendant in direct_descendants {
569 if visited.insert(*descendant) {
570 all_descendants.insert(*descendant);
571 queue.push(descendant);
572 }
573 }
574 }
575 }
576
577 all_descendants
578 }
579
580 #[inline]
582 #[must_use]
583 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
584 let lowercase_class = ascii_lowercase_atom(class);
585 let mut ancestors = AtomSet::default();
586 if let Some(meta) = self.class_likes.get(&lowercase_class) {
587 ancestors.extend(meta.all_parent_classes.iter().copied());
588 ancestors.extend(meta.all_parent_interfaces.iter().copied());
589 }
590 ancestors
591 }
592 #[inline]
596 #[must_use]
597 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
598 let lowercase_class = ascii_lowercase_atom(class);
599 let lowercase_method = ascii_lowercase_atom(method);
600
601 self.class_likes
602 .get(&lowercase_class)?
603 .declaring_method_ids
604 .get(&lowercase_method)
605 .map(|method_id| *method_id.get_class_name())
606 }
607
608 #[inline]
610 #[must_use]
611 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
612 let lowercase_class = ascii_lowercase_atom(class);
613 let lowercase_method = ascii_lowercase_atom(method);
614 self.class_likes
615 .get(&lowercase_class)?
616 .appearing_method_ids
617 .get(&lowercase_method)
618 .map(|method_id| *method_id.get_class_name())
619 }
620
621 #[must_use]
623 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
624 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
625 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
626
627 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
628 return *method_id;
629 };
630
631 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
632 return *declaring_method_id;
633 }
634
635 if class_meta.flags.is_abstract()
636 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
637 && let Some((_, first_method_id)) = overridden_map.iter().next()
638 {
639 return *first_method_id;
640 }
641
642 *method_id
643 }
644
645 #[inline]
647 #[must_use]
648 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
649 let lowercase_class = ascii_lowercase_atom(class);
650 let lowercase_method = ascii_lowercase_atom(method);
651 self.class_likes
652 .get(&lowercase_class)
653 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
654 }
655
656 #[inline]
658 #[must_use]
659 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
660 let lowercase_class = ascii_lowercase_atom(class);
661 let lowercase_method = ascii_lowercase_atom(method);
662 let identifier = (lowercase_class, lowercase_method);
663 self.function_likes
664 .get(&identifier)
665 .and_then(|meta| meta.method_metadata.as_ref())
666 .is_some_and(|method_meta| method_meta.is_abstract)
667 }
668
669 #[inline]
671 #[must_use]
672 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
673 let lowercase_class = ascii_lowercase_atom(class);
674 let lowercase_method = ascii_lowercase_atom(method);
675 let identifier = (lowercase_class, lowercase_method);
676 self.function_likes
677 .get(&identifier)
678 .and_then(|meta| meta.method_metadata.as_ref())
679 .is_some_and(|method_meta| method_meta.is_static)
680 }
681
682 #[inline]
684 #[must_use]
685 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
686 let lowercase_class = ascii_lowercase_atom(class);
687 let lowercase_method = ascii_lowercase_atom(method);
688 let identifier = (lowercase_class, lowercase_method);
689 self.function_likes
690 .get(&identifier)
691 .and_then(|meta| meta.method_metadata.as_ref())
692 .is_some_and(|method_meta| method_meta.is_final)
693 }
694
695 #[inline]
701 #[must_use]
702 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
703 let lowercase_class = ascii_lowercase_atom(class);
704 let lowercase_method = ascii_lowercase_atom(method);
705
706 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
708 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
709 {
710 return Some(*overridden_visibility);
711 }
712
713 let declaring_class = self.get_declaring_method_class(class, method)?;
715 let identifier = (declaring_class, lowercase_method);
716
717 self.function_likes
718 .get(&identifier)
719 .and_then(|meta| meta.method_metadata.as_ref())
720 .map(|method_meta| method_meta.visibility)
721 }
722
723 #[must_use]
725 pub fn get_function_like_thrown_types<'a>(
726 &'a self,
727 class_like: Option<&'a ClassLikeMetadata>,
728 function_like: &'a FunctionLikeMetadata,
729 ) -> &'a [TypeMetadata] {
730 if !function_like.thrown_types.is_empty() {
731 return function_like.thrown_types.as_slice();
732 }
733
734 if !function_like.kind.is_method() {
735 return &[];
736 }
737
738 let Some(class_like) = class_like else {
739 return &[];
740 };
741
742 let Some(method_name) = function_like.name.as_ref() else {
743 return &[];
744 };
745
746 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
747 for (parent_class_name, parent_method_id) in overridden_map {
748 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
749 continue;
750 };
751
752 let parent_method_key = (*parent_method_id.get_class_name(), *parent_method_id.get_method_name());
753 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
754 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
755 if !thrown.is_empty() {
756 return thrown;
757 }
758 }
759 }
760 }
761
762 &[]
763 }
764 #[inline]
768 #[must_use]
769 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
770 let lowercase_class = ascii_lowercase_atom(class);
771 let property_name = atom(property);
772 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
773 }
774
775 #[inline]
777 #[must_use]
778 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
779 let lowercase_class = ascii_lowercase_atom(class);
780 let property_name = atom(property);
781 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
782 }
783
784 #[must_use]
786 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
787 let lowercase_class = ascii_lowercase_atom(class);
788 let mut all_descendants = AtomSet::default();
789 let mut queue = vec![&lowercase_class];
790 let mut visited = AtomSet::default();
791 visited.insert(lowercase_class);
792
793 while let Some(current_name) = queue.pop() {
794 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
795 for descendant in direct_descendants {
796 if visited.insert(*descendant) {
797 all_descendants.insert(*descendant);
798 queue.push(descendant);
799 }
800 }
801 }
802 }
803
804 all_descendants
805 }
806
807 #[must_use]
809 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
810 use std::io::Write;
811
812 let mut buffer = [0u8; 64];
813 let mut writer = &mut buffer[..];
814
815 unsafe {
816 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
817 .unwrap_unchecked();
818 };
819
820 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
821
822 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
823 }
824
825 #[must_use]
827 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
828 let name = Self::get_anonymous_class_name(span);
829 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
830 }
831
832 #[inline]
842 #[must_use]
843 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
844 self.file_signatures.get(file_id)
845 }
846
847 #[inline]
858 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
859 self.file_signatures.insert(file_id, signature)
860 }
861
862 #[inline]
872 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
873 self.file_signatures.remove(file_id)
874 }
875
876 pub fn extend(&mut self, other: CodebaseMetadata) {
880 for (k, v) in other.class_likes {
881 let metadata_to_keep = match self.class_likes.entry(k) {
882 Entry::Occupied(entry) => {
883 let existing = entry.remove();
884 if v.flags.is_user_defined() {
885 v
886 } else if existing.flags.is_user_defined() {
887 existing
888 } else if v.flags.is_built_in() {
889 v
890 } else if existing.flags.is_built_in() {
891 existing
892 } else {
893 v
894 }
895 }
896 Entry::Vacant(_) => v,
897 };
898 self.class_likes.insert(k, metadata_to_keep);
899 }
900
901 for (k, v) in other.function_likes {
902 let metadata_to_keep = match self.function_likes.entry(k) {
903 Entry::Occupied(entry) => {
904 let existing = entry.remove();
905 if v.flags.is_user_defined() {
906 v
907 } else if existing.flags.is_user_defined() {
908 existing
909 } else if v.flags.is_built_in() {
910 v
911 } else if existing.flags.is_built_in() {
912 existing
913 } else {
914 v
915 }
916 }
917 Entry::Vacant(_) => v,
918 };
919 self.function_likes.insert(k, metadata_to_keep);
920 }
921
922 for (k, v) in other.constants {
923 let metadata_to_keep = match self.constants.entry(k) {
924 Entry::Occupied(entry) => {
925 let existing = entry.remove();
926 if v.flags.is_user_defined() {
927 v
928 } else if existing.flags.is_user_defined() {
929 existing
930 } else if v.flags.is_built_in() {
931 v
932 } else if existing.flags.is_built_in() {
933 existing
934 } else {
935 v
936 }
937 }
938 Entry::Vacant(_) => v,
939 };
940 self.constants.insert(k, metadata_to_keep);
941 }
942
943 self.symbols.extend(other.symbols);
944
945 for (k, v) in other.all_class_like_descendants {
946 self.all_class_like_descendants.entry(k).or_default().extend(v);
947 }
948
949 for (k, v) in other.direct_classlike_descendants {
950 self.direct_classlike_descendants.entry(k).or_default().extend(v);
951 }
952
953 self.safe_symbols.extend(other.safe_symbols);
954 self.safe_symbol_members.extend(other.safe_symbol_members);
955 self.infer_types_from_usage |= other.infer_types_from_usage;
956 }
957
958 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
960 let mut issues = IssueCollection::new();
961
962 for meta in self.class_likes.values_mut() {
963 if user_defined && !meta.flags.is_user_defined() {
964 continue;
965 }
966 issues.extend(meta.take_issues());
967 }
968
969 for meta in self.function_likes.values_mut() {
970 if user_defined && !meta.flags.is_user_defined() {
971 continue;
972 }
973 issues.extend(meta.take_issues());
974 }
975
976 for meta in self.constants.values_mut() {
977 if user_defined && !meta.flags.is_user_defined() {
978 continue;
979 }
980 issues.extend(meta.take_issues());
981 }
982
983 issues
984 }
985
986 #[must_use]
990 pub fn get_all_file_ids(&self) -> Vec<FileId> {
991 self.file_signatures.keys().copied().collect()
992 }
993}
994
995impl Default for CodebaseMetadata {
996 #[inline]
997 fn default() -> Self {
998 Self {
999 class_likes: AtomMap::default(),
1000 function_likes: HashMap::default(),
1001 symbols: Symbols::new(),
1002 infer_types_from_usage: false,
1003 constants: AtomMap::default(),
1004 all_class_like_descendants: AtomMap::default(),
1005 direct_classlike_descendants: AtomMap::default(),
1006 safe_symbols: AtomSet::default(),
1007 safe_symbol_members: HashSet::default(),
1008 file_signatures: HashMap::default(),
1009 }
1010 }
1011}