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