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_database::file::FileId;
10use mago_reporting::IssueCollection;
11use mago_span::Position;
12use mago_span::Span;
13use mago_word::Word;
14use mago_word::WordMap;
15use mago_word::WordSet;
16use mago_word::ascii_lowercase_constant_name_word;
17use mago_word::ascii_lowercase_word;
18use mago_word::empty_word;
19use mago_word::u32_word;
20use mago_word::u64_word;
21use mago_word::word;
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<Word>,
64 pub function_like_keys: Vec<(Word, Word)>,
66 pub constant_names: Vec<Word>,
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: WordMap<ClassLikeMetadata>,
85 pub function_likes: HashMap<(Word, Word), FunctionLikeMetadata>,
88 pub symbols: Symbols,
90 pub constants: WordMap<ConstantMetadata>,
92 pub all_class_like_descendants: WordMap<WordSet>,
94 pub direct_classlike_descendants: WordMap<WordSet>,
96 pub safe_symbols: WordSet,
98 pub safe_symbol_members: HashSet<(Word, Word)>,
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: &[u8]) -> bool {
124 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> bool {
132 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> bool {
140 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> bool {
148 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> bool {
156 let lowercase_name = ascii_lowercase_word(name);
157 self.symbols.contains(lowercase_name)
158 }
159
160 #[inline]
162 #[must_use]
163 pub fn namespace_exists(&self, name: &[u8]) -> bool {
164 let lowercase_name = ascii_lowercase_word(name);
165 self.symbols.contains_namespace(lowercase_name)
166 }
167
168 #[inline]
170 #[must_use]
171 pub fn class_or_trait_exists(&self, name: &[u8]) -> bool {
172 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> bool {
180 let lowercase_name = ascii_lowercase_word(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_word(method_id.get_class_name().as_bytes());
189 let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
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: &[u8]) -> bool {
198 let lowercase_name = ascii_lowercase_word(name);
199 let identifier = (empty_word(), lowercase_name);
200 self.function_likes.contains_key(&identifier)
201 }
202
203 #[inline]
206 #[must_use]
207 pub fn constant_exists(&self, name: &[u8]) -> bool {
208 let lowercase_name = ascii_lowercase_constant_name_word(name);
209 self.constants.contains_key(&lowercase_name)
210 }
211
212 #[inline]
214 #[must_use]
215 pub fn method_exists(&self, class: &[u8], method: &[u8]) -> bool {
216 let lowercase_class = ascii_lowercase_word(class);
217 let lowercase_method = ascii_lowercase_word(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: &[u8], property: &[u8]) -> bool {
228 let lowercase_class = ascii_lowercase_word(class);
229 let property_name = word(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: &[u8], constant: &[u8]) -> bool {
240 let lowercase_class = ascii_lowercase_word(class);
241 let constant_name = word(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: &[u8], method: &[u8]) -> bool {
251 let lowercase_class = ascii_lowercase_word(class);
252 let lowercase_method = ascii_lowercase_word(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: &[u8], property: &[u8]) -> bool {
263 let lowercase_class = ascii_lowercase_word(class);
264 let property_name = word(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: &[u8]) -> Option<&ClassLikeMetadata> {
273 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> Option<&ClassLikeMetadata> {
281 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> Option<&ClassLikeMetadata> {
289 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> Option<&ClassLikeMetadata> {
297 let lowercase_name = ascii_lowercase_word(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: &[u8]) -> Option<&ClassLikeMetadata> {
305 let lowercase_name = ascii_lowercase_word(name);
306 self.class_likes.get(&lowercase_name)
307 }
308
309 #[inline]
311 #[must_use]
312 pub fn get_function(&self, name: &[u8]) -> Option<&FunctionLikeMetadata> {
313 let lowercase_name = ascii_lowercase_word(name);
314 let identifier = (empty_word(), lowercase_name);
315 self.function_likes.get(&identifier)
316 }
317
318 #[inline]
320 #[must_use]
321 pub fn get_method(&self, class: &[u8], method: &[u8]) -> Option<&FunctionLikeMetadata> {
322 let lowercase_class = ascii_lowercase_word(class);
323 let lowercase_method = ascii_lowercase_word(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_word(file_id.as_u64());
333 let closure_ref = u32_word(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_word(method_id.get_class_name().as_bytes());
343 let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
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: &[u8], method: &[u8]) -> Option<&FunctionLikeMetadata> {
353 let method_id = MethodIdentifier::new(word(class), word(method));
354 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
355 self.get_method(
356 declaring_method_id.get_class_name().as_bytes(),
357 declaring_method_id.get_method_name().as_bytes(),
358 )
359 }
360
361 #[inline]
364 #[must_use]
365 pub fn get_function_like(
366 &self,
367 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
368 ) -> Option<&FunctionLikeMetadata> {
369 use crate::identifier::function_like::FunctionLikeIdentifier;
370 match identifier {
371 FunctionLikeIdentifier::Function(name) => self.get_function(name.as_bytes()),
372 FunctionLikeIdentifier::Method(class, method) => self.get_method(class.as_bytes(), method.as_bytes()),
373 FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
374 }
375 }
376
377 #[inline]
380 #[must_use]
381 pub fn get_constant(&self, name: &[u8]) -> Option<&ConstantMetadata> {
382 let lowercase_name = ascii_lowercase_constant_name_word(name);
383 self.constants.get(&lowercase_name)
384 }
385
386 #[inline]
389 #[must_use]
390 pub fn get_class_constant(&self, class: &[u8], constant: &[u8]) -> Option<&ClassLikeConstantMetadata> {
391 let lowercase_class = ascii_lowercase_word(class);
392 let constant_name = word(constant);
393 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
394 }
395
396 #[inline]
398 #[must_use]
399 pub fn get_enum_case(&self, class: &[u8], case: &[u8]) -> Option<&EnumCaseMetadata> {
400 let lowercase_class = ascii_lowercase_word(class);
401 let case_name = word(case);
402 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
403 }
404
405 #[inline]
408 #[must_use]
409 pub fn get_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
410 let lowercase_class = ascii_lowercase_word(class);
411 let property_name = word(property);
412 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
413 }
414
415 #[inline]
417 #[must_use]
418 pub fn get_declaring_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
419 let lowercase_class = ascii_lowercase_word(class);
420 let property_name = word(property);
421 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
422 self.class_likes.get(declaring_class)?.properties.get(&property_name)
423 }
424 #[inline]
428 #[must_use]
429 pub fn get_property_type(&self, class: &[u8], property: &[u8]) -> Option<&TUnion> {
430 let lowercase_class = ascii_lowercase_word(class);
431 let property_name = word(property);
432 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
433 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
434 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
435 }
436
437 #[must_use]
439 pub fn get_class_constant_type<'meta>(&'meta self, class: &[u8], constant: &[u8]) -> Option<Cow<'meta, TUnion>> {
440 let lowercase_class = ascii_lowercase_word(class);
441 let constant_name = word(constant);
442 let class_meta = self.class_likes.get(&lowercase_class)?;
443
444 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
446 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
447 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
448 }
449
450 let constant_meta = class_meta.constants.get(&constant_name)?;
452
453 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
455 return Some(Cow::Borrowed(&type_meta.type_union));
456 }
457
458 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
460 }
461
462 #[inline]
464 #[must_use]
465 pub fn get_class_constant_literal_value(&self, class: &[u8], constant: &[u8]) -> Option<&TAtomic> {
466 let lowercase_class = ascii_lowercase_word(class);
467 let constant_name = word(constant);
468 self.class_likes
469 .get(&lowercase_class)
470 .and_then(|meta| meta.constants.get(&constant_name))
471 .and_then(|constant_meta| constant_meta.inferred_type.as_ref())
472 }
473 #[inline]
477 #[must_use]
478 pub fn class_extends(&self, child: &[u8], parent: &[u8]) -> bool {
479 let lowercase_child = ascii_lowercase_word(child);
480 let lowercase_parent = ascii_lowercase_word(parent);
481 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
482 }
483
484 #[inline]
486 #[must_use]
487 pub fn class_directly_extends(&self, child: &[u8], parent: &[u8]) -> bool {
488 let lowercase_child = ascii_lowercase_word(child);
489 let lowercase_parent = ascii_lowercase_word(parent);
490 self.class_likes
491 .get(&lowercase_child)
492 .is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
493 }
494
495 #[inline]
497 #[must_use]
498 pub fn class_implements(&self, class: &[u8], interface: &[u8]) -> bool {
499 let lowercase_class = ascii_lowercase_word(class);
500 let lowercase_interface = ascii_lowercase_word(interface);
501 self.class_likes
502 .get(&lowercase_class)
503 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
504 }
505
506 #[inline]
508 #[must_use]
509 pub fn class_directly_implements(&self, class: &[u8], interface: &[u8]) -> bool {
510 let lowercase_class = ascii_lowercase_word(class);
511 let lowercase_interface = ascii_lowercase_word(interface);
512 self.class_likes
513 .get(&lowercase_class)
514 .is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
515 }
516
517 #[inline]
519 #[must_use]
520 pub fn class_uses_trait(&self, class: &[u8], trait_name: &[u8]) -> bool {
521 let lowercase_class = ascii_lowercase_word(class);
522 let lowercase_trait = ascii_lowercase_word(trait_name);
523 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
524 }
525
526 #[inline]
529 #[must_use]
530 pub fn trait_requires_extends(&self, trait_name: &[u8], class_name: &[u8]) -> bool {
531 let lowercase_trait = ascii_lowercase_word(trait_name);
532
533 self.class_likes.get(&lowercase_trait).is_some_and(|meta| {
534 meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required.as_bytes()))
535 })
536 }
537
538 #[inline]
540 #[must_use]
541 pub fn is_instance_of(&self, child: &[u8], parent: &[u8]) -> bool {
542 if child == parent {
543 return true;
544 }
545
546 let lowercase_child = ascii_lowercase_word(child);
547 let lowercase_parent = ascii_lowercase_word(parent);
548
549 if lowercase_child == lowercase_parent {
550 return true;
551 }
552
553 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
554 meta.all_parent_classes.contains(&lowercase_parent)
555 || meta.all_parent_interfaces.contains(&lowercase_parent)
556 || meta.used_traits.contains(&lowercase_parent)
557 || meta.require_extends.contains(&lowercase_parent)
558 || meta.require_implements.contains(&lowercase_parent)
559 })
560 }
561
562 #[inline]
564 #[must_use]
565 pub fn is_enum_or_final_class(&self, name: &[u8]) -> bool {
566 let lowercase_name = ascii_lowercase_word(name);
567 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
568 }
569
570 #[inline]
573 #[must_use]
574 pub fn is_inheritable(&self, name: &[u8]) -> bool {
575 let lowercase_name = ascii_lowercase_word(name);
576 match self.symbols.get_kind(lowercase_name) {
577 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
578 Some(SymbolKind::Enum) => false,
579 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
580 }
581 }
582
583 #[inline]
585 #[must_use]
586 pub fn get_class_descendants(&self, class: &[u8]) -> WordSet {
587 let lowercase_class = ascii_lowercase_word(class);
588 let mut all_descendants = WordSet::default();
589 let mut queue = vec![&lowercase_class];
590 let mut visited = WordSet::default();
591 visited.insert(lowercase_class);
592
593 while let Some(current_name) = queue.pop() {
594 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
595 for descendant in direct_descendants {
596 if visited.insert(*descendant) {
597 all_descendants.insert(*descendant);
598 queue.push(descendant);
599 }
600 }
601 }
602 }
603
604 all_descendants
605 }
606
607 #[inline]
609 #[must_use]
610 pub fn get_class_ancestors(&self, class: &[u8]) -> WordSet {
611 let lowercase_class = ascii_lowercase_word(class);
612 let mut ancestors = WordSet::default();
613 if let Some(meta) = self.class_likes.get(&lowercase_class) {
614 ancestors.extend(meta.all_parent_classes.iter().copied());
615 ancestors.extend(meta.all_parent_interfaces.iter().copied());
616 }
617 ancestors
618 }
619
620 #[inline]
622 #[must_use]
623 pub fn get_declaring_method_class(&self, class: &[u8], method: &[u8]) -> Option<Word> {
624 let lowercase_class = ascii_lowercase_word(class);
625 let lowercase_method = ascii_lowercase_word(method);
626
627 self.class_likes
628 .get(&lowercase_class)?
629 .declaring_method_ids
630 .get(&lowercase_method)
631 .map(|method_id| method_id.get_class_name())
632 }
633
634 #[inline]
636 #[must_use]
637 pub fn get_appearing_method_class(&self, class: &[u8], method: &[u8]) -> Option<Word> {
638 let lowercase_class = ascii_lowercase_word(class);
639 let lowercase_method = ascii_lowercase_word(method);
640 self.class_likes
641 .get(&lowercase_class)?
642 .appearing_method_ids
643 .get(&lowercase_method)
644 .map(|method_id| method_id.get_class_name())
645 }
646
647 #[must_use]
649 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
650 let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
651 let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
652
653 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
654 return *method_id;
655 };
656
657 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
658 return *declaring_method_id;
659 }
660
661 if class_meta.flags.is_abstract()
662 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
663 && let Some((_, first_method_id)) = overridden_map.first()
664 {
665 return *first_method_id;
666 }
667
668 *method_id
669 }
670
671 #[inline]
673 #[must_use]
674 pub fn method_is_overriding(&self, class: &[u8], method: &[u8]) -> bool {
675 let lowercase_class = ascii_lowercase_word(class);
676 let lowercase_method = ascii_lowercase_word(method);
677 self.class_likes
678 .get(&lowercase_class)
679 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
680 }
681
682 #[inline]
684 #[must_use]
685 pub fn method_is_abstract(&self, class: &[u8], method: &[u8]) -> bool {
686 let lowercase_class = ascii_lowercase_word(class);
687 let lowercase_method = ascii_lowercase_word(method);
688 let identifier = (lowercase_class, lowercase_method);
689 self.function_likes
690 .get(&identifier)
691 .and_then(|meta| meta.method_metadata.as_ref())
692 .is_some_and(|method_meta| method_meta.is_abstract)
693 }
694
695 #[inline]
697 #[must_use]
698 pub fn method_is_static(&self, class: &[u8], method: &[u8]) -> bool {
699 let lowercase_class = ascii_lowercase_word(class);
700 let lowercase_method = ascii_lowercase_word(method);
701 let identifier = (lowercase_class, lowercase_method);
702 self.function_likes
703 .get(&identifier)
704 .and_then(|meta| meta.method_metadata.as_ref())
705 .is_some_and(|method_meta| method_meta.is_static)
706 }
707
708 #[inline]
710 #[must_use]
711 pub fn method_is_final(&self, class: &[u8], method: &[u8]) -> bool {
712 let lowercase_class = ascii_lowercase_word(class);
713 let lowercase_method = ascii_lowercase_word(method);
714 let identifier = (lowercase_class, lowercase_method);
715 self.function_likes
716 .get(&identifier)
717 .and_then(|meta| meta.method_metadata.as_ref())
718 .is_some_and(|method_meta| method_meta.is_final)
719 }
720
721 #[inline]
727 #[must_use]
728 pub fn get_method_visibility(&self, class: &[u8], method: &[u8]) -> Option<Visibility> {
729 let lowercase_class = ascii_lowercase_word(class);
730 let lowercase_method = ascii_lowercase_word(method);
731
732 if let Some(class_meta) = self.class_likes.get(&lowercase_class)
734 && let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
735 {
736 return Some(*overridden_visibility);
737 }
738
739 let declaring_class = self.get_declaring_method_class(class, method)?;
741 let identifier = (declaring_class, lowercase_method);
742
743 self.function_likes
744 .get(&identifier)
745 .and_then(|meta| meta.method_metadata.as_ref())
746 .map(|method_meta| method_meta.visibility)
747 }
748
749 #[must_use]
751 pub fn get_function_like_thrown_types<'meta>(
752 &'meta self,
753 class_like: Option<&'meta ClassLikeMetadata>,
754 function_like: &'meta FunctionLikeMetadata,
755 ) -> &'meta [TypeMetadata] {
756 if !function_like.thrown_types.is_empty() {
757 return function_like.thrown_types.as_slice();
758 }
759
760 if !function_like.kind.is_method() {
761 return &[];
762 }
763
764 let Some(class_like) = class_like else {
765 return &[];
766 };
767
768 let Some(method_name) = function_like.name.as_ref() else {
769 return &[];
770 };
771
772 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
773 for (parent_class_name, parent_method_id) in overridden_map {
774 if class_like.name.as_bytes().eq_ignore_ascii_case(parent_class_name.as_bytes()) {
775 continue; }
777
778 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
779 continue;
780 };
781
782 let parent_method_key = (parent_method_id.get_class_name(), parent_method_id.get_method_name());
783 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
784 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
785 if !thrown.is_empty() {
786 return thrown;
787 }
788 }
789 }
790 }
791
792 &[]
793 }
794
795 #[inline]
797 #[must_use]
798 pub fn get_declaring_property_class(&self, class: &[u8], property: &[u8]) -> Option<Word> {
799 let lowercase_class = ascii_lowercase_word(class);
800 let property_name = word(property);
801 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
802 }
803
804 #[inline]
806 #[must_use]
807 pub fn get_appearing_property_class(&self, class: &[u8], property: &[u8]) -> Option<Word> {
808 let lowercase_class = ascii_lowercase_word(class);
809 let property_name = word(property);
810 self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
811 }
812
813 #[must_use]
815 pub fn get_all_descendants(&self, class: &[u8]) -> WordSet {
816 let lowercase_class = ascii_lowercase_word(class);
817 let mut all_descendants = WordSet::default();
818 let mut queue = vec![&lowercase_class];
819 let mut visited = WordSet::default();
820 visited.insert(lowercase_class);
821
822 while let Some(current_name) = queue.pop() {
823 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
824 for descendant in direct_descendants {
825 if visited.insert(*descendant) {
826 all_descendants.insert(*descendant);
827 queue.push(descendant);
828 }
829 }
830 }
831 }
832
833 all_descendants
834 }
835
836 #[must_use]
838 #[allow(clippy::semicolon_outside_block)]
839 pub fn get_anonymous_class_name(span: mago_span::Span) -> Word {
840 use std::io::Write;
841
842 let mut buffer = [0u8; 64];
843 let mut writer = &mut buffer[..];
844
845 unsafe {
849 write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
850 .unwrap_unchecked();
851 }
852
853 let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
854
855 word(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
858 }
859
860 #[must_use]
862 pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
863 let name = Self::get_anonymous_class_name(span);
864 if self.class_exists(name.as_bytes()) { self.class_likes.get(&name) } else { None }
865 }
866
867 #[inline]
877 #[must_use]
878 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
879 self.file_signatures.get(file_id)
880 }
881
882 #[inline]
893 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
894 self.file_signatures.insert(file_id, signature)
895 }
896
897 #[inline]
907 pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
908 self.file_signatures.remove(file_id)
909 }
910
911 pub fn mark_safe_symbols(&mut self, diff: &CodebaseDiff, references: &SymbolReferences) -> Option<bool> {
926 let (invalid_symbols, partially_invalid) = references.get_invalid_symbols(diff)?;
927
928 for keep_symbol in diff.get_keep() {
930 if !invalid_symbols.contains(keep_symbol) {
931 if keep_symbol.1.is_empty() {
932 if !partially_invalid.contains(&keep_symbol.0) {
934 self.safe_symbols.insert(keep_symbol.0);
935 }
936 } else {
937 self.safe_symbol_members.insert(*keep_symbol);
939 }
940 }
941 }
942
943 Some(invalid_symbols.contains(&(empty_word(), empty_word())))
944 }
945
946 pub fn extend(&mut self, other: CodebaseMetadata) {
951 for (k, mut v) in other.class_likes {
952 match self.class_likes.entry(k) {
953 Entry::Occupied(mut entry) => {
954 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
955 v.version_constraint.merge(entry.get().version_constraint.clone());
956 entry.insert(v);
957 } else {
958 entry.get_mut().version_constraint.merge(v.version_constraint);
959 }
960 }
961 Entry::Vacant(entry) => {
962 entry.insert(v);
963 }
964 }
965 }
966
967 for (k, mut v) in other.function_likes {
968 match self.function_likes.entry(k) {
969 Entry::Occupied(mut entry) => {
970 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
971 v.version_constraint.merge(entry.get().version_constraint.clone());
972 entry.insert(v);
973 } else {
974 entry.get_mut().version_constraint.merge(v.version_constraint);
975 }
976 }
977 Entry::Vacant(entry) => {
978 entry.insert(v);
979 }
980 }
981 }
982
983 for (k, mut v) in other.constants {
984 match self.constants.entry(k) {
985 Entry::Occupied(mut entry) => {
986 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
987 v.version_constraint.merge(entry.get().version_constraint.clone());
988 entry.insert(v);
989 } else {
990 entry.get_mut().version_constraint.merge(v.version_constraint);
991 }
992 }
993 Entry::Vacant(entry) => {
994 entry.insert(v);
995 }
996 }
997 }
998
999 self.symbols.extend(other.symbols);
1000
1001 for (k, v) in other.all_class_like_descendants {
1002 self.all_class_like_descendants.entry(k).or_default().extend(v);
1003 }
1004
1005 for (k, v) in other.direct_classlike_descendants {
1006 self.direct_classlike_descendants.entry(k).or_default().extend(v);
1007 }
1008
1009 self.file_signatures.extend(other.file_signatures);
1010 self.safe_symbols.extend(other.safe_symbols);
1011 self.safe_symbol_members.extend(other.safe_symbol_members);
1012 self.infer_types_from_usage |= other.infer_types_from_usage;
1013 }
1014
1015 pub fn extend_ref(&mut self, other: &CodebaseMetadata) {
1021 for (k, v) in &other.class_likes {
1022 match self.class_likes.entry(*k) {
1023 Entry::Occupied(mut entry) => {
1024 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1025 let mut new = v.clone();
1026 new.version_constraint.merge(entry.get().version_constraint.clone());
1027 entry.insert(new);
1028 } else {
1029 entry.get_mut().version_constraint.merge(v.version_constraint.clone());
1030 }
1031 }
1032 Entry::Vacant(entry) => {
1033 entry.insert(v.clone());
1034 }
1035 }
1036 }
1037
1038 for (k, v) in &other.function_likes {
1039 match self.function_likes.entry(*k) {
1040 Entry::Occupied(mut entry) => {
1041 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1042 let mut new = v.clone();
1043 new.version_constraint.merge(entry.get().version_constraint.clone());
1044 entry.insert(new);
1045 } else {
1046 entry.get_mut().version_constraint.merge(v.version_constraint.clone());
1047 }
1048 }
1049 Entry::Vacant(entry) => {
1050 entry.insert(v.clone());
1051 }
1052 }
1053 }
1054
1055 for (k, v) in &other.constants {
1056 match self.constants.entry(*k) {
1057 Entry::Occupied(mut entry) => {
1058 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1059 let mut new = v.clone();
1060 new.version_constraint.merge(entry.get().version_constraint.clone());
1061 entry.insert(new);
1062 } else {
1063 entry.get_mut().version_constraint.merge(v.version_constraint.clone());
1064 }
1065 }
1066 Entry::Vacant(entry) => {
1067 entry.insert(v.clone());
1068 }
1069 }
1070 }
1071
1072 self.symbols.extend_ref(&other.symbols);
1073
1074 for (k, v) in &other.all_class_like_descendants {
1075 self.all_class_like_descendants.entry(*k).or_default().extend(v.iter().copied());
1076 }
1077
1078 for (k, v) in &other.direct_classlike_descendants {
1079 self.direct_classlike_descendants.entry(*k).or_default().extend(v.iter().copied());
1080 }
1081
1082 for (k, v) in &other.file_signatures {
1083 self.file_signatures.insert(*k, v.clone());
1084 }
1085 self.safe_symbols.extend(other.safe_symbols.iter().copied());
1086 self.safe_symbol_members.extend(other.safe_symbol_members.iter().copied());
1087 self.infer_types_from_usage |= other.infer_types_from_usage;
1088 }
1089
1090 pub fn remove_entries_of(&mut self, file_metadata: &CodebaseMetadata) {
1101 for k in file_metadata.class_likes.keys() {
1102 self.class_likes.remove(k);
1103 }
1104
1105 for k in file_metadata.function_likes.keys() {
1106 self.function_likes.remove(k);
1107 }
1108
1109 for k in file_metadata.constants.keys() {
1110 self.constants.remove(k);
1111 }
1112
1113 for k in file_metadata.class_likes.keys() {
1116 self.symbols.remove(*k);
1117 }
1118
1119 for k in file_metadata.file_signatures.keys() {
1120 self.file_signatures.remove(k);
1121 }
1122 }
1123
1124 #[must_use]
1129 pub fn extract_keys(&self) -> CodebaseEntryKeys {
1130 CodebaseEntryKeys {
1131 class_like_names: self.class_likes.keys().copied().collect(),
1132 function_like_keys: self.function_likes.keys().copied().collect(),
1133 constant_names: self.constants.keys().copied().collect(),
1134 file_ids: self.file_signatures.keys().copied().collect(),
1135 }
1136 }
1137
1138 #[must_use]
1154 pub fn extract_owned_keys(&self, merged: &CodebaseMetadata) -> CodebaseEntryKeys {
1155 let class_like_names = self
1156 .class_likes
1157 .iter()
1158 .filter(|(name, meta)| merged.class_likes.get(*name).is_some_and(|m| m.span == meta.span))
1159 .map(|(name, _)| *name)
1160 .collect();
1161
1162 let function_like_keys = self
1163 .function_likes
1164 .iter()
1165 .filter(|(key, meta)| merged.function_likes.get(*key).is_some_and(|m| m.span == meta.span))
1166 .map(|(key, _)| *key)
1167 .collect();
1168
1169 let constant_names = self
1170 .constants
1171 .iter()
1172 .filter(|(name, meta)| merged.constants.get(*name).is_some_and(|m| m.span == meta.span))
1173 .map(|(name, _)| *name)
1174 .collect();
1175
1176 let file_ids = self.file_signatures.keys().copied().collect();
1178
1179 CodebaseEntryKeys { class_like_names, function_like_keys, constant_names, file_ids }
1180 }
1181
1182 pub fn remove_entries_by_keys(&mut self, keys: &CodebaseEntryKeys) {
1187 for k in &keys.class_like_names {
1188 self.class_likes.remove(k);
1189 self.symbols.remove(*k);
1190 }
1191
1192 for k in &keys.function_like_keys {
1193 self.function_likes.remove(k);
1194 }
1195
1196 for k in &keys.constant_names {
1197 self.constants.remove(k);
1198 }
1199
1200 for k in &keys.file_ids {
1201 self.file_signatures.remove(k);
1202 }
1203 }
1204
1205 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
1207 let mut issues = IssueCollection::new();
1208
1209 for meta in self.class_likes.values_mut() {
1210 if user_defined && !meta.flags.is_user_defined() {
1211 continue;
1212 }
1213 issues.extend(meta.take_issues());
1214 }
1215
1216 for meta in self.function_likes.values_mut() {
1217 if user_defined && !meta.flags.is_user_defined() {
1218 continue;
1219 }
1220 issues.extend(meta.take_issues());
1221 }
1222
1223 for meta in self.constants.values_mut() {
1224 if user_defined && !meta.flags.is_user_defined() {
1225 continue;
1226 }
1227 issues.extend(meta.take_issues());
1228 }
1229
1230 issues
1231 }
1232
1233 #[must_use]
1237 pub fn get_all_file_ids(&self) -> Vec<FileId> {
1238 self.file_signatures.keys().copied().collect()
1239 }
1240}
1241
1242impl Default for CodebaseMetadata {
1243 #[inline]
1244 fn default() -> Self {
1245 Self {
1246 class_likes: WordMap::default(),
1247 function_likes: HashMap::default(),
1248 symbols: Symbols::new(),
1249 infer_types_from_usage: false,
1250 constants: WordMap::default(),
1251 all_class_like_descendants: WordMap::default(),
1252 direct_classlike_descendants: WordMap::default(),
1253 safe_symbols: WordSet::default(),
1254 safe_symbol_members: HashSet::default(),
1255 file_signatures: HashMap::default(),
1256 }
1257 }
1258}
1259
1260fn should_replace_metadata(
1271 existing_flags: MetadataFlags,
1272 existing_span: Span,
1273 new_flags: MetadataFlags,
1274 new_span: Span,
1275) -> bool {
1276 let new_is_user_defined = new_flags.is_user_defined();
1277 let existing_is_user_defined = existing_flags.is_user_defined();
1278
1279 if new_is_user_defined != existing_is_user_defined {
1280 return new_is_user_defined;
1281 }
1282
1283 let new_is_built_in = new_flags.is_built_in();
1284 let existing_is_built_in = existing_flags.is_built_in();
1285
1286 if new_is_built_in != existing_is_built_in {
1287 return new_is_built_in;
1288 }
1289
1290 let new_is_polyfill = new_flags.is_polyfill();
1291 let existing_is_polyfill = existing_flags.is_polyfill();
1292
1293 if new_is_polyfill != existing_is_polyfill {
1294 return !new_is_polyfill;
1295 }
1296
1297 new_span < existing_span
1298}
1299
1300#[cfg(test)]
1301mod should_replace_metadata_tests {
1302 use super::*;
1303
1304 #[test]
1305 fn non_polyfill_replaces_polyfill() {
1306 let polyfill = MetadataFlags::POLYFILL;
1307 let real = MetadataFlags::empty();
1308 assert!(should_replace_metadata(polyfill, Span::dummy(0, 100), real, Span::dummy(0, 100)));
1309 assert!(!should_replace_metadata(real, Span::dummy(0, 100), polyfill, Span::dummy(0, 100)));
1310 }
1311
1312 #[test]
1313 fn polyfill_does_not_replace_non_polyfill_even_with_smaller_span() {
1314 let real = MetadataFlags::empty();
1315 let polyfill = MetadataFlags::POLYFILL;
1316 assert!(!should_replace_metadata(real, Span::dummy(500, 600), polyfill, Span::dummy(0, 10)));
1317 }
1318
1319 #[test]
1320 fn user_defined_beats_polyfill_flag() {
1321 let polyfill_user = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
1322 let plain = MetadataFlags::empty();
1323 assert!(!should_replace_metadata(polyfill_user, Span::dummy(0, 10), plain, Span::dummy(0, 10)));
1324 assert!(should_replace_metadata(plain, Span::dummy(0, 10), polyfill_user, Span::dummy(0, 10)));
1325 }
1326
1327 #[test]
1328 fn two_user_defined_fall_through_to_polyfill_check() {
1329 let a = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
1330 let b = MetadataFlags::USER_DEFINED;
1331 assert!(should_replace_metadata(a, Span::dummy(0, 10), b, Span::dummy(0, 10)));
1332 assert!(!should_replace_metadata(b, Span::dummy(0, 10), a, Span::dummy(0, 10)));
1333 }
1334
1335 #[test]
1336 fn two_non_polyfills_fall_through_to_priority_rules() {
1337 let user = MetadataFlags::USER_DEFINED;
1338 let builtin = MetadataFlags::BUILTIN;
1339 assert!(!should_replace_metadata(user, Span::dummy(0, 10), builtin, Span::dummy(0, 10)));
1340 assert!(should_replace_metadata(builtin, Span::dummy(0, 10), user, Span::dummy(0, 10)));
1341 }
1342}