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::symbol::SymbolKind;
31use crate::symbol::Symbols;
32use crate::ttype::atomic::TAtomic;
33use crate::ttype::atomic::object::TObject;
34use crate::ttype::union::TUnion;
35use crate::visibility::Visibility;
36
37pub mod attribute;
38pub mod class_like;
39pub mod class_like_constant;
40pub mod constant;
41pub mod enum_case;
42pub mod flags;
43pub mod function_like;
44pub mod parameter;
45pub mod property;
46pub mod ttype;
47
48#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
54pub struct CodebaseMetadata {
55 pub infer_types_from_usage: bool,
57 pub aliases: AtomMap<TypeMetadata>,
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}
77
78impl CodebaseMetadata {
79 #[inline]
83 pub fn new() -> Self {
84 Self::default()
85 }
86 #[inline]
97 pub fn class_exists(&self, name: &str) -> bool {
98 let lowercase_name = ascii_lowercase_atom(name);
99 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
100 }
101
102 #[inline]
104 pub fn interface_exists(&self, name: &str) -> bool {
105 let lowercase_name = ascii_lowercase_atom(name);
106 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
107 }
108
109 #[inline]
111 pub fn trait_exists(&self, name: &str) -> bool {
112 let lowercase_name = ascii_lowercase_atom(name);
113 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
114 }
115
116 #[inline]
118 pub fn enum_exists(&self, name: &str) -> bool {
119 let lowercase_name = ascii_lowercase_atom(name);
120 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
121 }
122
123 #[inline]
125 pub fn class_like_exists(&self, name: &str) -> bool {
126 let lowercase_name = ascii_lowercase_atom(name);
127 self.symbols.contains(&lowercase_name)
128 }
129
130 #[inline]
132 pub fn class_or_trait_exists(&self, name: &str) -> bool {
133 let lowercase_name = ascii_lowercase_atom(name);
134 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
135 }
136
137 #[inline]
139 pub fn class_or_interface_exists(&self, name: &str) -> bool {
140 let lowercase_name = ascii_lowercase_atom(name);
141 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
142 }
143
144 #[inline]
146 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
147 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
148 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
149 let identifier = (lowercase_class, lowercase_method);
150 self.function_likes.contains_key(&identifier)
151 }
152
153 #[inline]
155 pub fn function_exists(&self, name: &str) -> bool {
156 let lowercase_name = ascii_lowercase_atom(name);
157 let identifier = (empty_atom(), lowercase_name);
158 self.function_likes.contains_key(&identifier)
159 }
160
161 #[inline]
164 pub fn constant_exists(&self, name: &str) -> bool {
165 let lowercase_name = ascii_lowercase_constant_name_atom(name);
166 self.constants.contains_key(&lowercase_name)
167 }
168
169 #[inline]
171 pub fn method_exists(&self, class: &str, method: &str) -> bool {
172 let lowercase_class = ascii_lowercase_atom(class);
173 let lowercase_method = ascii_lowercase_atom(method);
174 self.class_likes
175 .get(&lowercase_class)
176 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
177 }
178
179 #[inline]
182 pub fn property_exists(&self, class: &str, property: &str) -> bool {
183 let lowercase_class = ascii_lowercase_atom(class);
184 let property_name = atom(property);
185 self.class_likes
186 .get(&lowercase_class)
187 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
188 }
189
190 #[inline]
193 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
194 let lowercase_class = ascii_lowercase_atom(class);
195 let constant_name = atom(constant);
196 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
197 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
198 })
199 }
200
201 #[inline]
203 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
204 let lowercase_class = ascii_lowercase_atom(class);
205 let lowercase_method = ascii_lowercase_atom(method);
206 self.class_likes
207 .get(&lowercase_class)
208 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
209 .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
210 }
211
212 #[inline]
214 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
215 let lowercase_class = ascii_lowercase_atom(class);
216 let property_name = atom(property);
217 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
218 }
219 #[inline]
224 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
225 let lowercase_name = ascii_lowercase_atom(name);
226 if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
227 }
228
229 #[inline]
231 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
232 let lowercase_name = ascii_lowercase_atom(name);
233 if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
234 }
235
236 #[inline]
238 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
239 let lowercase_name = ascii_lowercase_atom(name);
240 if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
241 }
242
243 #[inline]
245 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
246 let lowercase_name = ascii_lowercase_atom(name);
247 if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
248 }
249
250 #[inline]
252 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
253 let lowercase_name = ascii_lowercase_atom(name);
254 self.class_likes.get(&lowercase_name)
255 }
256 #[inline]
260 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
261 let lowercase_name = ascii_lowercase_atom(name);
262 let identifier = (empty_atom(), lowercase_name);
263 self.function_likes.get(&identifier)
264 }
265
266 #[inline]
268 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
269 let lowercase_class = ascii_lowercase_atom(class);
270 let lowercase_method = ascii_lowercase_atom(method);
271 let identifier = (lowercase_class, lowercase_method);
272 self.function_likes.get(&identifier)
273 }
274
275 #[inline]
277 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
278 let file_ref = u64_atom(file_id.as_u64());
279 let closure_ref = u32_atom(position.offset);
280 let identifier = (file_ref, closure_ref);
281 self.function_likes.get(&identifier)
282 }
283
284 #[inline]
286 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
287 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
288 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
289 let identifier = (lowercase_class, lowercase_method);
290 self.function_likes.get(&identifier)
291 }
292
293 #[inline]
296 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
297 let method_id = MethodIdentifier::new(atom(class), atom(method));
298 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
299 self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
300 }
301
302 #[inline]
305 pub fn get_function_like(
306 &self,
307 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
308 ) -> Option<&FunctionLikeMetadata> {
309 use crate::identifier::function_like::FunctionLikeIdentifier;
310 match identifier {
311 FunctionLikeIdentifier::Function(name) => self.get_function(name),
312 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
313 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
314 }
315 }
316 #[inline]
321 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
322 let lowercase_name = ascii_lowercase_constant_name_atom(name);
323 self.constants.get(&lowercase_name)
324 }
325
326 #[inline]
329 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
330 let lowercase_class = ascii_lowercase_atom(class);
331 let constant_name = atom(constant);
332 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
333 }
334
335 #[inline]
337 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
338 let lowercase_class = ascii_lowercase_atom(class);
339 let case_name = atom(case);
340 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
341 }
342 #[inline]
347 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
348 let lowercase_class = ascii_lowercase_atom(class);
349 let property_name = atom(property);
350 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
351 }
352
353 #[inline]
355 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
356 let lowercase_class = ascii_lowercase_atom(class);
357 let property_name = atom(property);
358 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
359 self.class_likes.get(declaring_class)?.properties.get(&property_name)
360 }
361 #[inline]
365 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
366 let lowercase_class = ascii_lowercase_atom(class);
367 let property_name = atom(property);
368 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
369 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
370 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
371 }
372
373 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
375 let lowercase_class = ascii_lowercase_atom(class);
376 let constant_name = atom(constant);
377 let class_meta = self.class_likes.get(&lowercase_class)?;
378
379 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
381 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
382 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
383 }
384
385 let constant_meta = class_meta.constants.get(&constant_name)?;
387
388 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
390 return Some(Cow::Borrowed(&type_meta.type_union));
391 }
392
393 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
395 }
396
397 #[inline]
399 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
400 let lowercase_class = ascii_lowercase_atom(class);
401 let constant_name = atom(constant);
402 self.class_likes
403 .get(&lowercase_class)
404 .and_then(|meta| meta.constants.get(&constant_name))
405 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
406 }
407 #[inline]
411 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
412 let lowercase_child = ascii_lowercase_atom(child);
413 let lowercase_parent = ascii_lowercase_atom(parent);
414 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
415 }
416
417 #[inline]
419 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
420 let lowercase_child = ascii_lowercase_atom(child);
421 let lowercase_parent = ascii_lowercase_atom(parent);
422 self.class_likes
423 .get(&lowercase_child)
424 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
425 }
426
427 #[inline]
429 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
430 let lowercase_class = ascii_lowercase_atom(class);
431 let lowercase_interface = ascii_lowercase_atom(interface);
432 self.class_likes
433 .get(&lowercase_class)
434 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
435 }
436
437 #[inline]
439 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
440 let lowercase_class = ascii_lowercase_atom(class);
441 let lowercase_interface = ascii_lowercase_atom(interface);
442 self.class_likes
443 .get(&lowercase_class)
444 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
445 }
446
447 #[inline]
449 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
450 let lowercase_class = ascii_lowercase_atom(class);
451 let lowercase_trait = ascii_lowercase_atom(trait_name);
452 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
453 }
454
455 #[inline]
457 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
458 if child == parent {
459 return true;
460 }
461
462 let lowercase_child = ascii_lowercase_atom(child);
463 let lowercase_parent = ascii_lowercase_atom(parent);
464
465 if lowercase_child == lowercase_parent {
466 return true;
467 }
468
469 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
470 meta.all_parent_classes.contains(&lowercase_parent)
471 || meta.all_parent_interfaces.contains(&lowercase_parent)
472 })
473 }
474
475 #[inline]
477 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
478 let lowercase_name = ascii_lowercase_atom(name);
479 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
480 }
481
482 #[inline]
485 pub fn is_inheritable(&self, name: &str) -> bool {
486 let lowercase_name = ascii_lowercase_atom(name);
487 match self.symbols.get_kind(&lowercase_name) {
488 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
489 Some(SymbolKind::Enum) => false,
490 Some(SymbolKind::Interface) | Some(SymbolKind::Trait) | None => true,
491 }
492 }
493
494 #[inline]
496 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
497 let lowercase_class = ascii_lowercase_atom(class);
498 let mut all_descendants = AtomSet::default();
499 let mut queue = vec![&lowercase_class];
500 let mut visited = AtomSet::default();
501 visited.insert(lowercase_class);
502
503 while let Some(current_name) = queue.pop() {
504 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
505 for descendant in direct_descendants {
506 if visited.insert(*descendant) {
507 all_descendants.insert(*descendant);
508 queue.push(descendant);
509 }
510 }
511 }
512 }
513
514 all_descendants
515 }
516
517 #[inline]
519 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
520 let lowercase_class = ascii_lowercase_atom(class);
521 let mut ancestors = AtomSet::default();
522 if let Some(meta) = self.class_likes.get(&lowercase_class) {
523 ancestors.extend(meta.all_parent_classes.iter().copied());
524 ancestors.extend(meta.all_parent_interfaces.iter().copied());
525 }
526 ancestors
527 }
528 #[inline]
532 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
533 let lowercase_class = ascii_lowercase_atom(class);
534 let lowercase_method = ascii_lowercase_atom(method);
535
536 self.class_likes
537 .get(&lowercase_class)?
538 .declaring_method_ids
539 .get(&lowercase_method)
540 .map(|method_id| *method_id.get_class_name())
541 }
542
543 #[inline]
545 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
546 let lowercase_class = ascii_lowercase_atom(class);
547 let lowercase_method = ascii_lowercase_atom(method);
548 self.class_likes
549 .get(&lowercase_class)?
550 .appearing_method_ids
551 .get(&lowercase_method)
552 .map(|method_id| *method_id.get_class_name())
553 }
554
555 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
557 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
558 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
559
560 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
561 return *method_id;
562 };
563
564 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
565 return *declaring_method_id;
566 }
567
568 if class_meta.flags.is_abstract()
569 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
570 && let Some((_, first_method_id)) = overridden_map.iter().next()
571 {
572 return *first_method_id;
573 }
574
575 *method_id
576 }
577
578 #[inline]
580 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
581 let lowercase_class = ascii_lowercase_atom(class);
582 let lowercase_method = ascii_lowercase_atom(method);
583 self.class_likes
584 .get(&lowercase_class)
585 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
586 }
587
588 #[inline]
590 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
591 let lowercase_class = ascii_lowercase_atom(class);
592 let lowercase_method = ascii_lowercase_atom(method);
593 let identifier = (lowercase_class, lowercase_method);
594 self.function_likes
595 .get(&identifier)
596 .and_then(|meta| meta.method_metadata.as_ref())
597 .is_some_and(|method_meta| method_meta.is_abstract)
598 }
599
600 #[inline]
602 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
603 let lowercase_class = ascii_lowercase_atom(class);
604 let lowercase_method = ascii_lowercase_atom(method);
605 let identifier = (lowercase_class, lowercase_method);
606 self.function_likes
607 .get(&identifier)
608 .and_then(|meta| meta.method_metadata.as_ref())
609 .is_some_and(|method_meta| method_meta.is_static)
610 }
611
612 #[inline]
614 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
615 let lowercase_class = ascii_lowercase_atom(class);
616 let lowercase_method = ascii_lowercase_atom(method);
617 let identifier = (lowercase_class, lowercase_method);
618 self.function_likes
619 .get(&identifier)
620 .and_then(|meta| meta.method_metadata.as_ref())
621 .is_some_and(|method_meta| method_meta.is_final)
622 }
623
624 #[inline]
630 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
631 let lowercase_class = ascii_lowercase_atom(class);
632 let lowercase_method = ascii_lowercase_atom(method);
633
634 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
636 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
637 {
638 return Some(*overridden_visibility);
639 }
640
641 let declaring_class = self.get_declaring_method_class(class, method)?;
643 let identifier = (declaring_class, lowercase_method);
644
645 self.function_likes
646 .get(&identifier)
647 .and_then(|meta| meta.method_metadata.as_ref())
648 .map(|method_meta| method_meta.visibility)
649 }
650
651 pub fn get_function_like_thrown_types<'a>(
653 &'a self,
654 class_like: Option<&'a ClassLikeMetadata>,
655 function_like: &'a FunctionLikeMetadata,
656 ) -> &'a [TypeMetadata] {
657 if !function_like.thrown_types.is_empty() {
658 return function_like.thrown_types.as_slice();
659 }
660
661 if !function_like.kind.is_method() {
662 return &[];
663 }
664
665 let Some(class_like) = class_like else {
666 return &[];
667 };
668
669 let Some(method_name) = function_like.name.as_ref() else {
670 return &[];
671 };
672
673 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
674 for (parent_class_name, parent_method_id) in overridden_map {
675 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
676 continue;
677 };
678
679 let parent_method_key = (*parent_method_id.get_class_name(), *parent_method_id.get_method_name());
680 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
681 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
682 if !thrown.is_empty() {
683 return thrown;
684 }
685 }
686 }
687 }
688
689 &[]
690 }
691 #[inline]
695 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
696 let lowercase_class = ascii_lowercase_atom(class);
697 let property_name = atom(property);
698 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
699 }
700
701 #[inline]
703 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
704 let lowercase_class = ascii_lowercase_atom(class);
705 let property_name = atom(property);
706 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
707 }
708
709 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
711 let lowercase_class = ascii_lowercase_atom(class);
712 let mut all_descendants = AtomSet::default();
713 let mut queue = vec![&lowercase_class];
714 let mut visited = AtomSet::default();
715 visited.insert(lowercase_class);
716
717 while let Some(current_name) = queue.pop() {
718 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
719 for descendant in direct_descendants {
720 if visited.insert(*descendant) {
721 all_descendants.insert(*descendant);
722 queue.push(descendant);
723 }
724 }
725 }
726 }
727
728 all_descendants
729 }
730
731 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
733 use std::io::Write;
734
735 let mut buffer = [0u8; 64];
736 let mut writer = &mut buffer[..];
737
738 unsafe {
739 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
740 .unwrap_unchecked()
741 };
742
743 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
744
745 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
746 }
747
748 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
750 let name = Self::get_anonymous_class_name(span);
751 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
752 }
753 pub fn extend(&mut self, other: CodebaseMetadata) {
757 for (k, v) in other.aliases {
758 self.aliases.entry(k).or_insert(v);
759 }
760
761 for (k, v) in other.class_likes {
762 let metadata_to_keep = match self.class_likes.entry(k) {
763 Entry::Occupied(entry) => {
764 let existing = entry.remove();
765 if v.flags.is_user_defined() {
766 v
767 } else if existing.flags.is_user_defined() {
768 existing
769 } else if v.flags.is_built_in() {
770 v
771 } else if existing.flags.is_built_in() {
772 existing
773 } else {
774 v
775 }
776 }
777 Entry::Vacant(_) => v,
778 };
779 self.class_likes.insert(k, metadata_to_keep);
780 }
781
782 for (k, v) in other.function_likes {
783 let metadata_to_keep = match self.function_likes.entry(k) {
784 Entry::Occupied(entry) => {
785 let existing = entry.remove();
786 if v.flags.is_user_defined() {
787 v
788 } else if existing.flags.is_user_defined() {
789 existing
790 } else if v.flags.is_built_in() {
791 v
792 } else if existing.flags.is_built_in() {
793 existing
794 } else {
795 v
796 }
797 }
798 Entry::Vacant(_) => v,
799 };
800 self.function_likes.insert(k, metadata_to_keep);
801 }
802
803 for (k, v) in other.constants {
804 let metadata_to_keep = match self.constants.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.constants.insert(k, metadata_to_keep);
822 }
823
824 self.symbols.extend(other.symbols);
825
826 for (k, v) in other.all_class_like_descendants {
827 self.all_class_like_descendants.entry(k).or_default().extend(v);
828 }
829
830 for (k, v) in other.direct_classlike_descendants {
831 self.direct_classlike_descendants.entry(k).or_default().extend(v);
832 }
833
834 self.safe_symbols.extend(other.safe_symbols);
835 self.safe_symbol_members.extend(other.safe_symbol_members);
836 self.infer_types_from_usage |= other.infer_types_from_usage;
837 }
838
839 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
841 let mut issues = IssueCollection::new();
842
843 for meta in self.class_likes.values_mut() {
844 if user_defined && !meta.flags.is_user_defined() {
845 continue;
846 }
847 issues.extend(meta.take_issues());
848 }
849
850 for meta in self.function_likes.values_mut() {
851 if user_defined && !meta.flags.is_user_defined() {
852 continue;
853 }
854 issues.extend(meta.take_issues());
855 }
856
857 for meta in self.constants.values_mut() {
858 if user_defined && !meta.flags.is_user_defined() {
859 continue;
860 }
861 issues.extend(meta.take_issues());
862 }
863
864 issues
865 }
866}
867
868impl Default for CodebaseMetadata {
869 #[inline]
870 fn default() -> Self {
871 Self {
872 class_likes: AtomMap::default(),
873 aliases: AtomMap::default(),
874 function_likes: HashMap::default(),
875 symbols: Symbols::new(),
876 infer_types_from_usage: false,
877 constants: AtomMap::default(),
878 all_class_like_descendants: AtomMap::default(),
879 direct_classlike_descendants: AtomMap::default(),
880 safe_symbols: AtomSet::default(),
881 safe_symbol_members: HashSet::default(),
882 }
883 }
884}