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 pub fn new() -> Self {
87 Self::default()
88 }
89 #[inline]
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 pub fn interface_exists(&self, name: &str) -> bool {
108 let lowercase_name = ascii_lowercase_atom(name);
109 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
110 }
111
112 #[inline]
114 pub fn trait_exists(&self, name: &str) -> bool {
115 let lowercase_name = ascii_lowercase_atom(name);
116 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
117 }
118
119 #[inline]
121 pub fn enum_exists(&self, name: &str) -> bool {
122 let lowercase_name = ascii_lowercase_atom(name);
123 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
124 }
125
126 #[inline]
128 pub fn class_like_exists(&self, name: &str) -> bool {
129 let lowercase_name = ascii_lowercase_atom(name);
130 self.symbols.contains(&lowercase_name)
131 }
132
133 #[inline]
135 pub fn class_or_trait_exists(&self, name: &str) -> bool {
136 let lowercase_name = ascii_lowercase_atom(name);
137 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
138 }
139
140 #[inline]
142 pub fn class_or_interface_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::Interface))
145 }
146
147 #[inline]
149 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
150 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
151 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
152 let identifier = (lowercase_class, lowercase_method);
153 self.function_likes.contains_key(&identifier)
154 }
155
156 #[inline]
158 pub fn function_exists(&self, name: &str) -> bool {
159 let lowercase_name = ascii_lowercase_atom(name);
160 let identifier = (empty_atom(), lowercase_name);
161 self.function_likes.contains_key(&identifier)
162 }
163
164 #[inline]
167 pub fn constant_exists(&self, name: &str) -> bool {
168 let lowercase_name = ascii_lowercase_constant_name_atom(name);
169 self.constants.contains_key(&lowercase_name)
170 }
171
172 #[inline]
174 pub fn method_exists(&self, class: &str, method: &str) -> bool {
175 let lowercase_class = ascii_lowercase_atom(class);
176 let lowercase_method = ascii_lowercase_atom(method);
177 self.class_likes
178 .get(&lowercase_class)
179 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
180 }
181
182 #[inline]
185 pub fn property_exists(&self, class: &str, property: &str) -> bool {
186 let lowercase_class = ascii_lowercase_atom(class);
187 let property_name = atom(property);
188 self.class_likes
189 .get(&lowercase_class)
190 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
191 }
192
193 #[inline]
196 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
197 let lowercase_class = ascii_lowercase_atom(class);
198 let constant_name = atom(constant);
199 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
200 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
201 })
202 }
203
204 #[inline]
206 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
207 let lowercase_class = ascii_lowercase_atom(class);
208 let lowercase_method = ascii_lowercase_atom(method);
209 self.class_likes
210 .get(&lowercase_class)
211 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
212 .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
213 }
214
215 #[inline]
217 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
218 let lowercase_class = ascii_lowercase_atom(class);
219 let property_name = atom(property);
220 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
221 }
222 #[inline]
227 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
228 let lowercase_name = ascii_lowercase_atom(name);
229 if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
230 }
231
232 #[inline]
234 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
235 let lowercase_name = ascii_lowercase_atom(name);
236 if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
237 }
238
239 #[inline]
241 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
242 let lowercase_name = ascii_lowercase_atom(name);
243 if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
244 }
245
246 #[inline]
248 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
249 let lowercase_name = ascii_lowercase_atom(name);
250 if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
251 }
252
253 #[inline]
255 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
256 let lowercase_name = ascii_lowercase_atom(name);
257 self.class_likes.get(&lowercase_name)
258 }
259 #[inline]
263 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
264 let lowercase_name = ascii_lowercase_atom(name);
265 let identifier = (empty_atom(), lowercase_name);
266 self.function_likes.get(&identifier)
267 }
268
269 #[inline]
271 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
272 let lowercase_class = ascii_lowercase_atom(class);
273 let lowercase_method = ascii_lowercase_atom(method);
274 let identifier = (lowercase_class, lowercase_method);
275 self.function_likes.get(&identifier)
276 }
277
278 #[inline]
280 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
281 let file_ref = u64_atom(file_id.as_u64());
282 let closure_ref = u32_atom(position.offset);
283 let identifier = (file_ref, closure_ref);
284 self.function_likes.get(&identifier)
285 }
286
287 #[inline]
289 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
290 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
291 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
292 let identifier = (lowercase_class, lowercase_method);
293 self.function_likes.get(&identifier)
294 }
295
296 #[inline]
299 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
300 let method_id = MethodIdentifier::new(atom(class), atom(method));
301 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
302 self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
303 }
304
305 #[inline]
308 pub fn get_function_like(
309 &self,
310 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
311 ) -> Option<&FunctionLikeMetadata> {
312 use crate::identifier::function_like::FunctionLikeIdentifier;
313 match identifier {
314 FunctionLikeIdentifier::Function(name) => self.get_function(name),
315 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
316 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
317 }
318 }
319 #[inline]
324 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
325 let lowercase_name = ascii_lowercase_constant_name_atom(name);
326 self.constants.get(&lowercase_name)
327 }
328
329 #[inline]
332 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
333 let lowercase_class = ascii_lowercase_atom(class);
334 let constant_name = atom(constant);
335 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
336 }
337
338 #[inline]
340 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
341 let lowercase_class = ascii_lowercase_atom(class);
342 let case_name = atom(case);
343 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
344 }
345 #[inline]
350 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
351 let lowercase_class = ascii_lowercase_atom(class);
352 let property_name = atom(property);
353 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
354 }
355
356 #[inline]
358 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
359 let lowercase_class = ascii_lowercase_atom(class);
360 let property_name = atom(property);
361 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
362 self.class_likes.get(declaring_class)?.properties.get(&property_name)
363 }
364 #[inline]
368 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
369 let lowercase_class = ascii_lowercase_atom(class);
370 let property_name = atom(property);
371 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
372 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
373 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
374 }
375
376 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
378 let lowercase_class = ascii_lowercase_atom(class);
379 let constant_name = atom(constant);
380 let class_meta = self.class_likes.get(&lowercase_class)?;
381
382 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
384 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
385 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
386 }
387
388 let constant_meta = class_meta.constants.get(&constant_name)?;
390
391 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
393 return Some(Cow::Borrowed(&type_meta.type_union));
394 }
395
396 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
398 }
399
400 #[inline]
402 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
403 let lowercase_class = ascii_lowercase_atom(class);
404 let constant_name = atom(constant);
405 self.class_likes
406 .get(&lowercase_class)
407 .and_then(|meta| meta.constants.get(&constant_name))
408 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
409 }
410 #[inline]
414 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
415 let lowercase_child = ascii_lowercase_atom(child);
416 let lowercase_parent = ascii_lowercase_atom(parent);
417 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
418 }
419
420 #[inline]
422 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
423 let lowercase_child = ascii_lowercase_atom(child);
424 let lowercase_parent = ascii_lowercase_atom(parent);
425 self.class_likes
426 .get(&lowercase_child)
427 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
428 }
429
430 #[inline]
432 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
433 let lowercase_class = ascii_lowercase_atom(class);
434 let lowercase_interface = ascii_lowercase_atom(interface);
435 self.class_likes
436 .get(&lowercase_class)
437 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
438 }
439
440 #[inline]
442 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
443 let lowercase_class = ascii_lowercase_atom(class);
444 let lowercase_interface = ascii_lowercase_atom(interface);
445 self.class_likes
446 .get(&lowercase_class)
447 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
448 }
449
450 #[inline]
452 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
453 let lowercase_class = ascii_lowercase_atom(class);
454 let lowercase_trait = ascii_lowercase_atom(trait_name);
455 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
456 }
457
458 #[inline]
460 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
461 if child == parent {
462 return true;
463 }
464
465 let lowercase_child = ascii_lowercase_atom(child);
466 let lowercase_parent = ascii_lowercase_atom(parent);
467
468 if lowercase_child == lowercase_parent {
469 return true;
470 }
471
472 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
473 meta.all_parent_classes.contains(&lowercase_parent)
474 || meta.all_parent_interfaces.contains(&lowercase_parent)
475 })
476 }
477
478 #[inline]
480 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
481 let lowercase_name = ascii_lowercase_atom(name);
482 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
483 }
484
485 #[inline]
488 pub fn is_inheritable(&self, name: &str) -> bool {
489 let lowercase_name = ascii_lowercase_atom(name);
490 match self.symbols.get_kind(&lowercase_name) {
491 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
492 Some(SymbolKind::Enum) => false,
493 Some(SymbolKind::Interface) | Some(SymbolKind::Trait) | None => true,
494 }
495 }
496
497 #[inline]
499 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
500 let lowercase_class = ascii_lowercase_atom(class);
501 let mut all_descendants = AtomSet::default();
502 let mut queue = vec![&lowercase_class];
503 let mut visited = AtomSet::default();
504 visited.insert(lowercase_class);
505
506 while let Some(current_name) = queue.pop() {
507 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
508 for descendant in direct_descendants {
509 if visited.insert(*descendant) {
510 all_descendants.insert(*descendant);
511 queue.push(descendant);
512 }
513 }
514 }
515 }
516
517 all_descendants
518 }
519
520 #[inline]
522 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
523 let lowercase_class = ascii_lowercase_atom(class);
524 let mut ancestors = AtomSet::default();
525 if let Some(meta) = self.class_likes.get(&lowercase_class) {
526 ancestors.extend(meta.all_parent_classes.iter().copied());
527 ancestors.extend(meta.all_parent_interfaces.iter().copied());
528 }
529 ancestors
530 }
531 #[inline]
535 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
536 let lowercase_class = ascii_lowercase_atom(class);
537 let lowercase_method = ascii_lowercase_atom(method);
538
539 self.class_likes
540 .get(&lowercase_class)?
541 .declaring_method_ids
542 .get(&lowercase_method)
543 .map(|method_id| *method_id.get_class_name())
544 }
545
546 #[inline]
548 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
549 let lowercase_class = ascii_lowercase_atom(class);
550 let lowercase_method = ascii_lowercase_atom(method);
551 self.class_likes
552 .get(&lowercase_class)?
553 .appearing_method_ids
554 .get(&lowercase_method)
555 .map(|method_id| *method_id.get_class_name())
556 }
557
558 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
560 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
561 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
562
563 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
564 return *method_id;
565 };
566
567 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
568 return *declaring_method_id;
569 }
570
571 if class_meta.flags.is_abstract()
572 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
573 && let Some((_, first_method_id)) = overridden_map.iter().next()
574 {
575 return *first_method_id;
576 }
577
578 *method_id
579 }
580
581 #[inline]
583 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
584 let lowercase_class = ascii_lowercase_atom(class);
585 let lowercase_method = ascii_lowercase_atom(method);
586 self.class_likes
587 .get(&lowercase_class)
588 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
589 }
590
591 #[inline]
593 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
594 let lowercase_class = ascii_lowercase_atom(class);
595 let lowercase_method = ascii_lowercase_atom(method);
596 let identifier = (lowercase_class, lowercase_method);
597 self.function_likes
598 .get(&identifier)
599 .and_then(|meta| meta.method_metadata.as_ref())
600 .is_some_and(|method_meta| method_meta.is_abstract)
601 }
602
603 #[inline]
605 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
606 let lowercase_class = ascii_lowercase_atom(class);
607 let lowercase_method = ascii_lowercase_atom(method);
608 let identifier = (lowercase_class, lowercase_method);
609 self.function_likes
610 .get(&identifier)
611 .and_then(|meta| meta.method_metadata.as_ref())
612 .is_some_and(|method_meta| method_meta.is_static)
613 }
614
615 #[inline]
617 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
618 let lowercase_class = ascii_lowercase_atom(class);
619 let lowercase_method = ascii_lowercase_atom(method);
620 let identifier = (lowercase_class, lowercase_method);
621 self.function_likes
622 .get(&identifier)
623 .and_then(|meta| meta.method_metadata.as_ref())
624 .is_some_and(|method_meta| method_meta.is_final)
625 }
626
627 #[inline]
633 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
634 let lowercase_class = ascii_lowercase_atom(class);
635 let lowercase_method = ascii_lowercase_atom(method);
636
637 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
639 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
640 {
641 return Some(*overridden_visibility);
642 }
643
644 let declaring_class = self.get_declaring_method_class(class, method)?;
646 let identifier = (declaring_class, lowercase_method);
647
648 self.function_likes
649 .get(&identifier)
650 .and_then(|meta| meta.method_metadata.as_ref())
651 .map(|method_meta| method_meta.visibility)
652 }
653
654 pub fn get_function_like_thrown_types<'a>(
656 &'a self,
657 class_like: Option<&'a ClassLikeMetadata>,
658 function_like: &'a FunctionLikeMetadata,
659 ) -> &'a [TypeMetadata] {
660 if !function_like.thrown_types.is_empty() {
661 return function_like.thrown_types.as_slice();
662 }
663
664 if !function_like.kind.is_method() {
665 return &[];
666 }
667
668 let Some(class_like) = class_like else {
669 return &[];
670 };
671
672 let Some(method_name) = function_like.name.as_ref() else {
673 return &[];
674 };
675
676 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
677 for (parent_class_name, parent_method_id) in overridden_map {
678 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
679 continue;
680 };
681
682 let parent_method_key = (*parent_method_id.get_class_name(), *parent_method_id.get_method_name());
683 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
684 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
685 if !thrown.is_empty() {
686 return thrown;
687 }
688 }
689 }
690 }
691
692 &[]
693 }
694 #[inline]
698 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
699 let lowercase_class = ascii_lowercase_atom(class);
700 let property_name = atom(property);
701 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
702 }
703
704 #[inline]
706 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
707 let lowercase_class = ascii_lowercase_atom(class);
708 let property_name = atom(property);
709 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
710 }
711
712 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
714 let lowercase_class = ascii_lowercase_atom(class);
715 let mut all_descendants = AtomSet::default();
716 let mut queue = vec![&lowercase_class];
717 let mut visited = AtomSet::default();
718 visited.insert(lowercase_class);
719
720 while let Some(current_name) = queue.pop() {
721 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
722 for descendant in direct_descendants {
723 if visited.insert(*descendant) {
724 all_descendants.insert(*descendant);
725 queue.push(descendant);
726 }
727 }
728 }
729 }
730
731 all_descendants
732 }
733
734 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
736 use std::io::Write;
737
738 let mut buffer = [0u8; 64];
739 let mut writer = &mut buffer[..];
740
741 unsafe {
742 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
743 .unwrap_unchecked()
744 };
745
746 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
747
748 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
749 }
750
751 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
753 let name = Self::get_anonymous_class_name(span);
754 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
755 }
756
757 #[inline]
767 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
768 self.file_signatures.get(file_id)
769 }
770
771 #[inline]
782 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
783 self.file_signatures.insert(file_id, signature)
784 }
785
786 #[inline]
796 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
797 self.file_signatures.remove(file_id)
798 }
799
800 pub fn extend(&mut self, other: CodebaseMetadata) {
804 for (k, v) in other.class_likes {
805 let metadata_to_keep = match self.class_likes.entry(k) {
806 Entry::Occupied(entry) => {
807 let existing = entry.remove();
808 if v.flags.is_user_defined() {
809 v
810 } else if existing.flags.is_user_defined() {
811 existing
812 } else if v.flags.is_built_in() {
813 v
814 } else if existing.flags.is_built_in() {
815 existing
816 } else {
817 v
818 }
819 }
820 Entry::Vacant(_) => v,
821 };
822 self.class_likes.insert(k, metadata_to_keep);
823 }
824
825 for (k, v) in other.function_likes {
826 let metadata_to_keep = match self.function_likes.entry(k) {
827 Entry::Occupied(entry) => {
828 let existing = entry.remove();
829 if v.flags.is_user_defined() {
830 v
831 } else if existing.flags.is_user_defined() {
832 existing
833 } else if v.flags.is_built_in() {
834 v
835 } else if existing.flags.is_built_in() {
836 existing
837 } else {
838 v
839 }
840 }
841 Entry::Vacant(_) => v,
842 };
843 self.function_likes.insert(k, metadata_to_keep);
844 }
845
846 for (k, v) in other.constants {
847 let metadata_to_keep = match self.constants.entry(k) {
848 Entry::Occupied(entry) => {
849 let existing = entry.remove();
850 if v.flags.is_user_defined() {
851 v
852 } else if existing.flags.is_user_defined() {
853 existing
854 } else if v.flags.is_built_in() {
855 v
856 } else if existing.flags.is_built_in() {
857 existing
858 } else {
859 v
860 }
861 }
862 Entry::Vacant(_) => v,
863 };
864 self.constants.insert(k, metadata_to_keep);
865 }
866
867 self.symbols.extend(other.symbols);
868
869 for (k, v) in other.all_class_like_descendants {
870 self.all_class_like_descendants.entry(k).or_default().extend(v);
871 }
872
873 for (k, v) in other.direct_classlike_descendants {
874 self.direct_classlike_descendants.entry(k).or_default().extend(v);
875 }
876
877 self.safe_symbols.extend(other.safe_symbols);
878 self.safe_symbol_members.extend(other.safe_symbol_members);
879 self.infer_types_from_usage |= other.infer_types_from_usage;
880 }
881
882 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
884 let mut issues = IssueCollection::new();
885
886 for meta in self.class_likes.values_mut() {
887 if user_defined && !meta.flags.is_user_defined() {
888 continue;
889 }
890 issues.extend(meta.take_issues());
891 }
892
893 for meta in self.function_likes.values_mut() {
894 if user_defined && !meta.flags.is_user_defined() {
895 continue;
896 }
897 issues.extend(meta.take_issues());
898 }
899
900 for meta in self.constants.values_mut() {
901 if user_defined && !meta.flags.is_user_defined() {
902 continue;
903 }
904 issues.extend(meta.take_issues());
905 }
906
907 issues
908 }
909
910 pub fn get_all_file_ids(&self) -> Vec<FileId> {
914 self.file_signatures.keys().copied().collect()
915 }
916}
917
918impl Default for CodebaseMetadata {
919 #[inline]
920 fn default() -> Self {
921 Self {
922 class_likes: AtomMap::default(),
923 function_likes: HashMap::default(),
924 symbols: Symbols::new(),
925 infer_types_from_usage: false,
926 constants: AtomMap::default(),
927 all_class_like_descendants: AtomMap::default(),
928 direct_classlike_descendants: AtomMap::default(),
929 safe_symbols: AtomSet::default(),
930 safe_symbol_members: HashSet::default(),
931 file_signatures: HashMap::default(),
932 }
933 }
934}