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]
78#[allow(clippy::unsafe_derive_deserialize)]
79pub struct CodebaseMetadata {
80 pub infer_types_from_usage: bool,
82 pub class_likes: AtomMap<ClassLikeMetadata>,
84 pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
87 pub symbols: Symbols,
89 pub constants: AtomMap<ConstantMetadata>,
91 pub all_class_like_descendants: AtomMap<AtomSet>,
93 pub direct_classlike_descendants: AtomMap<AtomSet>,
95 pub safe_symbols: AtomSet,
97 pub safe_symbol_members: HashSet<(Atom, Atom)>,
99 pub file_signatures: HashMap<FileId, FileSignature>,
102}
103
104impl CodebaseMetadata {
105 #[inline]
107 #[must_use]
108 pub fn new() -> Self {
109 Self::default()
110 }
111
112 #[inline]
121 #[must_use]
122 pub fn class_exists(&self, name: &str) -> bool {
123 let lowercase_name = ascii_lowercase_atom(name);
124 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class))
125 }
126
127 #[inline]
129 #[must_use]
130 pub fn interface_exists(&self, name: &str) -> bool {
131 let lowercase_name = ascii_lowercase_atom(name);
132 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Interface))
133 }
134
135 #[inline]
137 #[must_use]
138 pub fn trait_exists(&self, name: &str) -> bool {
139 let lowercase_name = ascii_lowercase_atom(name);
140 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Trait))
141 }
142
143 #[inline]
145 #[must_use]
146 pub fn enum_exists(&self, name: &str) -> bool {
147 let lowercase_name = ascii_lowercase_atom(name);
148 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Enum))
149 }
150
151 #[inline]
153 #[must_use]
154 pub fn class_like_exists(&self, name: &str) -> bool {
155 let lowercase_name = ascii_lowercase_atom(name);
156 self.symbols.contains(lowercase_name)
157 }
158
159 #[inline]
161 #[must_use]
162 pub fn namespace_exists(&self, name: &str) -> bool {
163 let lowercase_name = ascii_lowercase_atom(name);
164 self.symbols.contains_namespace(lowercase_name)
165 }
166
167 #[inline]
169 #[must_use]
170 pub fn class_or_trait_exists(&self, name: &str) -> bool {
171 let lowercase_name = ascii_lowercase_atom(name);
172 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
173 }
174
175 #[inline]
177 #[must_use]
178 pub fn class_or_interface_exists(&self, name: &str) -> bool {
179 let lowercase_name = ascii_lowercase_atom(name);
180 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
187 let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
188 let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
189 let identifier = (lowercase_class, lowercase_method);
190 self.function_likes.contains_key(&identifier)
191 }
192
193 #[inline]
195 #[must_use]
196 pub fn function_exists(&self, name: &str) -> bool {
197 let lowercase_name = ascii_lowercase_atom(name);
198 let identifier = (empty_atom(), lowercase_name);
199 self.function_likes.contains_key(&identifier)
200 }
201
202 #[inline]
205 #[must_use]
206 pub fn constant_exists(&self, name: &str) -> bool {
207 let lowercase_name = ascii_lowercase_constant_name_atom(name);
208 self.constants.contains_key(&lowercase_name)
209 }
210
211 #[inline]
213 #[must_use]
214 pub fn method_exists(&self, class: &str, method: &str) -> bool {
215 let lowercase_class = ascii_lowercase_atom(class);
216 let lowercase_method = ascii_lowercase_atom(method);
217 self.class_likes
218 .get(&lowercase_class)
219 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
220 }
221
222 #[inline]
225 #[must_use]
226 pub fn property_exists(&self, class: &str, property: &str) -> bool {
227 let lowercase_class = ascii_lowercase_atom(class);
228 let property_name = atom(property);
229 self.class_likes
230 .get(&lowercase_class)
231 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
232 }
233
234 #[inline]
237 #[must_use]
238 pub fn class_constant_exists(&self, class: &str, constant: &str) -> bool {
239 let lowercase_class = ascii_lowercase_atom(class);
240 let constant_name = atom(constant);
241 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
242 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
243 })
244 }
245
246 #[inline]
248 #[must_use]
249 pub fn method_is_declared_in_class(&self, class: &str, method: &str) -> bool {
250 let lowercase_class = ascii_lowercase_atom(class);
251 let lowercase_method = ascii_lowercase_atom(method);
252 self.class_likes
253 .get(&lowercase_class)
254 .and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
255 .is_some_and(|method_id| method_id.get_class_name() == lowercase_class)
256 }
257
258 #[inline]
260 #[must_use]
261 pub fn property_is_declared_in_class(&self, class: &str, property: &str) -> bool {
262 let lowercase_class = ascii_lowercase_atom(class);
263 let property_name = atom(property);
264 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
265 }
266
267 #[inline]
270 #[must_use]
271 pub fn get_class(&self, name: &str) -> Option<&ClassLikeMetadata> {
272 let lowercase_name = ascii_lowercase_atom(name);
273 if self.symbols.contains_class(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
274 }
275
276 #[inline]
278 #[must_use]
279 pub fn get_interface(&self, name: &str) -> Option<&ClassLikeMetadata> {
280 let lowercase_name = ascii_lowercase_atom(name);
281 if self.symbols.contains_interface(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
282 }
283
284 #[inline]
286 #[must_use]
287 pub fn get_trait(&self, name: &str) -> Option<&ClassLikeMetadata> {
288 let lowercase_name = ascii_lowercase_atom(name);
289 if self.symbols.contains_trait(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
290 }
291
292 #[inline]
294 #[must_use]
295 pub fn get_enum(&self, name: &str) -> Option<&ClassLikeMetadata> {
296 let lowercase_name = ascii_lowercase_atom(name);
297 if self.symbols.contains_enum(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
298 }
299
300 #[inline]
302 #[must_use]
303 pub fn get_class_like(&self, name: &str) -> Option<&ClassLikeMetadata> {
304 let lowercase_name = ascii_lowercase_atom(name);
305 self.class_likes.get(&lowercase_name)
306 }
307
308 #[inline]
310 #[must_use]
311 pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
312 let lowercase_name = ascii_lowercase_atom(name);
313 let identifier = (empty_atom(), lowercase_name);
314 self.function_likes.get(&identifier)
315 }
316
317 #[inline]
319 #[must_use]
320 pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
321 let lowercase_class = ascii_lowercase_atom(class);
322 let lowercase_method = ascii_lowercase_atom(method);
323 let identifier = (lowercase_class, lowercase_method);
324 self.function_likes.get(&identifier)
325 }
326
327 #[inline]
329 #[must_use]
330 pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
331 let file_ref = u64_atom(file_id.as_u64());
332 let closure_ref = u32_atom(position.offset);
333 let identifier = (file_ref, closure_ref);
334 self.function_likes.get(&identifier)
335 }
336
337 #[inline]
339 #[must_use]
340 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
341 let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
342 let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
343 let identifier = (lowercase_class, lowercase_method);
344 self.function_likes.get(&identifier)
345 }
346
347 #[inline]
350 #[must_use]
351 pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
352 let method_id = MethodIdentifier::new(atom(class), atom(method));
353 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
354 self.get_method(&declaring_method_id.get_class_name(), &declaring_method_id.get_method_name())
355 }
356
357 #[inline]
360 #[must_use]
361 pub fn get_function_like(
362 &self,
363 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
364 ) -> Option<&FunctionLikeMetadata> {
365 use crate::identifier::function_like::FunctionLikeIdentifier;
366 match identifier {
367 FunctionLikeIdentifier::Function(name) => self.get_function(name),
368 FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
369 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
370 }
371 }
372
373 #[inline]
376 #[must_use]
377 pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
378 let lowercase_name = ascii_lowercase_constant_name_atom(name);
379 self.constants.get(&lowercase_name)
380 }
381
382 #[inline]
385 #[must_use]
386 pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
387 let lowercase_class = ascii_lowercase_atom(class);
388 let constant_name = atom(constant);
389 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
390 }
391
392 #[inline]
394 #[must_use]
395 pub fn get_enum_case(&self, class: &str, case: &str) -> Option<&EnumCaseMetadata> {
396 let lowercase_class = ascii_lowercase_atom(class);
397 let case_name = atom(case);
398 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
399 }
400
401 #[inline]
404 #[must_use]
405 pub fn get_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
406 let lowercase_class = ascii_lowercase_atom(class);
407 let property_name = atom(property);
408 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
409 }
410
411 #[inline]
413 #[must_use]
414 pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
415 let lowercase_class = ascii_lowercase_atom(class);
416 let property_name = atom(property);
417 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
418 self.class_likes.get(declaring_class)?.properties.get(&property_name)
419 }
420 #[inline]
424 #[must_use]
425 pub fn get_property_type(&self, class: &str, property: &str) -> Option<&TUnion> {
426 let lowercase_class = ascii_lowercase_atom(class);
427 let property_name = atom(property);
428 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
429 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
430 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
431 }
432
433 #[must_use]
435 pub fn get_class_constant_type<'meta>(&'meta self, class: &str, constant: &str) -> Option<Cow<'meta, TUnion>> {
436 let lowercase_class = ascii_lowercase_atom(class);
437 let constant_name = atom(constant);
438 let class_meta = self.class_likes.get(&lowercase_class)?;
439
440 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
442 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
443 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
444 }
445
446 let constant_meta = class_meta.constants.get(&constant_name)?;
448
449 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
451 return Some(Cow::Borrowed(&type_meta.type_union));
452 }
453
454 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
456 }
457
458 #[inline]
460 #[must_use]
461 pub fn get_class_constant_literal_value(&self, class: &str, constant: &str) -> Option<&TAtomic> {
462 let lowercase_class = ascii_lowercase_atom(class);
463 let constant_name = atom(constant);
464 self.class_likes
465 .get(&lowercase_class)
466 .and_then(|meta| meta.constants.get(&constant_name))
467 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
468 }
469 #[inline]
473 #[must_use]
474 pub fn class_extends(&self, child: &str, parent: &str) -> bool {
475 let lowercase_child = ascii_lowercase_atom(child);
476 let lowercase_parent = ascii_lowercase_atom(parent);
477 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
478 }
479
480 #[inline]
482 #[must_use]
483 pub fn class_directly_extends(&self, child: &str, parent: &str) -> bool {
484 let lowercase_child = ascii_lowercase_atom(child);
485 let lowercase_parent = ascii_lowercase_atom(parent);
486 self.class_likes
487 .get(&lowercase_child)
488 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
489 }
490
491 #[inline]
493 #[must_use]
494 pub fn class_implements(&self, class: &str, interface: &str) -> bool {
495 let lowercase_class = ascii_lowercase_atom(class);
496 let lowercase_interface = ascii_lowercase_atom(interface);
497 self.class_likes
498 .get(&lowercase_class)
499 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
500 }
501
502 #[inline]
504 #[must_use]
505 pub fn class_directly_implements(&self, class: &str, interface: &str) -> bool {
506 let lowercase_class = ascii_lowercase_atom(class);
507 let lowercase_interface = ascii_lowercase_atom(interface);
508 self.class_likes
509 .get(&lowercase_class)
510 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
511 }
512
513 #[inline]
515 #[must_use]
516 pub fn class_uses_trait(&self, class: &str, trait_name: &str) -> bool {
517 let lowercase_class = ascii_lowercase_atom(class);
518 let lowercase_trait = ascii_lowercase_atom(trait_name);
519 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
520 }
521
522 #[inline]
525 #[must_use]
526 pub fn trait_requires_extends(&self, trait_name: &str, class_name: &str) -> bool {
527 let lowercase_trait = ascii_lowercase_atom(trait_name);
528
529 self.class_likes
530 .get(&lowercase_trait)
531 .is_some_and(|meta| meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required)))
532 }
533
534 #[inline]
536 #[must_use]
537 pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
538 if child == parent {
539 return true;
540 }
541
542 let lowercase_child = ascii_lowercase_atom(child);
543 let lowercase_parent = ascii_lowercase_atom(parent);
544
545 if lowercase_child == lowercase_parent {
546 return true;
547 }
548
549 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
550 meta.all_parent_classes.contains(&lowercase_parent)
551 || meta.all_parent_interfaces.contains(&lowercase_parent)
552 || meta.used_traits.contains(&lowercase_parent)
553 || meta.require_extends.contains(&lowercase_parent)
554 || meta.require_implements.contains(&lowercase_parent)
555 })
556 }
557
558 #[inline]
560 #[must_use]
561 pub fn is_enum_or_final_class(&self, name: &str) -> bool {
562 let lowercase_name = ascii_lowercase_atom(name);
563 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
564 }
565
566 #[inline]
569 #[must_use]
570 pub fn is_inheritable(&self, name: &str) -> bool {
571 let lowercase_name = ascii_lowercase_atom(name);
572 match self.symbols.get_kind(lowercase_name) {
573 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
574 Some(SymbolKind::Enum) => false,
575 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
576 }
577 }
578
579 #[inline]
581 #[must_use]
582 pub fn get_class_descendants(&self, class: &str) -> AtomSet {
583 let lowercase_class = ascii_lowercase_atom(class);
584 let mut all_descendants = AtomSet::default();
585 let mut queue = vec![&lowercase_class];
586 let mut visited = AtomSet::default();
587 visited.insert(lowercase_class);
588
589 while let Some(current_name) = queue.pop() {
590 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
591 for descendant in direct_descendants {
592 if visited.insert(*descendant) {
593 all_descendants.insert(*descendant);
594 queue.push(descendant);
595 }
596 }
597 }
598 }
599
600 all_descendants
601 }
602
603 #[inline]
605 #[must_use]
606 pub fn get_class_ancestors(&self, class: &str) -> AtomSet {
607 let lowercase_class = ascii_lowercase_atom(class);
608 let mut ancestors = AtomSet::default();
609 if let Some(meta) = self.class_likes.get(&lowercase_class) {
610 ancestors.extend(meta.all_parent_classes.iter().copied());
611 ancestors.extend(meta.all_parent_interfaces.iter().copied());
612 }
613 ancestors
614 }
615
616 #[inline]
618 #[must_use]
619 pub fn get_declaring_method_class(&self, class: &str, method: &str) -> Option<Atom> {
620 let lowercase_class = ascii_lowercase_atom(class);
621 let lowercase_method = ascii_lowercase_atom(method);
622
623 self.class_likes
624 .get(&lowercase_class)?
625 .declaring_method_ids
626 .get(&lowercase_method)
627 .map(|method_id| method_id.get_class_name())
628 }
629
630 #[inline]
632 #[must_use]
633 pub fn get_appearing_method_class(&self, class: &str, method: &str) -> Option<Atom> {
634 let lowercase_class = ascii_lowercase_atom(class);
635 let lowercase_method = ascii_lowercase_atom(method);
636 self.class_likes
637 .get(&lowercase_class)?
638 .appearing_method_ids
639 .get(&lowercase_method)
640 .map(|method_id| method_id.get_class_name())
641 }
642
643 #[must_use]
645 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
646 let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
647 let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
648
649 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
650 return *method_id;
651 };
652
653 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
654 return *declaring_method_id;
655 }
656
657 if class_meta.flags.is_abstract()
658 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
659 && let Some((_, first_method_id)) = overridden_map.first()
660 {
661 return *first_method_id;
662 }
663
664 *method_id
665 }
666
667 #[inline]
669 #[must_use]
670 pub fn method_is_overriding(&self, class: &str, method: &str) -> bool {
671 let lowercase_class = ascii_lowercase_atom(class);
672 let lowercase_method = ascii_lowercase_atom(method);
673 self.class_likes
674 .get(&lowercase_class)
675 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
676 }
677
678 #[inline]
680 #[must_use]
681 pub fn method_is_abstract(&self, class: &str, method: &str) -> bool {
682 let lowercase_class = ascii_lowercase_atom(class);
683 let lowercase_method = ascii_lowercase_atom(method);
684 let identifier = (lowercase_class, lowercase_method);
685 self.function_likes
686 .get(&identifier)
687 .and_then(|meta| meta.method_metadata.as_ref())
688 .is_some_and(|method_meta| method_meta.is_abstract)
689 }
690
691 #[inline]
693 #[must_use]
694 pub fn method_is_static(&self, class: &str, method: &str) -> bool {
695 let lowercase_class = ascii_lowercase_atom(class);
696 let lowercase_method = ascii_lowercase_atom(method);
697 let identifier = (lowercase_class, lowercase_method);
698 self.function_likes
699 .get(&identifier)
700 .and_then(|meta| meta.method_metadata.as_ref())
701 .is_some_and(|method_meta| method_meta.is_static)
702 }
703
704 #[inline]
706 #[must_use]
707 pub fn method_is_final(&self, class: &str, method: &str) -> bool {
708 let lowercase_class = ascii_lowercase_atom(class);
709 let lowercase_method = ascii_lowercase_atom(method);
710 let identifier = (lowercase_class, lowercase_method);
711 self.function_likes
712 .get(&identifier)
713 .and_then(|meta| meta.method_metadata.as_ref())
714 .is_some_and(|method_meta| method_meta.is_final)
715 }
716
717 #[inline]
723 #[must_use]
724 pub fn get_method_visibility(&self, class: &str, method: &str) -> Option<Visibility> {
725 let lowercase_class = ascii_lowercase_atom(class);
726 let lowercase_method = ascii_lowercase_atom(method);
727
728 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
730 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
731 {
732 return Some(*overridden_visibility);
733 }
734
735 let declaring_class = self.get_declaring_method_class(class, method)?;
737 let identifier = (declaring_class, lowercase_method);
738
739 self.function_likes
740 .get(&identifier)
741 .and_then(|meta| meta.method_metadata.as_ref())
742 .map(|method_meta| method_meta.visibility)
743 }
744
745 #[must_use]
747 pub fn get_function_like_thrown_types<'meta>(
748 &'meta self,
749 class_like: Option<&'meta ClassLikeMetadata>,
750 function_like: &'meta FunctionLikeMetadata,
751 ) -> &'meta [TypeMetadata] {
752 if !function_like.thrown_types.is_empty() {
753 return function_like.thrown_types.as_slice();
754 }
755
756 if !function_like.kind.is_method() {
757 return &[];
758 }
759
760 let Some(class_like) = class_like else {
761 return &[];
762 };
763
764 let Some(method_name) = function_like.name.as_ref() else {
765 return &[];
766 };
767
768 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
769 for (parent_class_name, parent_method_id) in overridden_map {
770 if class_like.name.eq_ignore_ascii_case(parent_class_name) {
771 continue; }
773
774 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
775 continue;
776 };
777
778 let parent_method_key = (parent_method_id.get_class_name(), parent_method_id.get_method_name());
779 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
780 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
781 if !thrown.is_empty() {
782 return thrown;
783 }
784 }
785 }
786 }
787
788 &[]
789 }
790
791 #[inline]
793 #[must_use]
794 pub fn get_declaring_property_class(&self, class: &str, property: &str) -> Option<Atom> {
795 let lowercase_class = ascii_lowercase_atom(class);
796 let property_name = atom(property);
797 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
798 }
799
800 #[inline]
802 #[must_use]
803 pub fn get_appearing_property_class(&self, class: &str, property: &str) -> Option<Atom> {
804 let lowercase_class = ascii_lowercase_atom(class);
805 let property_name = atom(property);
806 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
807 }
808
809 #[must_use]
811 pub fn get_all_descendants(&self, class: &str) -> AtomSet {
812 let lowercase_class = ascii_lowercase_atom(class);
813 let mut all_descendants = AtomSet::default();
814 let mut queue = vec![&lowercase_class];
815 let mut visited = AtomSet::default();
816 visited.insert(lowercase_class);
817
818 while let Some(current_name) = queue.pop() {
819 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
820 for descendant in direct_descendants {
821 if visited.insert(*descendant) {
822 all_descendants.insert(*descendant);
823 queue.push(descendant);
824 }
825 }
826 }
827 }
828
829 all_descendants
830 }
831
832 #[must_use]
834 #[allow(clippy::semicolon_outside_block)]
835 pub fn get_anonymous_class_name(span: mago_span::Span) -> Atom {
836 use std::io::Write;
837
838 let mut buffer = [0u8; 64];
839 let mut writer = &mut buffer[..];
840
841 unsafe {
845 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
846 .unwrap_unchecked();
847 }
848
849 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
850
851 atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
854 }
855
856 #[must_use]
858 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
859 let name = Self::get_anonymous_class_name(span);
860 if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
861 }
862
863 #[inline]
873 #[must_use]
874 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
875 self.file_signatures.get(file_id)
876 }
877
878 #[inline]
889 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
890 self.file_signatures.insert(file_id, signature)
891 }
892
893 #[inline]
903 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
904 self.file_signatures.remove(file_id)
905 }
906
907 pub fn mark_safe_symbols(&mut self, diff: &CodebaseDiff, references: &SymbolReferences) -> Option<bool> {
922 let (invalid_symbols, partially_invalid) = references.get_invalid_symbols(diff)?;
923
924 for keep_symbol in diff.get_keep() {
926 if !invalid_symbols.contains(keep_symbol) {
927 if keep_symbol.1.is_empty() {
928 if !partially_invalid.contains(&keep_symbol.0) {
930 self.safe_symbols.insert(keep_symbol.0);
931 }
932 } else {
933 self.safe_symbol_members.insert(*keep_symbol);
935 }
936 }
937 }
938
939 Some(invalid_symbols.contains(&(empty_atom(), empty_atom())))
940 }
941
942 pub fn extend(&mut self, other: CodebaseMetadata) {
947 for (k, v) in other.class_likes {
948 match self.class_likes.entry(k) {
949 Entry::Occupied(mut entry) => {
950 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
951 entry.insert(v);
952 }
953 }
954 Entry::Vacant(entry) => {
955 entry.insert(v);
956 }
957 }
958 }
959
960 for (k, v) in other.function_likes {
961 match self.function_likes.entry(k) {
962 Entry::Occupied(mut entry) => {
963 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
964 entry.insert(v);
965 }
966 }
967 Entry::Vacant(entry) => {
968 entry.insert(v);
969 }
970 }
971 }
972
973 for (k, v) in other.constants {
974 match self.constants.entry(k) {
975 Entry::Occupied(mut entry) => {
976 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
977 entry.insert(v);
978 }
979 }
980 Entry::Vacant(entry) => {
981 entry.insert(v);
982 }
983 }
984 }
985
986 self.symbols.extend(other.symbols);
987
988 for (k, v) in other.all_class_like_descendants {
989 self.all_class_like_descendants.entry(k).or_default().extend(v);
990 }
991
992 for (k, v) in other.direct_classlike_descendants {
993 self.direct_classlike_descendants.entry(k).or_default().extend(v);
994 }
995
996 self.file_signatures.extend(other.file_signatures);
997 self.safe_symbols.extend(other.safe_symbols);
998 self.safe_symbol_members.extend(other.safe_symbol_members);
999 self.infer_types_from_usage |= other.infer_types_from_usage;
1000 }
1001
1002 pub fn extend_ref(&mut self, other: &CodebaseMetadata) {
1008 for (k, v) in &other.class_likes {
1009 match self.class_likes.entry(*k) {
1010 Entry::Occupied(mut entry) => {
1011 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1012 entry.insert(v.clone());
1013 }
1014 }
1015 Entry::Vacant(entry) => {
1016 entry.insert(v.clone());
1017 }
1018 }
1019 }
1020
1021 for (k, v) in &other.function_likes {
1022 match self.function_likes.entry(*k) {
1023 Entry::Occupied(mut entry) => {
1024 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1025 entry.insert(v.clone());
1026 }
1027 }
1028 Entry::Vacant(entry) => {
1029 entry.insert(v.clone());
1030 }
1031 }
1032 }
1033
1034 for (k, v) in &other.constants {
1035 match self.constants.entry(*k) {
1036 Entry::Occupied(mut entry) => {
1037 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1038 entry.insert(v.clone());
1039 }
1040 }
1041 Entry::Vacant(entry) => {
1042 entry.insert(v.clone());
1043 }
1044 }
1045 }
1046
1047 self.symbols.extend_ref(&other.symbols);
1048
1049 for (k, v) in &other.all_class_like_descendants {
1050 self.all_class_like_descendants.entry(*k).or_default().extend(v.iter().copied());
1051 }
1052
1053 for (k, v) in &other.direct_classlike_descendants {
1054 self.direct_classlike_descendants.entry(*k).or_default().extend(v.iter().copied());
1055 }
1056
1057 for (k, v) in &other.file_signatures {
1058 self.file_signatures.insert(*k, v.clone());
1059 }
1060 self.safe_symbols.extend(other.safe_symbols.iter().copied());
1061 self.safe_symbol_members.extend(other.safe_symbol_members.iter().copied());
1062 self.infer_types_from_usage |= other.infer_types_from_usage;
1063 }
1064
1065 pub fn remove_entries_of(&mut self, file_metadata: &CodebaseMetadata) {
1076 for k in file_metadata.class_likes.keys() {
1077 self.class_likes.remove(k);
1078 }
1079
1080 for k in file_metadata.function_likes.keys() {
1081 self.function_likes.remove(k);
1082 }
1083
1084 for k in file_metadata.constants.keys() {
1085 self.constants.remove(k);
1086 }
1087
1088 for k in file_metadata.class_likes.keys() {
1091 self.symbols.remove(*k);
1092 }
1093
1094 for k in file_metadata.file_signatures.keys() {
1095 self.file_signatures.remove(k);
1096 }
1097 }
1098
1099 #[must_use]
1104 pub fn extract_keys(&self) -> CodebaseEntryKeys {
1105 CodebaseEntryKeys {
1106 class_like_names: self.class_likes.keys().copied().collect(),
1107 function_like_keys: self.function_likes.keys().copied().collect(),
1108 constant_names: self.constants.keys().copied().collect(),
1109 file_ids: self.file_signatures.keys().copied().collect(),
1110 }
1111 }
1112
1113 #[must_use]
1129 pub fn extract_owned_keys(&self, merged: &CodebaseMetadata) -> CodebaseEntryKeys {
1130 let class_like_names = self
1131 .class_likes
1132 .iter()
1133 .filter(|(name, meta)| merged.class_likes.get(*name).is_some_and(|m| m.span == meta.span))
1134 .map(|(name, _)| *name)
1135 .collect();
1136
1137 let function_like_keys = self
1138 .function_likes
1139 .iter()
1140 .filter(|(key, meta)| merged.function_likes.get(*key).is_some_and(|m| m.span == meta.span))
1141 .map(|(key, _)| *key)
1142 .collect();
1143
1144 let constant_names = self
1145 .constants
1146 .iter()
1147 .filter(|(name, meta)| merged.constants.get(*name).is_some_and(|m| m.span == meta.span))
1148 .map(|(name, _)| *name)
1149 .collect();
1150
1151 let file_ids = self.file_signatures.keys().copied().collect();
1153
1154 CodebaseEntryKeys { class_like_names, function_like_keys, constant_names, file_ids }
1155 }
1156
1157 pub fn remove_entries_by_keys(&mut self, keys: &CodebaseEntryKeys) {
1162 for k in &keys.class_like_names {
1163 self.class_likes.remove(k);
1164 self.symbols.remove(*k);
1165 }
1166
1167 for k in &keys.function_like_keys {
1168 self.function_likes.remove(k);
1169 }
1170
1171 for k in &keys.constant_names {
1172 self.constants.remove(k);
1173 }
1174
1175 for k in &keys.file_ids {
1176 self.file_signatures.remove(k);
1177 }
1178 }
1179
1180 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
1182 let mut issues = IssueCollection::new();
1183
1184 for meta in self.class_likes.values_mut() {
1185 if user_defined && !meta.flags.is_user_defined() {
1186 continue;
1187 }
1188 issues.extend(meta.take_issues());
1189 }
1190
1191 for meta in self.function_likes.values_mut() {
1192 if user_defined && !meta.flags.is_user_defined() {
1193 continue;
1194 }
1195 issues.extend(meta.take_issues());
1196 }
1197
1198 for meta in self.constants.values_mut() {
1199 if user_defined && !meta.flags.is_user_defined() {
1200 continue;
1201 }
1202 issues.extend(meta.take_issues());
1203 }
1204
1205 issues
1206 }
1207
1208 #[must_use]
1212 pub fn get_all_file_ids(&self) -> Vec<FileId> {
1213 self.file_signatures.keys().copied().collect()
1214 }
1215}
1216
1217impl Default for CodebaseMetadata {
1218 #[inline]
1219 fn default() -> Self {
1220 Self {
1221 class_likes: AtomMap::default(),
1222 function_likes: HashMap::default(),
1223 symbols: Symbols::new(),
1224 infer_types_from_usage: false,
1225 constants: AtomMap::default(),
1226 all_class_like_descendants: AtomMap::default(),
1227 direct_classlike_descendants: AtomMap::default(),
1228 safe_symbols: AtomSet::default(),
1229 safe_symbol_members: HashSet::default(),
1230 file_signatures: HashMap::default(),
1231 }
1232 }
1233}
1234
1235fn should_replace_metadata(
1246 existing_flags: MetadataFlags,
1247 existing_span: Span,
1248 new_flags: MetadataFlags,
1249 new_span: Span,
1250) -> bool {
1251 let new_is_user_defined = new_flags.is_user_defined();
1252 let existing_is_user_defined = existing_flags.is_user_defined();
1253
1254 if new_is_user_defined != existing_is_user_defined {
1255 return new_is_user_defined;
1256 }
1257
1258 let new_is_built_in = new_flags.is_built_in();
1259 let existing_is_built_in = existing_flags.is_built_in();
1260
1261 if new_is_built_in != existing_is_built_in {
1262 return new_is_built_in;
1263 }
1264
1265 let new_is_polyfill = new_flags.is_polyfill();
1266 let existing_is_polyfill = existing_flags.is_polyfill();
1267
1268 if new_is_polyfill != existing_is_polyfill {
1269 return !new_is_polyfill;
1270 }
1271
1272 new_span < existing_span
1273}
1274
1275#[cfg(test)]
1276mod should_replace_metadata_tests {
1277 use super::*;
1278
1279 #[test]
1280 fn non_polyfill_replaces_polyfill() {
1281 let polyfill = MetadataFlags::POLYFILL;
1282 let real = MetadataFlags::empty();
1283 assert!(should_replace_metadata(polyfill, Span::dummy(0, 100), real, Span::dummy(0, 100)));
1284 assert!(!should_replace_metadata(real, Span::dummy(0, 100), polyfill, Span::dummy(0, 100)));
1285 }
1286
1287 #[test]
1288 fn polyfill_does_not_replace_non_polyfill_even_with_smaller_span() {
1289 let real = MetadataFlags::empty();
1290 let polyfill = MetadataFlags::POLYFILL;
1291 assert!(!should_replace_metadata(real, Span::dummy(500, 600), polyfill, Span::dummy(0, 10)));
1292 }
1293
1294 #[test]
1295 fn user_defined_beats_polyfill_flag() {
1296 let polyfill_user = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
1297 let plain = MetadataFlags::empty();
1298 assert!(!should_replace_metadata(polyfill_user, Span::dummy(0, 10), plain, Span::dummy(0, 10)));
1299 assert!(should_replace_metadata(plain, Span::dummy(0, 10), polyfill_user, Span::dummy(0, 10)));
1300 }
1301
1302 #[test]
1303 fn two_user_defined_fall_through_to_polyfill_check() {
1304 let a = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
1305 let b = MetadataFlags::USER_DEFINED;
1306 assert!(should_replace_metadata(a, Span::dummy(0, 10), b, Span::dummy(0, 10)));
1307 assert!(!should_replace_metadata(b, Span::dummy(0, 10), a, Span::dummy(0, 10)));
1308 }
1309
1310 #[test]
1311 fn two_non_polyfills_fall_through_to_priority_rules() {
1312 let user = MetadataFlags::USER_DEFINED;
1313 let builtin = MetadataFlags::BUILTIN;
1314 assert!(!should_replace_metadata(user, Span::dummy(0, 10), builtin, Span::dummy(0, 10)));
1315 assert!(should_replace_metadata(builtin, Span::dummy(0, 10), user, Span::dummy(0, 10)));
1316 }
1317}