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)]
56pub struct CodebaseMetadata {
57 pub infer_types_from_usage: bool,
59 pub class_likes: AtomMap<ClassLikeMetadata>,
61 pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
64 pub symbols: Symbols,
66 pub constants: AtomMap<ConstantMetadata>,
68 pub all_class_like_descendants: AtomMap<AtomSet>,
70 pub direct_classlike_descendants: AtomMap<AtomSet>,
72 pub safe_symbols: AtomSet,
74 pub safe_symbol_members: HashSet<(Atom, Atom)>,
76 pub file_signatures: HashMap<FileId, FileSignature>,
79}
80
81impl CodebaseMetadata {
82 #[inline]
86 #[must_use]
87 pub fn new() -> Self {
88 Self::default()
89 }
90 #[inline]
101 #[must_use]
102 pub fn class_exists(&self, name: &str) -> bool {
103 let lowercase_name = ascii_lowercase_atom(name);
104 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn interface_exists(&self, name: &str) -> bool {
111 let lowercase_name = ascii_lowercase_atom(name);
112 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
113 }
114
115 #[inline]
117 #[must_use]
118 pub fn trait_exists(&self, name: &str) -> bool {
119 let lowercase_name = ascii_lowercase_atom(name);
120 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
121 }
122
123 #[inline]
125 #[must_use]
126 pub fn enum_exists(&self, name: &str) -> bool {
127 let lowercase_name = ascii_lowercase_atom(name);
128 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
129 }
130
131 #[inline]
133 #[must_use]
134 pub fn class_like_exists(&self, name: &str) -> bool {
135 let lowercase_name = ascii_lowercase_atom(name);
136 self.symbols.contains(&lowercase_name)
137 }
138
139 #[inline]
141 #[must_use]
142 pub fn class_or_trait_exists(&self, name: &str) -> bool {
143 let lowercase_name = ascii_lowercase_atom(name);
144 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
145 }
146
147 #[inline]
149 #[must_use]
150 pub fn class_or_interface_exists(&self, name: &str) -> bool {
151 let lowercase_name = ascii_lowercase_atom(name);
152 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
159 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
160 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
161 let identifier = (lowercase_class, lowercase_method);
162 self.function_likes.contains_key(&identifier)
163 }
164
165 #[inline]
167 #[must_use]
168 pub fn function_exists(&self, name: &str) -> bool {
169 let lowercase_name = ascii_lowercase_atom(name);
170 let identifier = (empty_atom(), lowercase_name);
171 self.function_likes.contains_key(&identifier)
172 }
173
174 #[inline]
177 #[must_use]
178 pub fn constant_exists(&self, name: &str) -> bool {
179 let lowercase_name = ascii_lowercase_constant_name_atom(name);
180 self.constants.contains_key(&lowercase_name)
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn method_exists(&self, class: &str, method: &str) -> bool {
187 let lowercase_class = ascii_lowercase_atom(class);
188 let lowercase_method = ascii_lowercase_atom(method);
189 self.class_likes
190 .get(&lowercase_class)
191 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
192 }
193
194 #[inline]
197 #[must_use]
198 pub fn property_exists(&self, class: &str, property: &str) -> bool {
199 let lowercase_class = ascii_lowercase_atom(class);
200 let property_name = atom(property);
201 self.class_likes
202 .get(&lowercase_class)
203 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
204 }
205
206 #[inline]
209 #[must_use]
210 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
211 let lowercase_class = ascii_lowercase_atom(class);
212 let constant_name = atom(constant);
213 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
214 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
215 })
216 }
217
218 #[inline]
220 #[must_use]
221 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
222 let lowercase_class = ascii_lowercase_atom(class);
223 let lowercase_method = ascii_lowercase_atom(method);
224 self.class_likes
225 .get(&lowercase_class)
226 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
227 .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
228 }
229
230 #[inline]
232 #[must_use]
233 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
234 let lowercase_class = ascii_lowercase_atom(class);
235 let property_name = atom(property);
236 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
237 }
238 #[inline]
243 #[must_use]
244 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
245 let lowercase_name = ascii_lowercase_atom(name);
246 if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
247 }
248
249 #[inline]
251 #[must_use]
252 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
253 let lowercase_name = ascii_lowercase_atom(name);
254 if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
255 }
256
257 #[inline]
259 #[must_use]
260 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
261 let lowercase_name = ascii_lowercase_atom(name);
262 if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
263 }
264
265 #[inline]
267 #[must_use]
268 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
269 let lowercase_name = ascii_lowercase_atom(name);
270 if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
271 }
272
273 #[inline]
275 #[must_use]
276 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
277 let lowercase_name = ascii_lowercase_atom(name);
278 self.class_likes.get(&lowercase_name)
279 }
280 #[inline]
284 #[must_use]
285 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
286 let lowercase_name = ascii_lowercase_atom(name);
287 let identifier = (empty_atom(), lowercase_name);
288 self.function_likes.get(&identifier)
289 }
290
291 #[inline]
293 #[must_use]
294 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
295 let lowercase_class = ascii_lowercase_atom(class);
296 let lowercase_method = ascii_lowercase_atom(method);
297 let identifier = (lowercase_class, lowercase_method);
298 self.function_likes.get(&identifier)
299 }
300
301 #[inline]
303 #[must_use]
304 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
305 let file_ref = u64_atom(file_id.as_u64());
306 let closure_ref = u32_atom(position.offset);
307 let identifier = (file_ref, closure_ref);
308 self.function_likes.get(&identifier)
309 }
310
311 #[inline]
313 #[must_use]
314 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
315 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
316 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
317 let identifier = (lowercase_class, lowercase_method);
318 self.function_likes.get(&identifier)
319 }
320
321 #[inline]
324 #[must_use]
325 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
326 let method_id = MethodIdentifier::new(atom(class), atom(method));
327 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
328 self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
329 }
330
331 #[inline]
334 #[must_use]
335 pub fn get_function_like(
336 &self,
337 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
338 ) -> Option<&FunctionLikeMetadata> {
339 use crate::identifier::function_like::FunctionLikeIdentifier;
340 match identifier {
341 FunctionLikeIdentifier::Function(name) => self.get_function(name),
342 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
343 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
344 }
345 }
346 #[inline]
351 #[must_use]
352 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
353 let lowercase_name = ascii_lowercase_constant_name_atom(name);
354 self.constants.get(&lowercase_name)
355 }
356
357 #[inline]
360 #[must_use]
361 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
362 let lowercase_class = ascii_lowercase_atom(class);
363 let constant_name = atom(constant);
364 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
365 }
366
367 #[inline]
369 #[must_use]
370 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
371 let lowercase_class = ascii_lowercase_atom(class);
372 let case_name = atom(case);
373 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
374 }
375 #[inline]
380 #[must_use]
381 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
382 let lowercase_class = ascii_lowercase_atom(class);
383 let property_name = atom(property);
384 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
385 }
386
387 #[inline]
389 #[must_use]
390 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
391 let lowercase_class = ascii_lowercase_atom(class);
392 let property_name = atom(property);
393 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
394 self.class_likes.get(declaring_class)?.properties.get(&property_name)
395 }
396 #[inline]
400 #[must_use]
401 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
402 let lowercase_class = ascii_lowercase_atom(class);
403 let property_name = atom(property);
404 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
405 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
406 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
407 }
408
409 #[must_use]
411 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
412 let lowercase_class = ascii_lowercase_atom(class);
413 let constant_name = atom(constant);
414 let class_meta = self.class_likes.get(&lowercase_class)?;
415
416 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
418 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
419 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
420 }
421
422 let constant_meta = class_meta.constants.get(&constant_name)?;
424
425 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
427 return Some(Cow::Borrowed(&type_meta.type_union));
428 }
429
430 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
432 }
433
434 #[inline]
436 #[must_use]
437 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
438 let lowercase_class = ascii_lowercase_atom(class);
439 let constant_name = atom(constant);
440 self.class_likes
441 .get(&lowercase_class)
442 .and_then(|meta| meta.constants.get(&constant_name))
443 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
444 }
445 #[inline]
449 #[must_use]
450 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
451 let lowercase_child = ascii_lowercase_atom(child);
452 let lowercase_parent = ascii_lowercase_atom(parent);
453 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
454 }
455
456 #[inline]
458 #[must_use]
459 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
460 let lowercase_child = ascii_lowercase_atom(child);
461 let lowercase_parent = ascii_lowercase_atom(parent);
462 self.class_likes
463 .get(&lowercase_child)
464 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
465 }
466
467 #[inline]
469 #[must_use]
470 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
471 let lowercase_class = ascii_lowercase_atom(class);
472 let lowercase_interface = ascii_lowercase_atom(interface);
473 self.class_likes
474 .get(&lowercase_class)
475 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
476 }
477
478 #[inline]
480 #[must_use]
481 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
482 let lowercase_class = ascii_lowercase_atom(class);
483 let lowercase_interface = ascii_lowercase_atom(interface);
484 self.class_likes
485 .get(&lowercase_class)
486 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
487 }
488
489 #[inline]
491 #[must_use]
492 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
493 let lowercase_class = ascii_lowercase_atom(class);
494 let lowercase_trait = ascii_lowercase_atom(trait_name);
495 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
496 }
497
498 #[inline]
501 #[must_use]
502 pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
503 let lowercase_trait = ascii_lowercase_atom(trait_name);
504
505 self.class_likes
506 .get(&lowercase_trait)
507 .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
508 }
509
510 #[inline]
512 #[must_use]
513 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
514 if child == parent {
515 return true;
516 }
517
518 let lowercase_child = ascii_lowercase_atom(child);
519 let lowercase_parent = ascii_lowercase_atom(parent);
520
521 if lowercase_child == lowercase_parent {
522 return true;
523 }
524
525 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
526 meta.all_parent_classes.contains(&lowercase_parent)
527 || meta.all_parent_interfaces.contains(&lowercase_parent)
528 || meta.used_traits.contains(&lowercase_parent)
529 || meta.require_extends.contains(&lowercase_parent)
530 || meta.require_implements.contains(&lowercase_parent)
531 })
532 }
533
534 #[inline]
536 #[must_use]
537 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
538 let lowercase_name = ascii_lowercase_atom(name);
539 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
540 }
541
542 #[inline]
545 #[must_use]
546 pub fn is_inheritable(&self, name: &str) -> bool {
547 let lowercase_name = ascii_lowercase_atom(name);
548 match self.symbols.get_kind(&lowercase_name) {
549 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
550 Some(SymbolKind::Enum) => false,
551 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
552 }
553 }
554
555 #[inline]
557 #[must_use]
558 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
559 let lowercase_class = ascii_lowercase_atom(class);
560 let mut all_descendants = AtomSet::default();
561 let mut queue = vec![&lowercase_class];
562 let mut visited = AtomSet::default();
563 visited.insert(lowercase_class);
564
565 while let Some(current_name) = queue.pop() {
566 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
567 for descendant in direct_descendants {
568 if visited.insert(*descendant) {
569 all_descendants.insert(*descendant);
570 queue.push(descendant);
571 }
572 }
573 }
574 }
575
576 all_descendants
577 }
578
579 #[inline]
581 #[must_use]
582 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
583 let lowercase_class = ascii_lowercase_atom(class);
584 let mut ancestors = AtomSet::default();
585 if let Some(meta) = self.class_likes.get(&lowercase_class) {
586 ancestors.extend(meta.all_parent_classes.iter().copied());
587 ancestors.extend(meta.all_parent_interfaces.iter().copied());
588 }
589 ancestors
590 }
591 #[inline]
595 #[must_use]
596 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
597 let lowercase_class = ascii_lowercase_atom(class);
598 let lowercase_method = ascii_lowercase_atom(method);
599
600 self.class_likes
601 .get(&lowercase_class)?
602 .declaring_method_ids
603 .get(&lowercase_method)
604 .map(|method_id| *method_id.get_class_name())
605 }
606
607 #[inline]
609 #[must_use]
610 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
611 let lowercase_class = ascii_lowercase_atom(class);
612 let lowercase_method = ascii_lowercase_atom(method);
613 self.class_likes
614 .get(&lowercase_class)?
615 .appearing_method_ids
616 .get(&lowercase_method)
617 .map(|method_id| *method_id.get_class_name())
618 }
619
620 #[must_use]
622 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
623 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
624 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
625
626 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
627 return *method_id;
628 };
629
630 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
631 return *declaring_method_id;
632 }
633
634 if class_meta.flags.is_abstract()
635 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
636 && let Some((_, first_method_id)) = overridden_map.iter().next()
637 {
638 return *first_method_id;
639 }
640
641 *method_id
642 }
643
644 #[inline]
646 #[must_use]
647 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
648 let lowercase_class = ascii_lowercase_atom(class);
649 let lowercase_method = ascii_lowercase_atom(method);
650 self.class_likes
651 .get(&lowercase_class)
652 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
653 }
654
655 #[inline]
657 #[must_use]
658 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
659 let lowercase_class = ascii_lowercase_atom(class);
660 let lowercase_method = ascii_lowercase_atom(method);
661 let identifier = (lowercase_class, lowercase_method);
662 self.function_likes
663 .get(&identifier)
664 .and_then(|meta| meta.method_metadata.as_ref())
665 .is_some_and(|method_meta| method_meta.is_abstract)
666 }
667
668 #[inline]
670 #[must_use]
671 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
672 let lowercase_class = ascii_lowercase_atom(class);
673 let lowercase_method = ascii_lowercase_atom(method);
674 let identifier = (lowercase_class, lowercase_method);
675 self.function_likes
676 .get(&identifier)
677 .and_then(|meta| meta.method_metadata.as_ref())
678 .is_some_and(|method_meta| method_meta.is_static)
679 }
680
681 #[inline]
683 #[must_use]
684 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
685 let lowercase_class = ascii_lowercase_atom(class);
686 let lowercase_method = ascii_lowercase_atom(method);
687 let identifier = (lowercase_class, lowercase_method);
688 self.function_likes
689 .get(&identifier)
690 .and_then(|meta| meta.method_metadata.as_ref())
691 .is_some_and(|method_meta| method_meta.is_final)
692 }
693
694 #[inline]
700 #[must_use]
701 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
702 let lowercase_class = ascii_lowercase_atom(class);
703 let lowercase_method = ascii_lowercase_atom(method);
704
705 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
707 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
708 {
709 return Some(*overridden_visibility);
710 }
711
712 let declaring_class = self.get_declaring_method_class(class, method)?;
714 let identifier = (declaring_class, lowercase_method);
715
716 self.function_likes
717 .get(&identifier)
718 .and_then(|meta| meta.method_metadata.as_ref())
719 .map(|method_meta| method_meta.visibility)
720 }
721
722 #[must_use]
724 pub fn get_function_like_thrown_types<'a>(
725 &'a self,
726 class_like: Option<&'a ClassLikeMetadata>,
727 function_like: &'a FunctionLikeMetadata,
728 ) -> &'a [TypeMetadata] {
729 if !function_like.thrown_types.is_empty() {
730 return function_like.thrown_types.as_slice();
731 }
732
733 if !function_like.kind.is_method() {
734 return &[];
735 }
736
737 let Some(class_like) = class_like else {
738 return &[];
739 };
740
741 let Some(method_name) = function_like.name.as_ref() else {
742 return &[];
743 };
744
745 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
746 for (parent_class_name, parent_method_id) in overridden_map {
747 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
748 continue;
749 };
750
751 let parent_method_key = (*parent_method_id.get_class_name(), *parent_method_id.get_method_name());
752 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
753 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
754 if !thrown.is_empty() {
755 return thrown;
756 }
757 }
758 }
759 }
760
761 &[]
762 }
763 #[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) {
879 for (k, v) in other.class_likes {
880 let metadata_to_keep = match self.class_likes.entry(k) {
881 Entry::Occupied(entry) => {
882 let existing = entry.remove();
883 if v.flags.is_user_defined() {
884 v
885 } else if existing.flags.is_user_defined() {
886 existing
887 } else if v.flags.is_built_in() {
888 v
889 } else if existing.flags.is_built_in() {
890 existing
891 } else {
892 v
893 }
894 }
895 Entry::Vacant(_) => v,
896 };
897 self.class_likes.insert(k, metadata_to_keep);
898 }
899
900 for (k, v) in other.function_likes {
901 let metadata_to_keep = match self.function_likes.entry(k) {
902 Entry::Occupied(entry) => {
903 let existing = entry.remove();
904 if v.flags.is_user_defined() {
905 v
906 } else if existing.flags.is_user_defined() {
907 existing
908 } else if v.flags.is_built_in() {
909 v
910 } else if existing.flags.is_built_in() {
911 existing
912 } else {
913 v
914 }
915 }
916 Entry::Vacant(_) => v,
917 };
918 self.function_likes.insert(k, metadata_to_keep);
919 }
920
921 for (k, v) in other.constants {
922 let metadata_to_keep = match self.constants.entry(k) {
923 Entry::Occupied(entry) => {
924 let existing = entry.remove();
925 if v.flags.is_user_defined() {
926 v
927 } else if existing.flags.is_user_defined() {
928 existing
929 } else if v.flags.is_built_in() {
930 v
931 } else if existing.flags.is_built_in() {
932 existing
933 } else {
934 v
935 }
936 }
937 Entry::Vacant(_) => v,
938 };
939 self.constants.insert(k, metadata_to_keep);
940 }
941
942 self.symbols.extend(other.symbols);
943
944 for (k, v) in other.all_class_like_descendants {
945 self.all_class_like_descendants.entry(k).or_default().extend(v);
946 }
947
948 for (k, v) in other.direct_classlike_descendants {
949 self.direct_classlike_descendants.entry(k).or_default().extend(v);
950 }
951
952 self.safe_symbols.extend(other.safe_symbols);
953 self.safe_symbol_members.extend(other.safe_symbol_members);
954 self.infer_types_from_usage |= other.infer_types_from_usage;
955 }
956
957 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
959 let mut issues = IssueCollection::new();
960
961 for meta in self.class_likes.values_mut() {
962 if user_defined && !meta.flags.is_user_defined() {
963 continue;
964 }
965 issues.extend(meta.take_issues());
966 }
967
968 for meta in self.function_likes.values_mut() {
969 if user_defined && !meta.flags.is_user_defined() {
970 continue;
971 }
972 issues.extend(meta.take_issues());
973 }
974
975 for meta in self.constants.values_mut() {
976 if user_defined && !meta.flags.is_user_defined() {
977 continue;
978 }
979 issues.extend(meta.take_issues());
980 }
981
982 issues
983 }
984
985 #[must_use]
989 pub fn get_all_file_ids(&self) -> Vec<FileId> {
990 self.file_signatures.keys().copied().collect()
991 }
992}
993
994impl Default for CodebaseMetadata {
995 #[inline]
996 fn default() -> Self {
997 Self {
998 class_likes: AtomMap::default(),
999 function_likes: HashMap::default(),
1000 symbols: Symbols::new(),
1001 infer_types_from_usage: false,
1002 constants: AtomMap::default(),
1003 all_class_like_descendants: AtomMap::default(),
1004 direct_classlike_descendants: AtomMap::default(),
1005 safe_symbols: AtomSet::default(),
1006 safe_symbol_members: HashSet::default(),
1007 file_signatures: HashMap::default(),
1008 }
1009 }
1010}