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