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]
85 #[must_use]
86 pub fn new() -> Self {
87 Self::default()
88 }
89
90 #[inline]
99 #[must_use]
100 pub fn class_exists(&self, name: &str) -> bool {
101 let lowercase_name = ascii_lowercase_atom(name);
102 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
103 }
104
105 #[inline]
107 #[must_use]
108 pub fn interface_exists(&self, name: &str) -> bool {
109 let lowercase_name = ascii_lowercase_atom(name);
110 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn trait_exists(&self, name: &str) -> bool {
117 let lowercase_name = ascii_lowercase_atom(name);
118 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
119 }
120
121 #[inline]
123 #[must_use]
124 pub fn enum_exists(&self, name: &str) -> bool {
125 let lowercase_name = ascii_lowercase_atom(name);
126 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
127 }
128
129 #[inline]
131 #[must_use]
132 pub fn class_like_exists(&self, name: &str) -> bool {
133 let lowercase_name = ascii_lowercase_atom(name);
134 self.symbols.contains(&lowercase_name)
135 }
136
137 #[inline]
139 #[must_use]
140 pub fn namespace_exists(&self, name: &str) -> bool {
141 let lowercase_name = ascii_lowercase_atom(name);
142 self.symbols.contains_namespace(&lowercase_name)
143 }
144
145 #[inline]
147 #[must_use]
148 pub fn class_or_trait_exists(&self, name: &str) -> bool {
149 let lowercase_name = ascii_lowercase_atom(name);
150 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
151 }
152
153 #[inline]
155 #[must_use]
156 pub fn class_or_interface_exists(&self, name: &str) -> bool {
157 let lowercase_name = ascii_lowercase_atom(name);
158 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
159 }
160
161 #[inline]
163 #[must_use]
164 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
165 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
166 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
167 let identifier = (lowercase_class, lowercase_method);
168 self.function_likes.contains_key(&identifier)
169 }
170
171 #[inline]
173 #[must_use]
174 pub fn function_exists(&self, name: &str) -> bool {
175 let lowercase_name = ascii_lowercase_atom(name);
176 let identifier = (empty_atom(), lowercase_name);
177 self.function_likes.contains_key(&identifier)
178 }
179
180 #[inline]
183 #[must_use]
184 pub fn constant_exists(&self, name: &str) -> bool {
185 let lowercase_name = ascii_lowercase_constant_name_atom(name);
186 self.constants.contains_key(&lowercase_name)
187 }
188
189 #[inline]
191 #[must_use]
192 pub fn method_exists(&self, class: &str, method: &str) -> bool {
193 let lowercase_class = ascii_lowercase_atom(class);
194 let lowercase_method = ascii_lowercase_atom(method);
195 self.class_likes
196 .get(&lowercase_class)
197 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
198 }
199
200 #[inline]
203 #[must_use]
204 pub fn property_exists(&self, class: &str, property: &str) -> bool {
205 let lowercase_class = ascii_lowercase_atom(class);
206 let property_name = atom(property);
207 self.class_likes
208 .get(&lowercase_class)
209 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
210 }
211
212 #[inline]
215 #[must_use]
216 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
217 let lowercase_class = ascii_lowercase_atom(class);
218 let constant_name = atom(constant);
219 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
220 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
221 })
222 }
223
224 #[inline]
226 #[must_use]
227 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
228 let lowercase_class = ascii_lowercase_atom(class);
229 let lowercase_method = ascii_lowercase_atom(method);
230 self.class_likes
231 .get(&lowercase_class)
232 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
233 .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
234 }
235
236 #[inline]
238 #[must_use]
239 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
240 let lowercase_class = ascii_lowercase_atom(class);
241 let property_name = atom(property);
242 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
243 }
244
245 #[inline]
248 #[must_use]
249 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
250 let lowercase_name = ascii_lowercase_atom(name);
251 if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
252 }
253
254 #[inline]
256 #[must_use]
257 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
258 let lowercase_name = ascii_lowercase_atom(name);
259 if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
260 }
261
262 #[inline]
264 #[must_use]
265 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
266 let lowercase_name = ascii_lowercase_atom(name);
267 if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
268 }
269
270 #[inline]
272 #[must_use]
273 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
274 let lowercase_name = ascii_lowercase_atom(name);
275 if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
276 }
277
278 #[inline]
280 #[must_use]
281 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
282 let lowercase_name = ascii_lowercase_atom(name);
283 self.class_likes.get(&lowercase_name)
284 }
285
286 #[inline]
288 #[must_use]
289 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
290 let lowercase_name = ascii_lowercase_atom(name);
291 let identifier = (empty_atom(), lowercase_name);
292 self.function_likes.get(&identifier)
293 }
294
295 #[inline]
297 #[must_use]
298 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
299 let lowercase_class = ascii_lowercase_atom(class);
300 let lowercase_method = ascii_lowercase_atom(method);
301 let identifier = (lowercase_class, lowercase_method);
302 self.function_likes.get(&identifier)
303 }
304
305 #[inline]
307 #[must_use]
308 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
309 let file_ref = u64_atom(file_id.as_u64());
310 let closure_ref = u32_atom(position.offset);
311 let identifier = (file_ref, closure_ref);
312 self.function_likes.get(&identifier)
313 }
314
315 #[inline]
317 #[must_use]
318 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
319 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
320 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
321 let identifier = (lowercase_class, lowercase_method);
322 self.function_likes.get(&identifier)
323 }
324
325 #[inline]
328 #[must_use]
329 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
330 let method_id = MethodIdentifier::new(atom(class), atom(method));
331 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
332 self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
333 }
334
335 #[inline]
338 #[must_use]
339 pub fn get_function_like(
340 &self,
341 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
342 ) -> Option<&FunctionLikeMetadata> {
343 use crate::identifier::function_like::FunctionLikeIdentifier;
344 match identifier {
345 FunctionLikeIdentifier::Function(name) => self.get_function(name),
346 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
347 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
348 }
349 }
350
351 #[inline]
354 #[must_use]
355 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
356 let lowercase_name = ascii_lowercase_constant_name_atom(name);
357 self.constants.get(&lowercase_name)
358 }
359
360 #[inline]
363 #[must_use]
364 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
365 let lowercase_class = ascii_lowercase_atom(class);
366 let constant_name = atom(constant);
367 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
368 }
369
370 #[inline]
372 #[must_use]
373 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
374 let lowercase_class = ascii_lowercase_atom(class);
375 let case_name = atom(case);
376 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
377 }
378
379 #[inline]
382 #[must_use]
383 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
384 let lowercase_class = ascii_lowercase_atom(class);
385 let property_name = atom(property);
386 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
387 }
388
389 #[inline]
391 #[must_use]
392 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
393 let lowercase_class = ascii_lowercase_atom(class);
394 let property_name = atom(property);
395 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
396 self.class_likes.get(declaring_class)?.properties.get(&property_name)
397 }
398 #[inline]
402 #[must_use]
403 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
404 let lowercase_class = ascii_lowercase_atom(class);
405 let property_name = atom(property);
406 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
407 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
408 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
409 }
410
411 #[must_use]
413 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
414 let lowercase_class = ascii_lowercase_atom(class);
415 let constant_name = atom(constant);
416 let class_meta = self.class_likes.get(&lowercase_class)?;
417
418 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
420 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
421 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
422 }
423
424 let constant_meta = class_meta.constants.get(&constant_name)?;
426
427 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
429 return Some(Cow::Borrowed(&type_meta.type_union));
430 }
431
432 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
434 }
435
436 #[inline]
438 #[must_use]
439 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
440 let lowercase_class = ascii_lowercase_atom(class);
441 let constant_name = atom(constant);
442 self.class_likes
443 .get(&lowercase_class)
444 .and_then(|meta| meta.constants.get(&constant_name))
445 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
446 }
447 #[inline]
451 #[must_use]
452 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
453 let lowercase_child = ascii_lowercase_atom(child);
454 let lowercase_parent = ascii_lowercase_atom(parent);
455 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
456 }
457
458 #[inline]
460 #[must_use]
461 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
462 let lowercase_child = ascii_lowercase_atom(child);
463 let lowercase_parent = ascii_lowercase_atom(parent);
464 self.class_likes
465 .get(&lowercase_child)
466 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
467 }
468
469 #[inline]
471 #[must_use]
472 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
473 let lowercase_class = ascii_lowercase_atom(class);
474 let lowercase_interface = ascii_lowercase_atom(interface);
475 self.class_likes
476 .get(&lowercase_class)
477 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
478 }
479
480 #[inline]
482 #[must_use]
483 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
484 let lowercase_class = ascii_lowercase_atom(class);
485 let lowercase_interface = ascii_lowercase_atom(interface);
486 self.class_likes
487 .get(&lowercase_class)
488 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
489 }
490
491 #[inline]
493 #[must_use]
494 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
495 let lowercase_class = ascii_lowercase_atom(class);
496 let lowercase_trait = ascii_lowercase_atom(trait_name);
497 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
498 }
499
500 #[inline]
503 #[must_use]
504 pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
505 let lowercase_trait = ascii_lowercase_atom(trait_name);
506
507 self.class_likes
508 .get(&lowercase_trait)
509 .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
510 }
511
512 #[inline]
514 #[must_use]
515 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
516 if child == parent {
517 return true;
518 }
519
520 let lowercase_child = ascii_lowercase_atom(child);
521 let lowercase_parent = ascii_lowercase_atom(parent);
522
523 if lowercase_child == lowercase_parent {
524 return true;
525 }
526
527 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
528 meta.all_parent_classes.contains(&lowercase_parent)
529 || meta.all_parent_interfaces.contains(&lowercase_parent)
530 || meta.used_traits.contains(&lowercase_parent)
531 || meta.require_extends.contains(&lowercase_parent)
532 || meta.require_implements.contains(&lowercase_parent)
533 })
534 }
535
536 #[inline]
538 #[must_use]
539 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
540 let lowercase_name = ascii_lowercase_atom(name);
541 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
542 }
543
544 #[inline]
547 #[must_use]
548 pub fn is_inheritable(&self, name: &str) -> bool {
549 let lowercase_name = ascii_lowercase_atom(name);
550 match self.symbols.get_kind(&lowercase_name) {
551 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
552 Some(SymbolKind::Enum) => false,
553 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
554 }
555 }
556
557 #[inline]
559 #[must_use]
560 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
561 let lowercase_class = ascii_lowercase_atom(class);
562 let mut all_descendants = AtomSet::default();
563 let mut queue = vec![&lowercase_class];
564 let mut visited = AtomSet::default();
565 visited.insert(lowercase_class);
566
567 while let Some(current_name) = queue.pop() {
568 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
569 for descendant in direct_descendants {
570 if visited.insert(*descendant) {
571 all_descendants.insert(*descendant);
572 queue.push(descendant);
573 }
574 }
575 }
576 }
577
578 all_descendants
579 }
580
581 #[inline]
583 #[must_use]
584 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
585 let lowercase_class = ascii_lowercase_atom(class);
586 let mut ancestors = AtomSet::default();
587 if let Some(meta) = self.class_likes.get(&lowercase_class) {
588 ancestors.extend(meta.all_parent_classes.iter().copied());
589 ancestors.extend(meta.all_parent_interfaces.iter().copied());
590 }
591 ancestors
592 }
593
594 #[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
765 #[inline]
767 #[must_use]
768 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
769 let lowercase_class = ascii_lowercase_atom(class);
770 let property_name = atom(property);
771 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
772 }
773
774 #[inline]
776 #[must_use]
777 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
778 let lowercase_class = ascii_lowercase_atom(class);
779 let property_name = atom(property);
780 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
781 }
782
783 #[must_use]
785 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
786 let lowercase_class = ascii_lowercase_atom(class);
787 let mut all_descendants = AtomSet::default();
788 let mut queue = vec![&lowercase_class];
789 let mut visited = AtomSet::default();
790 visited.insert(lowercase_class);
791
792 while let Some(current_name) = queue.pop() {
793 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
794 for descendant in direct_descendants {
795 if visited.insert(*descendant) {
796 all_descendants.insert(*descendant);
797 queue.push(descendant);
798 }
799 }
800 }
801 }
802
803 all_descendants
804 }
805
806 #[must_use]
808 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
809 use std::io::Write;
810
811 let mut buffer = [0u8; 64];
812 let mut writer = &mut buffer[..];
813
814 unsafe {
815 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
816 .unwrap_unchecked();
817 };
818
819 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
820
821 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
822 }
823
824 #[must_use]
826 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
827 let name = Self::get_anonymous_class_name(span);
828 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
829 }
830
831 #[inline]
841 #[must_use]
842 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
843 self.file_signatures.get(file_id)
844 }
845
846 #[inline]
857 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
858 self.file_signatures.insert(file_id, signature)
859 }
860
861 #[inline]
871 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
872 self.file_signatures.remove(file_id)
873 }
874
875 pub fn extend(&mut self, other: CodebaseMetadata) {
877 for (k, v) in other.class_likes {
878 let metadata_to_keep = match self.class_likes.entry(k) {
879 Entry::Occupied(entry) => {
880 let existing = entry.remove();
881 if v.flags.is_user_defined() {
882 v
883 } else if existing.flags.is_user_defined() {
884 existing
885 } else if v.flags.is_built_in() {
886 v
887 } else if existing.flags.is_built_in() {
888 existing
889 } else {
890 v
891 }
892 }
893 Entry::Vacant(_) => v,
894 };
895 self.class_likes.insert(k, metadata_to_keep);
896 }
897
898 for (k, v) in other.function_likes {
899 let metadata_to_keep = match self.function_likes.entry(k) {
900 Entry::Occupied(entry) => {
901 let existing = entry.remove();
902 if v.flags.is_user_defined() {
903 v
904 } else if existing.flags.is_user_defined() {
905 existing
906 } else if v.flags.is_built_in() {
907 v
908 } else if existing.flags.is_built_in() {
909 existing
910 } else {
911 v
912 }
913 }
914 Entry::Vacant(_) => v,
915 };
916 self.function_likes.insert(k, metadata_to_keep);
917 }
918
919 for (k, v) in other.constants {
920 let metadata_to_keep = match self.constants.entry(k) {
921 Entry::Occupied(entry) => {
922 let existing = entry.remove();
923 if v.flags.is_user_defined() {
924 v
925 } else if existing.flags.is_user_defined() {
926 existing
927 } else if v.flags.is_built_in() {
928 v
929 } else if existing.flags.is_built_in() {
930 existing
931 } else {
932 v
933 }
934 }
935 Entry::Vacant(_) => v,
936 };
937 self.constants.insert(k, metadata_to_keep);
938 }
939
940 self.symbols.extend(other.symbols);
941
942 for (k, v) in other.all_class_like_descendants {
943 self.all_class_like_descendants.entry(k).or_default().extend(v);
944 }
945
946 for (k, v) in other.direct_classlike_descendants {
947 self.direct_classlike_descendants.entry(k).or_default().extend(v);
948 }
949
950 self.safe_symbols.extend(other.safe_symbols);
951 self.safe_symbol_members.extend(other.safe_symbol_members);
952 self.infer_types_from_usage |= other.infer_types_from_usage;
953 }
954
955 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
957 let mut issues = IssueCollection::new();
958
959 for meta in self.class_likes.values_mut() {
960 if user_defined && !meta.flags.is_user_defined() {
961 continue;
962 }
963 issues.extend(meta.take_issues());
964 }
965
966 for meta in self.function_likes.values_mut() {
967 if user_defined && !meta.flags.is_user_defined() {
968 continue;
969 }
970 issues.extend(meta.take_issues());
971 }
972
973 for meta in self.constants.values_mut() {
974 if user_defined && !meta.flags.is_user_defined() {
975 continue;
976 }
977 issues.extend(meta.take_issues());
978 }
979
980 issues
981 }
982
983 #[must_use]
987 pub fn get_all_file_ids(&self) -> Vec<FileId> {
988 self.file_signatures.keys().copied().collect()
989 }
990}
991
992impl Default for CodebaseMetadata {
993 #[inline]
994 fn default() -> Self {
995 Self {
996 class_likes: AtomMap::default(),
997 function_likes: HashMap::default(),
998 symbols: Symbols::new(),
999 infer_types_from_usage: false,
1000 constants: AtomMap::default(),
1001 all_class_like_descendants: AtomMap::default(),
1002 direct_classlike_descendants: AtomMap::default(),
1003 safe_symbols: AtomSet::default(),
1004 safe_symbol_members: HashSet::default(),
1005 file_signatures: HashMap::default(),
1006 }
1007 }
1008}