1use std::borrow::Cow;
2use std::collections::hash_map::Entry;
3
4use foldhash::HashMap;
5use foldhash::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::diff::CodebaseDiff;
24use crate::identifier::method::MethodIdentifier;
25use crate::metadata::class_like::ClassLikeMetadata;
26use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
27use crate::metadata::constant::ConstantMetadata;
28use crate::metadata::enum_case::EnumCaseMetadata;
29use crate::metadata::flags::MetadataFlags;
30use crate::metadata::function_like::FunctionLikeMetadata;
31use crate::metadata::property::PropertyMetadata;
32use crate::metadata::ttype::TypeMetadata;
33use crate::reference::SymbolReferences;
34use crate::signature::FileSignature;
35use crate::symbol::SymbolKind;
36use crate::symbol::Symbols;
37use crate::ttype::atomic::TAtomic;
38use crate::ttype::atomic::object::TObject;
39use crate::ttype::union::TUnion;
40use crate::visibility::Visibility;
41
42pub mod attribute;
43pub mod class_like;
44pub mod class_like_constant;
45pub mod constant;
46pub mod enum_case;
47pub mod flags;
48pub mod function_like;
49pub mod parameter;
50pub mod property;
51pub mod property_hook;
52pub mod ttype;
53
54#[derive(Debug, Clone)]
60pub struct CodebaseEntryKeys {
61 pub class_like_names: Vec<Atom>,
63 pub function_like_keys: Vec<(Atom, Atom)>,
65 pub constant_names: Vec<Atom>,
67 pub file_ids: Vec<FileId>,
69}
70
71#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
77#[non_exhaustive]
78pub struct CodebaseMetadata {
79 pub infer_types_from_usage: bool,
81 pub class_likes: AtomMap<ClassLikeMetadata>,
83 pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
86 pub symbols: Symbols,
88 pub constants: AtomMap<ConstantMetadata>,
90 pub all_class_like_descendants: AtomMap<AtomSet>,
92 pub direct_classlike_descendants: AtomMap<AtomSet>,
94 pub safe_symbols: AtomSet,
96 pub safe_symbol_members: HashSet<(Atom, Atom)>,
98 pub file_signatures: HashMap<FileId, FileSignature>,
101}
102
103impl CodebaseMetadata {
104 #[inline]
106 #[must_use]
107 pub fn new() -> Self {
108 Self::default()
109 }
110
111 #[inline]
120 #[must_use]
121 pub fn class_exists(&self, name: &str) -> bool {
122 let lowercase_name = ascii_lowercase_atom(name);
123 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class))
124 }
125
126 #[inline]
128 #[must_use]
129 pub fn interface_exists(&self, name: &str) -> bool {
130 let lowercase_name = ascii_lowercase_atom(name);
131 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Interface))
132 }
133
134 #[inline]
136 #[must_use]
137 pub fn trait_exists(&self, name: &str) -> bool {
138 let lowercase_name = ascii_lowercase_atom(name);
139 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Trait))
140 }
141
142 #[inline]
144 #[must_use]
145 pub fn enum_exists(&self, name: &str) -> bool {
146 let lowercase_name = ascii_lowercase_atom(name);
147 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Enum))
148 }
149
150 #[inline]
152 #[must_use]
153 pub fn class_like_exists(&self, name: &str) -> bool {
154 let lowercase_name = ascii_lowercase_atom(name);
155 self.symbols.contains(lowercase_name)
156 }
157
158 #[inline]
160 #[must_use]
161 pub fn namespace_exists(&self, name: &str) -> bool {
162 let lowercase_name = ascii_lowercase_atom(name);
163 self.symbols.contains_namespace(lowercase_name)
164 }
165
166 #[inline]
168 #[must_use]
169 pub fn class_or_trait_exists(&self, name: &str) -> bool {
170 let lowercase_name = ascii_lowercase_atom(name);
171 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
172 }
173
174 #[inline]
176 #[must_use]
177 pub fn class_or_interface_exists(&self, name: &str) -> bool {
178 let lowercase_name = ascii_lowercase_atom(name);
179 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
180 }
181
182 #[inline]
184 #[must_use]
185 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
186 let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
187 let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
188 let identifier = (lowercase_class, lowercase_method);
189 self.function_likes.contains_key(&identifier)
190 }
191
192 #[inline]
194 #[must_use]
195 pub fn function_exists(&self, name: &str) -> bool {
196 let lowercase_name = ascii_lowercase_atom(name);
197 let identifier = (empty_atom(), lowercase_name);
198 self.function_likes.contains_key(&identifier)
199 }
200
201 #[inline]
204 #[must_use]
205 pub fn constant_exists(&self, name: &str) -> bool {
206 let lowercase_name = ascii_lowercase_constant_name_atom(name);
207 self.constants.contains_key(&lowercase_name)
208 }
209
210 #[inline]
212 #[must_use]
213 pub fn method_exists(&self, class: &str, method: &str) -> bool {
214 let lowercase_class = ascii_lowercase_atom(class);
215 let lowercase_method = ascii_lowercase_atom(method);
216 self.class_likes
217 .get(&lowercase_class)
218 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
219 }
220
221 #[inline]
224 #[must_use]
225 pub fn property_exists(&self, class: &str, property: &str) -> bool {
226 let lowercase_class = ascii_lowercase_atom(class);
227 let property_name = atom(property);
228 self.class_likes
229 .get(&lowercase_class)
230 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
231 }
232
233 #[inline]
236 #[must_use]
237 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
238 let lowercase_class = ascii_lowercase_atom(class);
239 let constant_name = atom(constant);
240 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
241 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
242 })
243 }
244
245 #[inline]
247 #[must_use]
248 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
249 let lowercase_class = ascii_lowercase_atom(class);
250 let lowercase_method = ascii_lowercase_atom(method);
251 self.class_likes
252 .get(&lowercase_class)
253 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
254 .is_some_and(|method_id| method_id.get_class_name() == lowercase_class)
255 }
256
257 #[inline]
259 #[must_use]
260 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
261 let lowercase_class = ascii_lowercase_atom(class);
262 let property_name = atom(property);
263 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
264 }
265
266 #[inline]
269 #[must_use]
270 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
271 let lowercase_name = ascii_lowercase_atom(name);
272 if self.symbols.contains_class(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
273 }
274
275 #[inline]
277 #[must_use]
278 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
279 let lowercase_name = ascii_lowercase_atom(name);
280 if self.symbols.contains_interface(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
281 }
282
283 #[inline]
285 #[must_use]
286 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
287 let lowercase_name = ascii_lowercase_atom(name);
288 if self.symbols.contains_trait(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
289 }
290
291 #[inline]
293 #[must_use]
294 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
295 let lowercase_name = ascii_lowercase_atom(name);
296 if self.symbols.contains_enum(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
297 }
298
299 #[inline]
301 #[must_use]
302 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
303 let lowercase_name = ascii_lowercase_atom(name);
304 self.class_likes.get(&lowercase_name)
305 }
306
307 #[inline]
309 #[must_use]
310 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
311 let lowercase_name = ascii_lowercase_atom(name);
312 let identifier = (empty_atom(), lowercase_name);
313 self.function_likes.get(&identifier)
314 }
315
316 #[inline]
318 #[must_use]
319 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
320 let lowercase_class = ascii_lowercase_atom(class);
321 let lowercase_method = ascii_lowercase_atom(method);
322 let identifier = (lowercase_class, lowercase_method);
323 self.function_likes.get(&identifier)
324 }
325
326 #[inline]
328 #[must_use]
329 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
330 let file_ref = u64_atom(file_id.as_u64());
331 let closure_ref = u32_atom(position.offset);
332 let identifier = (file_ref, closure_ref);
333 self.function_likes.get(&identifier)
334 }
335
336 #[inline]
338 #[must_use]
339 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
340 let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
341 let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
342 let identifier = (lowercase_class, lowercase_method);
343 self.function_likes.get(&identifier)
344 }
345
346 #[inline]
349 #[must_use]
350 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
351 let method_id = MethodIdentifier::new(atom(class), atom(method));
352 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
353 self.get_method(&declaring_method_id.get_class_name(), &declaring_method_id.get_method_name())
354 }
355
356 #[inline]
359 #[must_use]
360 pub fn get_function_like(
361 &self,
362 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
363 ) -> Option<&FunctionLikeMetadata> {
364 use crate::identifier::function_like::FunctionLikeIdentifier;
365 match identifier {
366 FunctionLikeIdentifier::Function(name) => self.get_function(name),
367 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
368 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
369 }
370 }
371
372 #[inline]
375 #[must_use]
376 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
377 let lowercase_name = ascii_lowercase_constant_name_atom(name);
378 self.constants.get(&lowercase_name)
379 }
380
381 #[inline]
384 #[must_use]
385 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
386 let lowercase_class = ascii_lowercase_atom(class);
387 let constant_name = atom(constant);
388 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
389 }
390
391 #[inline]
393 #[must_use]
394 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
395 let lowercase_class = ascii_lowercase_atom(class);
396 let case_name = atom(case);
397 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
398 }
399
400 #[inline]
403 #[must_use]
404 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
405 let lowercase_class = ascii_lowercase_atom(class);
406 let property_name = atom(property);
407 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
408 }
409
410 #[inline]
412 #[must_use]
413 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
414 let lowercase_class = ascii_lowercase_atom(class);
415 let property_name = atom(property);
416 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
417 self.class_likes.get(declaring_class)?.properties.get(&property_name)
418 }
419 #[inline]
423 #[must_use]
424 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
425 let lowercase_class = ascii_lowercase_atom(class);
426 let property_name = atom(property);
427 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
428 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
429 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
430 }
431
432 #[must_use]
434 pub fn get_class_constant_type<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
435 let lowercase_class = ascii_lowercase_atom(class);
436 let constant_name = atom(constant);
437 let class_meta = self.class_likes.get(&lowercase_class)?;
438
439 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
441 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
442 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
443 }
444
445 let constant_meta = class_meta.constants.get(&constant_name)?;
447
448 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
450 return Some(Cow::Borrowed(&type_meta.type_union));
451 }
452
453 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
455 }
456
457 #[inline]
459 #[must_use]
460 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
461 let lowercase_class = ascii_lowercase_atom(class);
462 let constant_name = atom(constant);
463 self.class_likes
464 .get(&lowercase_class)
465 .and_then(|meta| meta.constants.get(&constant_name))
466 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
467 }
468 #[inline]
472 #[must_use]
473 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
474 let lowercase_child = ascii_lowercase_atom(child);
475 let lowercase_parent = ascii_lowercase_atom(parent);
476 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
477 }
478
479 #[inline]
481 #[must_use]
482 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
483 let lowercase_child = ascii_lowercase_atom(child);
484 let lowercase_parent = ascii_lowercase_atom(parent);
485 self.class_likes
486 .get(&lowercase_child)
487 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
488 }
489
490 #[inline]
492 #[must_use]
493 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
494 let lowercase_class = ascii_lowercase_atom(class);
495 let lowercase_interface = ascii_lowercase_atom(interface);
496 self.class_likes
497 .get(&lowercase_class)
498 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
499 }
500
501 #[inline]
503 #[must_use]
504 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
505 let lowercase_class = ascii_lowercase_atom(class);
506 let lowercase_interface = ascii_lowercase_atom(interface);
507 self.class_likes
508 .get(&lowercase_class)
509 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
510 }
511
512 #[inline]
514 #[must_use]
515 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
516 let lowercase_class = ascii_lowercase_atom(class);
517 let lowercase_trait = ascii_lowercase_atom(trait_name);
518 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
519 }
520
521 #[inline]
524 #[must_use]
525 pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
526 let lowercase_trait = ascii_lowercase_atom(trait_name);
527
528 self.class_likes
529 .get(&lowercase_trait)
530 .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
531 }
532
533 #[inline]
535 #[must_use]
536 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
537 if child == parent {
538 return true;
539 }
540
541 let lowercase_child = ascii_lowercase_atom(child);
542 let lowercase_parent = ascii_lowercase_atom(parent);
543
544 if lowercase_child == lowercase_parent {
545 return true;
546 }
547
548 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
549 meta.all_parent_classes.contains(&lowercase_parent)
550 || meta.all_parent_interfaces.contains(&lowercase_parent)
551 || meta.used_traits.contains(&lowercase_parent)
552 || meta.require_extends.contains(&lowercase_parent)
553 || meta.require_implements.contains(&lowercase_parent)
554 })
555 }
556
557 #[inline]
559 #[must_use]
560 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
561 let lowercase_name = ascii_lowercase_atom(name);
562 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
563 }
564
565 #[inline]
568 #[must_use]
569 pub fn is_inheritable(&self, name: &str) -> bool {
570 let lowercase_name = ascii_lowercase_atom(name);
571 match self.symbols.get_kind(lowercase_name) {
572 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
573 Some(SymbolKind::Enum) => false,
574 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
575 }
576 }
577
578 #[inline]
580 #[must_use]
581 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
582 let lowercase_class = ascii_lowercase_atom(class);
583 let mut all_descendants = AtomSet::default();
584 let mut queue = vec![&lowercase_class];
585 let mut visited = AtomSet::default();
586 visited.insert(lowercase_class);
587
588 while let Some(current_name) = queue.pop() {
589 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
590 for descendant in direct_descendants {
591 if visited.insert(*descendant) {
592 all_descendants.insert(*descendant);
593 queue.push(descendant);
594 }
595 }
596 }
597 }
598
599 all_descendants
600 }
601
602 #[inline]
604 #[must_use]
605 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
606 let lowercase_class = ascii_lowercase_atom(class);
607 let mut ancestors = AtomSet::default();
608 if let Some(meta) = self.class_likes.get(&lowercase_class) {
609 ancestors.extend(meta.all_parent_classes.iter().copied());
610 ancestors.extend(meta.all_parent_interfaces.iter().copied());
611 }
612 ancestors
613 }
614
615 #[inline]
617 #[must_use]
618 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
619 let lowercase_class = ascii_lowercase_atom(class);
620 let lowercase_method = ascii_lowercase_atom(method);
621
622 self.class_likes
623 .get(&lowercase_class)?
624 .declaring_method_ids
625 .get(&lowercase_method)
626 .map(|method_id| method_id.get_class_name())
627 }
628
629 #[inline]
631 #[must_use]
632 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
633 let lowercase_class = ascii_lowercase_atom(class);
634 let lowercase_method = ascii_lowercase_atom(method);
635 self.class_likes
636 .get(&lowercase_class)?
637 .appearing_method_ids
638 .get(&lowercase_method)
639 .map(|method_id| method_id.get_class_name())
640 }
641
642 #[must_use]
644 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
645 let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
646 let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
647
648 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
649 return *method_id;
650 };
651
652 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
653 return *declaring_method_id;
654 }
655
656 if class_meta.flags.is_abstract()
657 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
658 && let Some((_, first_method_id)) = overridden_map.first()
659 {
660 return *first_method_id;
661 }
662
663 *method_id
664 }
665
666 #[inline]
668 #[must_use]
669 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
670 let lowercase_class = ascii_lowercase_atom(class);
671 let lowercase_method = ascii_lowercase_atom(method);
672 self.class_likes
673 .get(&lowercase_class)
674 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
675 }
676
677 #[inline]
679 #[must_use]
680 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
681 let lowercase_class = ascii_lowercase_atom(class);
682 let lowercase_method = ascii_lowercase_atom(method);
683 let identifier = (lowercase_class, lowercase_method);
684 self.function_likes
685 .get(&identifier)
686 .and_then(|meta| meta.method_metadata.as_ref())
687 .is_some_and(|method_meta| method_meta.is_abstract)
688 }
689
690 #[inline]
692 #[must_use]
693 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
694 let lowercase_class = ascii_lowercase_atom(class);
695 let lowercase_method = ascii_lowercase_atom(method);
696 let identifier = (lowercase_class, lowercase_method);
697 self.function_likes
698 .get(&identifier)
699 .and_then(|meta| meta.method_metadata.as_ref())
700 .is_some_and(|method_meta| method_meta.is_static)
701 }
702
703 #[inline]
705 #[must_use]
706 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
707 let lowercase_class = ascii_lowercase_atom(class);
708 let lowercase_method = ascii_lowercase_atom(method);
709 let identifier = (lowercase_class, lowercase_method);
710 self.function_likes
711 .get(&identifier)
712 .and_then(|meta| meta.method_metadata.as_ref())
713 .is_some_and(|method_meta| method_meta.is_final)
714 }
715
716 #[inline]
722 #[must_use]
723 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
724 let lowercase_class = ascii_lowercase_atom(class);
725 let lowercase_method = ascii_lowercase_atom(method);
726
727 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
729 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
730 {
731 return Some(*overridden_visibility);
732 }
733
734 let declaring_class = self.get_declaring_method_class(class, method)?;
736 let identifier = (declaring_class, lowercase_method);
737
738 self.function_likes
739 .get(&identifier)
740 .and_then(|meta| meta.method_metadata.as_ref())
741 .map(|method_meta| method_meta.visibility)
742 }
743
744 #[must_use]
746 pub fn get_function_like_thrown_types<'a>(
747 &'a self,
748 class_like: Option<&'a ClassLikeMetadata>,
749 function_like: &'a FunctionLikeMetadata,
750 ) -> &'a [TypeMetadata] {
751 if !function_like.thrown_types.is_empty() {
752 return function_like.thrown_types.as_slice();
753 }
754
755 if !function_like.kind.is_method() {
756 return &[];
757 }
758
759 let Some(class_like) = class_like else {
760 return &[];
761 };
762
763 let Some(method_name) = function_like.name.as_ref() else {
764 return &[];
765 };
766
767 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
768 for (parent_class_name, parent_method_id) in overridden_map {
769 if class_like.name.eq_ignore_ascii_case(parent_class_name) {
770 continue; }
772
773 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
774 continue;
775 };
776
777 let parent_method_key = (parent_method_id.get_class_name(), parent_method_id.get_method_name());
778 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
779 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
780 if !thrown.is_empty() {
781 return thrown;
782 }
783 }
784 }
785 }
786
787 &[]
788 }
789
790 #[inline]
792 #[must_use]
793 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
794 let lowercase_class = ascii_lowercase_atom(class);
795 let property_name = atom(property);
796 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
797 }
798
799 #[inline]
801 #[must_use]
802 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
803 let lowercase_class = ascii_lowercase_atom(class);
804 let property_name = atom(property);
805 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
806 }
807
808 #[must_use]
810 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
811 let lowercase_class = ascii_lowercase_atom(class);
812 let mut all_descendants = AtomSet::default();
813 let mut queue = vec![&lowercase_class];
814 let mut visited = AtomSet::default();
815 visited.insert(lowercase_class);
816
817 while let Some(current_name) = queue.pop() {
818 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
819 for descendant in direct_descendants {
820 if visited.insert(*descendant) {
821 all_descendants.insert(*descendant);
822 queue.push(descendant);
823 }
824 }
825 }
826 }
827
828 all_descendants
829 }
830
831 #[must_use]
833 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
834 use std::io::Write;
835
836 let mut buffer = [0u8; 64];
837 let mut writer = &mut buffer[..];
838
839 unsafe {
840 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
841 .unwrap_unchecked();
842 };
843
844 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
845
846 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
847 }
848
849 #[must_use]
851 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
852 let name = Self::get_anonymous_class_name(span);
853 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
854 }
855
856 #[inline]
866 #[must_use]
867 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
868 self.file_signatures.get(file_id)
869 }
870
871 #[inline]
882 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
883 self.file_signatures.insert(file_id, signature)
884 }
885
886 #[inline]
896 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
897 self.file_signatures.remove(file_id)
898 }
899
900 pub fn mark_safe_symbols(&mut self, diff: &CodebaseDiff, references: &SymbolReferences) -> bool {
914 let Some((invalid_symbols, partially_invalid)) = references.get_invalid_symbols(diff) else {
916 return false;
918 };
919
920 for keep_symbol in diff.get_keep() {
922 if !invalid_symbols.contains(keep_symbol) {
923 if keep_symbol.1.is_empty() {
924 if !partially_invalid.contains(&keep_symbol.0) {
926 self.safe_symbols.insert(keep_symbol.0);
927 }
928 } else {
929 self.safe_symbol_members.insert(*keep_symbol);
931 }
932 }
933 }
934
935 true
936 }
937
938 pub fn extend(&mut self, other: CodebaseMetadata) {
943 for (k, v) in other.class_likes {
944 match self.class_likes.entry(k) {
945 Entry::Occupied(mut entry) => {
946 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
947 entry.insert(v);
948 }
949 }
950 Entry::Vacant(entry) => {
951 entry.insert(v);
952 }
953 }
954 }
955
956 for (k, v) in other.function_likes {
957 match self.function_likes.entry(k) {
958 Entry::Occupied(mut entry) => {
959 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
960 entry.insert(v);
961 }
962 }
963 Entry::Vacant(entry) => {
964 entry.insert(v);
965 }
966 }
967 }
968
969 for (k, v) in other.constants {
970 match self.constants.entry(k) {
971 Entry::Occupied(mut entry) => {
972 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
973 entry.insert(v);
974 }
975 }
976 Entry::Vacant(entry) => {
977 entry.insert(v);
978 }
979 }
980 }
981
982 self.symbols.extend(other.symbols);
983
984 for (k, v) in other.all_class_like_descendants {
985 self.all_class_like_descendants.entry(k).or_default().extend(v);
986 }
987
988 for (k, v) in other.direct_classlike_descendants {
989 self.direct_classlike_descendants.entry(k).or_default().extend(v);
990 }
991
992 self.file_signatures.extend(other.file_signatures);
993 self.safe_symbols.extend(other.safe_symbols);
994 self.safe_symbol_members.extend(other.safe_symbol_members);
995 self.infer_types_from_usage |= other.infer_types_from_usage;
996 }
997
998 pub fn extend_ref(&mut self, other: &CodebaseMetadata) {
1004 for (k, v) in &other.class_likes {
1005 match self.class_likes.entry(*k) {
1006 Entry::Occupied(mut entry) => {
1007 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1008 entry.insert(v.clone());
1009 }
1010 }
1011 Entry::Vacant(entry) => {
1012 entry.insert(v.clone());
1013 }
1014 }
1015 }
1016
1017 for (k, v) in &other.function_likes {
1018 match self.function_likes.entry(*k) {
1019 Entry::Occupied(mut entry) => {
1020 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1021 entry.insert(v.clone());
1022 }
1023 }
1024 Entry::Vacant(entry) => {
1025 entry.insert(v.clone());
1026 }
1027 }
1028 }
1029
1030 for (k, v) in &other.constants {
1031 match self.constants.entry(*k) {
1032 Entry::Occupied(mut entry) => {
1033 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1034 entry.insert(v.clone());
1035 }
1036 }
1037 Entry::Vacant(entry) => {
1038 entry.insert(v.clone());
1039 }
1040 }
1041 }
1042
1043 self.symbols.extend_ref(&other.symbols);
1044
1045 for (k, v) in &other.all_class_like_descendants {
1046 self.all_class_like_descendants.entry(*k).or_default().extend(v.iter().copied());
1047 }
1048
1049 for (k, v) in &other.direct_classlike_descendants {
1050 self.direct_classlike_descendants.entry(*k).or_default().extend(v.iter().copied());
1051 }
1052
1053 for (k, v) in &other.file_signatures {
1054 self.file_signatures.insert(*k, v.clone());
1055 }
1056 self.safe_symbols.extend(other.safe_symbols.iter().copied());
1057 self.safe_symbol_members.extend(other.safe_symbol_members.iter().cloned());
1058 self.infer_types_from_usage |= other.infer_types_from_usage;
1059 }
1060
1061 pub fn remove_entries_of(&mut self, file_metadata: &CodebaseMetadata) {
1072 for k in file_metadata.class_likes.keys() {
1073 self.class_likes.remove(k);
1074 }
1075
1076 for k in file_metadata.function_likes.keys() {
1077 self.function_likes.remove(k);
1078 }
1079
1080 for k in file_metadata.constants.keys() {
1081 self.constants.remove(k);
1082 }
1083
1084 for k in file_metadata.class_likes.keys() {
1087 self.symbols.remove(*k);
1088 }
1089
1090 for k in file_metadata.file_signatures.keys() {
1091 self.file_signatures.remove(k);
1092 }
1093 }
1094
1095 pub fn extract_keys(&self) -> CodebaseEntryKeys {
1100 CodebaseEntryKeys {
1101 class_like_names: self.class_likes.keys().copied().collect(),
1102 function_like_keys: self.function_likes.keys().copied().collect(),
1103 constant_names: self.constants.keys().copied().collect(),
1104 file_ids: self.file_signatures.keys().copied().collect(),
1105 }
1106 }
1107
1108 pub fn remove_entries_by_keys(&mut self, keys: &CodebaseEntryKeys) {
1113 for k in &keys.class_like_names {
1114 self.class_likes.remove(k);
1115 self.symbols.remove(*k);
1116 }
1117
1118 for k in &keys.function_like_keys {
1119 self.function_likes.remove(k);
1120 }
1121
1122 for k in &keys.constant_names {
1123 self.constants.remove(k);
1124 }
1125
1126 for k in &keys.file_ids {
1127 self.file_signatures.remove(k);
1128 }
1129 }
1130
1131 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
1133 let mut issues = IssueCollection::new();
1134
1135 for meta in self.class_likes.values_mut() {
1136 if user_defined && !meta.flags.is_user_defined() {
1137 continue;
1138 }
1139 issues.extend(meta.take_issues());
1140 }
1141
1142 for meta in self.function_likes.values_mut() {
1143 if user_defined && !meta.flags.is_user_defined() {
1144 continue;
1145 }
1146 issues.extend(meta.take_issues());
1147 }
1148
1149 for meta in self.constants.values_mut() {
1150 if user_defined && !meta.flags.is_user_defined() {
1151 continue;
1152 }
1153 issues.extend(meta.take_issues());
1154 }
1155
1156 issues
1157 }
1158
1159 #[must_use]
1163 pub fn get_all_file_ids(&self) -> Vec<FileId> {
1164 self.file_signatures.keys().copied().collect()
1165 }
1166}
1167
1168impl Default for CodebaseMetadata {
1169 #[inline]
1170 fn default() -> Self {
1171 Self {
1172 class_likes: AtomMap::default(),
1173 function_likes: HashMap::default(),
1174 symbols: Symbols::new(),
1175 infer_types_from_usage: false,
1176 constants: AtomMap::default(),
1177 all_class_like_descendants: AtomMap::default(),
1178 direct_classlike_descendants: AtomMap::default(),
1179 safe_symbols: AtomSet::default(),
1180 safe_symbol_members: HashSet::default(),
1181 file_signatures: HashMap::default(),
1182 }
1183 }
1184}
1185
1186fn should_replace_metadata(
1191 existing_flags: MetadataFlags,
1192 existing_span: Span,
1193 new_flags: MetadataFlags,
1194 new_span: Span,
1195) -> bool {
1196 let new_is_user_defined = new_flags.is_user_defined();
1197 let existing_is_user_defined = existing_flags.is_user_defined();
1198
1199 if new_is_user_defined != existing_is_user_defined {
1200 return new_is_user_defined;
1201 }
1202
1203 let new_is_built_in = new_flags.is_built_in();
1204 let existing_is_built_in = existing_flags.is_built_in();
1205
1206 if new_is_built_in != existing_is_built_in {
1207 return new_is_built_in;
1208 }
1209
1210 new_span < existing_span
1211}