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