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;
21use mago_span::Span;
22
23use crate::identifier::method::MethodIdentifier;
24use crate::metadata::class_like::ClassLikeMetadata;
25use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
26use crate::metadata::constant::ConstantMetadata;
27use crate::metadata::enum_case::EnumCaseMetadata;
28use crate::metadata::flags::MetadataFlags;
29use crate::metadata::function_like::FunctionLikeMetadata;
30use crate::metadata::property::PropertyMetadata;
31use crate::metadata::ttype::TypeMetadata;
32use crate::signature::FileSignature;
33use crate::symbol::SymbolKind;
34use crate::symbol::Symbols;
35use crate::ttype::atomic::TAtomic;
36use crate::ttype::atomic::object::TObject;
37use crate::ttype::union::TUnion;
38use crate::visibility::Visibility;
39
40pub mod attribute;
41pub mod class_like;
42pub mod class_like_constant;
43pub mod constant;
44pub mod enum_case;
45pub mod flags;
46pub mod function_like;
47pub mod parameter;
48pub mod property;
49pub mod property_hook;
50pub mod ttype;
51
52#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
58#[non_exhaustive]
59pub struct CodebaseMetadata {
60 pub infer_types_from_usage: bool,
62 pub class_likes: AtomMap<ClassLikeMetadata>,
64 pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
67 pub symbols: Symbols,
69 pub constants: AtomMap<ConstantMetadata>,
71 pub all_class_like_descendants: AtomMap<AtomSet>,
73 pub direct_classlike_descendants: AtomMap<AtomSet>,
75 pub safe_symbols: AtomSet,
77 pub safe_symbol_members: HashSet<(Atom, Atom)>,
79 pub file_signatures: HashMap<FileId, FileSignature>,
82}
83
84impl CodebaseMetadata {
85 #[inline]
87 #[must_use]
88 pub fn new() -> Self {
89 Self::default()
90 }
91
92 #[inline]
101 #[must_use]
102 pub fn class_exists(&self, name: &str) -> bool {
103 let lowercase_name = ascii_lowercase_atom(name);
104 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class))
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn interface_exists(&self, name: &str) -> bool {
111 let lowercase_name = ascii_lowercase_atom(name);
112 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Interface))
113 }
114
115 #[inline]
117 #[must_use]
118 pub fn trait_exists(&self, name: &str) -> bool {
119 let lowercase_name = ascii_lowercase_atom(name);
120 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Trait))
121 }
122
123 #[inline]
125 #[must_use]
126 pub fn enum_exists(&self, name: &str) -> bool {
127 let lowercase_name = ascii_lowercase_atom(name);
128 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Enum))
129 }
130
131 #[inline]
133 #[must_use]
134 pub fn class_like_exists(&self, name: &str) -> bool {
135 let lowercase_name = ascii_lowercase_atom(name);
136 self.symbols.contains(&lowercase_name)
137 }
138
139 #[inline]
141 #[must_use]
142 pub fn namespace_exists(&self, name: &str) -> bool {
143 let lowercase_name = ascii_lowercase_atom(name);
144 self.symbols.contains_namespace(&lowercase_name)
145 }
146
147 #[inline]
149 #[must_use]
150 pub fn class_or_trait_exists(&self, name: &str) -> bool {
151 let lowercase_name = ascii_lowercase_atom(name);
152 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn class_or_interface_exists(&self, name: &str) -> bool {
159 let lowercase_name = ascii_lowercase_atom(name);
160 matches!(self.symbols.get_kind(&lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
161 }
162
163 #[inline]
165 #[must_use]
166 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
167 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
168 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
169 let identifier = (lowercase_class, lowercase_method);
170 self.function_likes.contains_key(&identifier)
171 }
172
173 #[inline]
175 #[must_use]
176 pub fn function_exists(&self, name: &str) -> bool {
177 let lowercase_name = ascii_lowercase_atom(name);
178 let identifier = (empty_atom(), lowercase_name);
179 self.function_likes.contains_key(&identifier)
180 }
181
182 #[inline]
185 #[must_use]
186 pub fn constant_exists(&self, name: &str) -> bool {
187 let lowercase_name = ascii_lowercase_constant_name_atom(name);
188 self.constants.contains_key(&lowercase_name)
189 }
190
191 #[inline]
193 #[must_use]
194 pub fn method_exists(&self, class: &str, method: &str) -> bool {
195 let lowercase_class = ascii_lowercase_atom(class);
196 let lowercase_method = ascii_lowercase_atom(method);
197 self.class_likes
198 .get(&lowercase_class)
199 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
200 }
201
202 #[inline]
205 #[must_use]
206 pub fn property_exists(&self, class: &str, property: &str) -> bool {
207 let lowercase_class = ascii_lowercase_atom(class);
208 let property_name = atom(property);
209 self.class_likes
210 .get(&lowercase_class)
211 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
212 }
213
214 #[inline]
217 #[must_use]
218 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
219 let lowercase_class = ascii_lowercase_atom(class);
220 let constant_name = atom(constant);
221 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
222 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
223 })
224 }
225
226 #[inline]
228 #[must_use]
229 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
230 let lowercase_class = ascii_lowercase_atom(class);
231 let lowercase_method = ascii_lowercase_atom(method);
232 self.class_likes
233 .get(&lowercase_class)
234 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
235 .is_some_and(|method_id| method_id.get_class_name() == &lowercase_class)
236 }
237
238 #[inline]
240 #[must_use]
241 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
242 let lowercase_class = ascii_lowercase_atom(class);
243 let property_name = atom(property);
244 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
245 }
246
247 #[inline]
250 #[must_use]
251 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
252 let lowercase_name = ascii_lowercase_atom(name);
253 if self.symbols.contains_class(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
254 }
255
256 #[inline]
258 #[must_use]
259 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
260 let lowercase_name = ascii_lowercase_atom(name);
261 if self.symbols.contains_interface(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
262 }
263
264 #[inline]
266 #[must_use]
267 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
268 let lowercase_name = ascii_lowercase_atom(name);
269 if self.symbols.contains_trait(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
270 }
271
272 #[inline]
274 #[must_use]
275 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
276 let lowercase_name = ascii_lowercase_atom(name);
277 if self.symbols.contains_enum(&lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
278 }
279
280 #[inline]
282 #[must_use]
283 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
284 let lowercase_name = ascii_lowercase_atom(name);
285 self.class_likes.get(&lowercase_name)
286 }
287
288 #[inline]
290 #[must_use]
291 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
292 let lowercase_name = ascii_lowercase_atom(name);
293 let identifier = (empty_atom(), lowercase_name);
294 self.function_likes.get(&identifier)
295 }
296
297 #[inline]
299 #[must_use]
300 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
301 let lowercase_class = ascii_lowercase_atom(class);
302 let lowercase_method = ascii_lowercase_atom(method);
303 let identifier = (lowercase_class, lowercase_method);
304 self.function_likes.get(&identifier)
305 }
306
307 #[inline]
309 #[must_use]
310 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
311 let file_ref = u64_atom(file_id.as_u64());
312 let closure_ref = u32_atom(position.offset);
313 let identifier = (file_ref, closure_ref);
314 self.function_likes.get(&identifier)
315 }
316
317 #[inline]
319 #[must_use]
320 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
321 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
322 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
323 let identifier = (lowercase_class, lowercase_method);
324 self.function_likes.get(&identifier)
325 }
326
327 #[inline]
330 #[must_use]
331 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
332 let method_id = MethodIdentifier::new(atom(class), atom(method));
333 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
334 self.get_method(declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
335 }
336
337 #[inline]
340 #[must_use]
341 pub fn get_function_like(
342 &self,
343 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
344 ) -> Option<&FunctionLikeMetadata> {
345 use crate::identifier::function_like::FunctionLikeIdentifier;
346 match identifier {
347 FunctionLikeIdentifier::Function(name) => self.get_function(name),
348 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
349 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
350 }
351 }
352
353 #[inline]
356 #[must_use]
357 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
358 let lowercase_name = ascii_lowercase_constant_name_atom(name);
359 self.constants.get(&lowercase_name)
360 }
361
362 #[inline]
365 #[must_use]
366 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
367 let lowercase_class = ascii_lowercase_atom(class);
368 let constant_name = atom(constant);
369 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
370 }
371
372 #[inline]
374 #[must_use]
375 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
376 let lowercase_class = ascii_lowercase_atom(class);
377 let case_name = atom(case);
378 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
379 }
380
381 #[inline]
384 #[must_use]
385 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
386 let lowercase_class = ascii_lowercase_atom(class);
387 let property_name = atom(property);
388 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
389 }
390
391 #[inline]
393 #[must_use]
394 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
395 let lowercase_class = ascii_lowercase_atom(class);
396 let property_name = atom(property);
397 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
398 self.class_likes.get(declaring_class)?.properties.get(&property_name)
399 }
400 #[inline]
404 #[must_use]
405 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
406 let lowercase_class = ascii_lowercase_atom(class);
407 let property_name = atom(property);
408 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
409 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
410 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
411 }
412
413 #[must_use]
415 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
416 let lowercase_class = ascii_lowercase_atom(class);
417 let constant_name = atom(constant);
418 let class_meta = self.class_likes.get(&lowercase_class)?;
419
420 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
422 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
423 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
424 }
425
426 let constant_meta = class_meta.constants.get(&constant_name)?;
428
429 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
431 return Some(Cow::Borrowed(&type_meta.type_union));
432 }
433
434 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
436 }
437
438 #[inline]
440 #[must_use]
441 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
442 let lowercase_class = ascii_lowercase_atom(class);
443 let constant_name = atom(constant);
444 self.class_likes
445 .get(&lowercase_class)
446 .and_then(|meta| meta.constants.get(&constant_name))
447 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
448 }
449 #[inline]
453 #[must_use]
454 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
455 let lowercase_child = ascii_lowercase_atom(child);
456 let lowercase_parent = ascii_lowercase_atom(parent);
457 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
458 }
459
460 #[inline]
462 #[must_use]
463 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
464 let lowercase_child = ascii_lowercase_atom(child);
465 let lowercase_parent = ascii_lowercase_atom(parent);
466 self.class_likes
467 .get(&lowercase_child)
468 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
469 }
470
471 #[inline]
473 #[must_use]
474 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
475 let lowercase_class = ascii_lowercase_atom(class);
476 let lowercase_interface = ascii_lowercase_atom(interface);
477 self.class_likes
478 .get(&lowercase_class)
479 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
480 }
481
482 #[inline]
484 #[must_use]
485 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
486 let lowercase_class = ascii_lowercase_atom(class);
487 let lowercase_interface = ascii_lowercase_atom(interface);
488 self.class_likes
489 .get(&lowercase_class)
490 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
491 }
492
493 #[inline]
495 #[must_use]
496 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
497 let lowercase_class = ascii_lowercase_atom(class);
498 let lowercase_trait = ascii_lowercase_atom(trait_name);
499 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
500 }
501
502 #[inline]
505 #[must_use]
506 pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
507 let lowercase_trait = ascii_lowercase_atom(trait_name);
508
509 self.class_likes
510 .get(&lowercase_trait)
511 .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
512 }
513
514 #[inline]
516 #[must_use]
517 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
518 if child == parent {
519 return true;
520 }
521
522 let lowercase_child = ascii_lowercase_atom(child);
523 let lowercase_parent = ascii_lowercase_atom(parent);
524
525 if lowercase_child == lowercase_parent {
526 return true;
527 }
528
529 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
530 meta.all_parent_classes.contains(&lowercase_parent)
531 || meta.all_parent_interfaces.contains(&lowercase_parent)
532 || meta.used_traits.contains(&lowercase_parent)
533 || meta.require_extends.contains(&lowercase_parent)
534 || meta.require_implements.contains(&lowercase_parent)
535 })
536 }
537
538 #[inline]
540 #[must_use]
541 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
542 let lowercase_name = ascii_lowercase_atom(name);
543 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
544 }
545
546 #[inline]
549 #[must_use]
550 pub fn is_inheritable(&self, name: &str) -> bool {
551 let lowercase_name = ascii_lowercase_atom(name);
552 match self.symbols.get_kind(&lowercase_name) {
553 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
554 Some(SymbolKind::Enum) => false,
555 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
556 }
557 }
558
559 #[inline]
561 #[must_use]
562 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
563 let lowercase_class = ascii_lowercase_atom(class);
564 let mut all_descendants = AtomSet::default();
565 let mut queue = vec![&lowercase_class];
566 let mut visited = AtomSet::default();
567 visited.insert(lowercase_class);
568
569 while let Some(current_name) = queue.pop() {
570 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
571 for descendant in direct_descendants {
572 if visited.insert(*descendant) {
573 all_descendants.insert(*descendant);
574 queue.push(descendant);
575 }
576 }
577 }
578 }
579
580 all_descendants
581 }
582
583 #[inline]
585 #[must_use]
586 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
587 let lowercase_class = ascii_lowercase_atom(class);
588 let mut ancestors = AtomSet::default();
589 if let Some(meta) = self.class_likes.get(&lowercase_class) {
590 ancestors.extend(meta.all_parent_classes.iter().copied());
591 ancestors.extend(meta.all_parent_interfaces.iter().copied());
592 }
593 ancestors
594 }
595
596 #[inline]
598 #[must_use]
599 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
600 let lowercase_class = ascii_lowercase_atom(class);
601 let lowercase_method = ascii_lowercase_atom(method);
602
603 self.class_likes
604 .get(&lowercase_class)?
605 .declaring_method_ids
606 .get(&lowercase_method)
607 .map(|method_id| *method_id.get_class_name())
608 }
609
610 #[inline]
612 #[must_use]
613 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
614 let lowercase_class = ascii_lowercase_atom(class);
615 let lowercase_method = ascii_lowercase_atom(method);
616 self.class_likes
617 .get(&lowercase_class)?
618 .appearing_method_ids
619 .get(&lowercase_method)
620 .map(|method_id| *method_id.get_class_name())
621 }
622
623 #[must_use]
625 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
626 let lowercase_class = ascii_lowercase_atom(method_id.get_class_name());
627 let lowercase_method = ascii_lowercase_atom(method_id.get_method_name());
628
629 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
630 return *method_id;
631 };
632
633 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
634 return *declaring_method_id;
635 }
636
637 if class_meta.flags.is_abstract()
638 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
639 && let Some((_, first_method_id)) = overridden_map.first()
640 {
641 return *first_method_id;
642 }
643
644 *method_id
645 }
646
647 #[inline]
649 #[must_use]
650 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
651 let lowercase_class = ascii_lowercase_atom(class);
652 let lowercase_method = ascii_lowercase_atom(method);
653 self.class_likes
654 .get(&lowercase_class)
655 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
656 }
657
658 #[inline]
660 #[must_use]
661 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
662 let lowercase_class = ascii_lowercase_atom(class);
663 let lowercase_method = ascii_lowercase_atom(method);
664 let identifier = (lowercase_class, lowercase_method);
665 self.function_likes
666 .get(&identifier)
667 .and_then(|meta| meta.method_metadata.as_ref())
668 .is_some_and(|method_meta| method_meta.is_abstract)
669 }
670
671 #[inline]
673 #[must_use]
674 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
675 let lowercase_class = ascii_lowercase_atom(class);
676 let lowercase_method = ascii_lowercase_atom(method);
677 let identifier = (lowercase_class, lowercase_method);
678 self.function_likes
679 .get(&identifier)
680 .and_then(|meta| meta.method_metadata.as_ref())
681 .is_some_and(|method_meta| method_meta.is_static)
682 }
683
684 #[inline]
686 #[must_use]
687 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
688 let lowercase_class = ascii_lowercase_atom(class);
689 let lowercase_method = ascii_lowercase_atom(method);
690 let identifier = (lowercase_class, lowercase_method);
691 self.function_likes
692 .get(&identifier)
693 .and_then(|meta| meta.method_metadata.as_ref())
694 .is_some_and(|method_meta| method_meta.is_final)
695 }
696
697 #[inline]
703 #[must_use]
704 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
705 let lowercase_class = ascii_lowercase_atom(class);
706 let lowercase_method = ascii_lowercase_atom(method);
707
708 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
710 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
711 {
712 return Some(*overridden_visibility);
713 }
714
715 let declaring_class = self.get_declaring_method_class(class, method)?;
717 let identifier = (declaring_class, lowercase_method);
718
719 self.function_likes
720 .get(&identifier)
721 .and_then(|meta| meta.method_metadata.as_ref())
722 .map(|method_meta| method_meta.visibility)
723 }
724
725 #[must_use]
727 pub fn get_function_like_thrown_types<'a>(
728 &'a self,
729 class_like: Option<&'a ClassLikeMetadata>,
730 function_like: &'a FunctionLikeMetadata,
731 ) -> &'a [TypeMetadata] {
732 if !function_like.thrown_types.is_empty() {
733 return function_like.thrown_types.as_slice();
734 }
735
736 if !function_like.kind.is_method() {
737 return &[];
738 }
739
740 let Some(class_like) = class_like else {
741 return &[];
742 };
743
744 let Some(method_name) = function_like.name.as_ref() else {
745 return &[];
746 };
747
748 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
749 for (parent_class_name, parent_method_id) in overridden_map {
750 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
751 continue;
752 };
753
754 let parent_method_key = (*parent_method_id.get_class_name(), *parent_method_id.get_method_name());
755 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
756 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
757 if !thrown.is_empty() {
758 return thrown;
759 }
760 }
761 }
762 }
763
764 &[]
765 }
766
767 #[inline]
769 #[must_use]
770 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
771 let lowercase_class = ascii_lowercase_atom(class);
772 let property_name = atom(property);
773 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
774 }
775
776 #[inline]
778 #[must_use]
779 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
780 let lowercase_class = ascii_lowercase_atom(class);
781 let property_name = atom(property);
782 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
783 }
784
785 #[must_use]
787 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
788 let lowercase_class = ascii_lowercase_atom(class);
789 let mut all_descendants = AtomSet::default();
790 let mut queue = vec![&lowercase_class];
791 let mut visited = AtomSet::default();
792 visited.insert(lowercase_class);
793
794 while let Some(current_name) = queue.pop() {
795 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
796 for descendant in direct_descendants {
797 if visited.insert(*descendant) {
798 all_descendants.insert(*descendant);
799 queue.push(descendant);
800 }
801 }
802 }
803 }
804
805 all_descendants
806 }
807
808 #[must_use]
810 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
811 use std::io::Write;
812
813 let mut buffer = [0u8; 64];
814 let mut writer = &mut buffer[..];
815
816 unsafe {
817 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
818 .unwrap_unchecked();
819 };
820
821 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
822
823 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
824 }
825
826 #[must_use]
828 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
829 let name = Self::get_anonymous_class_name(span);
830 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
831 }
832
833 #[inline]
843 #[must_use]
844 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
845 self.file_signatures.get(file_id)
846 }
847
848 #[inline]
859 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
860 self.file_signatures.insert(file_id, signature)
861 }
862
863 #[inline]
873 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
874 self.file_signatures.remove(file_id)
875 }
876
877 pub fn extend(&mut self, other: CodebaseMetadata) {
882 for (k, v) in other.class_likes {
883 match self.class_likes.entry(k) {
884 Entry::Occupied(mut entry) => {
885 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
886 entry.insert(v);
887 }
888 }
889 Entry::Vacant(entry) => {
890 entry.insert(v);
891 }
892 }
893 }
894
895 for (k, v) in other.function_likes {
896 match self.function_likes.entry(k) {
897 Entry::Occupied(mut entry) => {
898 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
899 entry.insert(v);
900 }
901 }
902 Entry::Vacant(entry) => {
903 entry.insert(v);
904 }
905 }
906 }
907
908 for (k, v) in other.constants {
909 match self.constants.entry(k) {
910 Entry::Occupied(mut entry) => {
911 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
912 entry.insert(v);
913 }
914 }
915 Entry::Vacant(entry) => {
916 entry.insert(v);
917 }
918 }
919 }
920
921 self.symbols.extend(other.symbols);
922
923 for (k, v) in other.all_class_like_descendants {
924 self.all_class_like_descendants.entry(k).or_default().extend(v);
925 }
926
927 for (k, v) in other.direct_classlike_descendants {
928 self.direct_classlike_descendants.entry(k).or_default().extend(v);
929 }
930
931 self.safe_symbols.extend(other.safe_symbols);
932 self.safe_symbol_members.extend(other.safe_symbol_members);
933 self.infer_types_from_usage |= other.infer_types_from_usage;
934 }
935
936 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
938 let mut issues = IssueCollection::new();
939
940 for meta in self.class_likes.values_mut() {
941 if user_defined && !meta.flags.is_user_defined() {
942 continue;
943 }
944 issues.extend(meta.take_issues());
945 }
946
947 for meta in self.function_likes.values_mut() {
948 if user_defined && !meta.flags.is_user_defined() {
949 continue;
950 }
951 issues.extend(meta.take_issues());
952 }
953
954 for meta in self.constants.values_mut() {
955 if user_defined && !meta.flags.is_user_defined() {
956 continue;
957 }
958 issues.extend(meta.take_issues());
959 }
960
961 issues
962 }
963
964 #[must_use]
968 pub fn get_all_file_ids(&self) -> Vec<FileId> {
969 self.file_signatures.keys().copied().collect()
970 }
971}
972
973impl Default for CodebaseMetadata {
974 #[inline]
975 fn default() -> Self {
976 Self {
977 class_likes: AtomMap::default(),
978 function_likes: HashMap::default(),
979 symbols: Symbols::new(),
980 infer_types_from_usage: false,
981 constants: AtomMap::default(),
982 all_class_like_descendants: AtomMap::default(),
983 direct_classlike_descendants: AtomMap::default(),
984 safe_symbols: AtomSet::default(),
985 safe_symbol_members: HashSet::default(),
986 file_signatures: HashMap::default(),
987 }
988 }
989}
990
991fn should_replace_metadata(
996 existing_flags: MetadataFlags,
997 existing_span: Span,
998 new_flags: MetadataFlags,
999 new_span: Span,
1000) -> bool {
1001 let new_is_user_defined = new_flags.is_user_defined();
1002 let existing_is_user_defined = existing_flags.is_user_defined();
1003
1004 if new_is_user_defined != existing_is_user_defined {
1005 return new_is_user_defined;
1006 }
1007
1008 let new_is_built_in = new_flags.is_built_in();
1009 let existing_is_built_in = existing_flags.is_built_in();
1010
1011 if new_is_built_in != existing_is_built_in {
1012 return new_is_built_in;
1013 }
1014
1015 new_span < existing_span
1016}