1use std::borrow::Cow;
2use std::collections::hash_map::Entry;
3
4use foldhash::HashMap;
5use foldhash::HashSet;
6
7use mago_database::file::File;
8use mago_database::file::FileId;
9use mago_reporting::Annotation;
10use mago_reporting::Issue;
11use mago_reporting::IssueCollection;
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::word;
20
21use crate::diff::CodebaseDiff;
22use crate::identifier::method::MethodIdentifier;
23use crate::issue::ScanningIssueKind;
24use crate::metadata::class_like::ClassLikeMetadata;
25use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
26use crate::metadata::constant::ConstantMetadata;
27use crate::metadata::enum_case::EnumCaseMetadata;
28use crate::metadata::flags::MetadataFlags;
29use crate::metadata::function_like::FunctionLikeMetadata;
30use crate::metadata::property::PropertyMetadata;
31use crate::metadata::ttype::TypeMetadata;
32use crate::reference::SymbolReferences;
33use crate::signature::FileSignature;
34use crate::symbol::SymbolKind;
35use crate::symbol::Symbols;
36use crate::ttype::atomic::TAtomic;
37use crate::ttype::atomic::object::TObject;
38use crate::ttype::union::TUnion;
39use crate::visibility::Visibility;
40
41pub mod attribute;
42pub mod class_like;
43pub mod class_like_constant;
44pub mod constant;
45pub mod enum_case;
46pub mod flags;
47pub mod function_like;
48pub mod parameter;
49pub mod property;
50pub mod property_hook;
51pub mod ttype;
52pub mod version_constraint;
53
54#[derive(Debug, Clone)]
60pub struct CodebaseEntryKeys {
61 pub class_like_names: Vec<Word>,
63 pub function_like_keys: Vec<(Word, Word)>,
65 pub constant_names: Vec<Word>,
67 pub file_ids: Vec<FileId>,
69}
70
71#[derive(Clone, Debug, PartialEq, Default)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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 pub patch_class_likes: WordMap<ClassLikeMetadata>,
111 pub patch_function_likes: HashMap<(Word, Word), FunctionLikeMetadata>,
116 pub patch_constants: WordMap<ConstantMetadata>,
118}
119
120impl CodebaseMetadata {
121 #[inline]
123 #[must_use]
124 pub fn new() -> Self {
125 Self::default()
126 }
127
128 #[inline]
137 #[must_use]
138 pub fn class_exists(&self, name: &[u8]) -> bool {
139 let lowercase_name = ascii_lowercase_word(name);
140 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class))
141 }
142
143 #[inline]
145 #[must_use]
146 pub fn interface_exists(&self, name: &[u8]) -> bool {
147 let lowercase_name = ascii_lowercase_word(name);
148 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Interface))
149 }
150
151 #[inline]
153 #[must_use]
154 pub fn trait_exists(&self, name: &[u8]) -> bool {
155 let lowercase_name = ascii_lowercase_word(name);
156 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Trait))
157 }
158
159 #[inline]
161 #[must_use]
162 pub fn enum_exists(&self, name: &[u8]) -> bool {
163 let lowercase_name = ascii_lowercase_word(name);
164 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Enum))
165 }
166
167 #[inline]
169 #[must_use]
170 pub fn class_like_exists(&self, name: &[u8]) -> bool {
171 let lowercase_name = ascii_lowercase_word(name);
172 self.symbols.contains(lowercase_name)
173 }
174
175 #[inline]
177 #[must_use]
178 pub fn namespace_exists(&self, name: &[u8]) -> bool {
179 let lowercase_name = ascii_lowercase_word(name);
180 self.symbols.contains_namespace(lowercase_name)
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn class_or_trait_exists(&self, name: &[u8]) -> bool {
187 let lowercase_name = ascii_lowercase_word(name);
188 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
189 }
190
191 #[inline]
193 #[must_use]
194 pub fn class_or_interface_exists(&self, name: &[u8]) -> bool {
195 let lowercase_name = ascii_lowercase_word(name);
196 matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
197 }
198
199 #[inline]
201 #[must_use]
202 pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
203 let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
204 let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
205 let identifier = (lowercase_class, lowercase_method);
206 self.function_likes.contains_key(&identifier)
207 }
208
209 #[inline]
211 #[must_use]
212 pub fn function_exists(&self, name: &[u8]) -> bool {
213 let lowercase_name = ascii_lowercase_word(name);
214 let identifier = (empty_word(), lowercase_name);
215 self.function_likes.contains_key(&identifier)
216 }
217
218 #[inline]
221 #[must_use]
222 pub fn constant_exists(&self, name: &[u8]) -> bool {
223 let lowercase_name = ascii_lowercase_constant_name_word(name);
224 self.constants.contains_key(&lowercase_name)
225 }
226
227 #[inline]
229 #[must_use]
230 pub fn method_exists(&self, class: &[u8], method: &[u8]) -> bool {
231 let lowercase_class = ascii_lowercase_word(class);
232 let lowercase_method = ascii_lowercase_word(method);
233 self.class_likes
234 .get(&lowercase_class)
235 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
236 }
237
238 #[inline]
243 #[must_use]
244 pub fn property_exists(&self, class: &[u8], property: &[u8]) -> bool {
245 let lowercase_class = ascii_lowercase_word(class);
246 let property_name = word(property);
247 self.class_likes
248 .get(&lowercase_class)
249 .is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
250 }
251
252 #[inline]
255 #[must_use]
256 pub fn magic_property_exists(&self, class: &[u8], property: &[u8]) -> bool {
257 let lowercase_class = ascii_lowercase_word(class);
258 let property_name = word(property);
259 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.magic_property_ids.contains_key(&property_name))
260 }
261
262 #[inline]
265 #[must_use]
266 pub fn class_constant_exists(&self, class: &[u8], constant: &[u8]) -> bool {
267 let lowercase_class = ascii_lowercase_word(class);
268 let constant_name = word(constant);
269 self.class_likes.get(&lowercase_class).is_some_and(|meta| {
270 meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
271 })
272 }
273
274 #[inline]
277 #[must_use]
278 pub fn get_class(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
279 let lowercase_name = ascii_lowercase_word(name);
280 if matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class)) {
281 self.class_likes.get(&lowercase_name)
282 } else {
283 None
284 }
285 }
286
287 #[inline]
289 #[must_use]
290 pub fn get_interface(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
291 let lowercase_name = ascii_lowercase_word(name);
292 if matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Interface)) {
293 self.class_likes.get(&lowercase_name)
294 } else {
295 None
296 }
297 }
298
299 #[inline]
301 #[must_use]
302 pub fn get_trait(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
303 let lowercase_name = ascii_lowercase_word(name);
304 if matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Trait)) {
305 self.class_likes.get(&lowercase_name)
306 } else {
307 None
308 }
309 }
310
311 #[inline]
313 #[must_use]
314 pub fn get_enum(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
315 let lowercase_name = ascii_lowercase_word(name);
316 if self.symbols.contains_enum(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
317 }
318
319 #[inline]
321 #[must_use]
322 pub fn get_class_like(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
323 let lowercase_name = ascii_lowercase_word(name);
324 self.class_likes.get(&lowercase_name)
325 }
326
327 #[inline]
329 #[must_use]
330 pub fn get_function(&self, name: &[u8]) -> Option<&FunctionLikeMetadata> {
331 let lowercase_name = ascii_lowercase_word(name);
332 let identifier = (empty_word(), lowercase_name);
333 self.function_likes.get(&identifier)
334 }
335
336 #[inline]
338 #[must_use]
339 pub fn get_method(&self, class: &[u8], method: &[u8]) -> Option<&FunctionLikeMetadata> {
340 let lowercase_class = ascii_lowercase_word(class);
341 let lowercase_method = ascii_lowercase_word(method);
342 let identifier = (lowercase_class, lowercase_method);
343 self.function_likes.get(&identifier)
344 }
345
346 #[inline]
349 #[must_use]
350 pub fn get_closure(&self, synthetic_name: &Word) -> Option<&FunctionLikeMetadata> {
351 self.function_likes.get(&(empty_word(), *synthetic_name))
352 }
353
354 #[inline]
358 #[must_use]
359 pub fn get_closure_at(&self, file: &File, span: Span) -> Option<&FunctionLikeMetadata> {
360 let name = crate::build_synthetic_name("closure", file, span);
361 self.get_closure(&name)
362 }
363
364 #[inline]
366 #[must_use]
367 pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
368 let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
369 let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
370 let identifier = (lowercase_class, lowercase_method);
371 self.function_likes.get(&identifier)
372 }
373
374 #[inline]
377 #[must_use]
378 pub fn get_declaring_method(&self, class: &[u8], method: &[u8]) -> Option<&FunctionLikeMetadata> {
379 let method_id = MethodIdentifier::new(word(class), word(method));
380 let declaring_method_id = self.get_declaring_method_identifier(&method_id);
381 self.get_method(
382 declaring_method_id.get_class_name().as_bytes(),
383 declaring_method_id.get_method_name().as_bytes(),
384 )
385 }
386
387 #[inline]
390 #[must_use]
391 pub fn get_function_like(
392 &self,
393 identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
394 ) -> Option<&FunctionLikeMetadata> {
395 use crate::identifier::function_like::FunctionLikeIdentifier;
396 match identifier {
397 FunctionLikeIdentifier::Function(name) => self.get_function(name.as_bytes()),
398 FunctionLikeIdentifier::Method(class, method) => self.get_method(class.as_bytes(), method.as_bytes()),
399 FunctionLikeIdentifier::Closure(name) => self.get_closure(name),
400 }
401 }
402
403 #[inline]
406 #[must_use]
407 pub fn get_constant(&self, name: &[u8]) -> Option<&ConstantMetadata> {
408 let lowercase_name = ascii_lowercase_constant_name_word(name);
409 self.constants.get(&lowercase_name)
410 }
411
412 #[inline]
419 #[must_use]
420 pub fn span_of(&self, name: &[u8]) -> Option<Span> {
421 if let Some(meta) = self.get_class_like(name) {
422 return Some(meta.name_span.unwrap_or(meta.span));
423 }
424
425 if let Some(meta) = self.get_function(name) {
426 return Some(meta.name_span.unwrap_or(meta.span));
427 }
428
429 self.get_constant(name).map(|meta| meta.span)
430 }
431
432 #[inline]
435 #[must_use]
436 pub fn get_class_constant(&self, class: &[u8], constant: &[u8]) -> Option<&ClassLikeConstantMetadata> {
437 let lowercase_class = ascii_lowercase_word(class);
438 let constant_name = word(constant);
439 self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
440 }
441
442 #[inline]
444 #[must_use]
445 pub fn get_enum_case(&self, class: &[u8], case: &[u8]) -> Option<&EnumCaseMetadata> {
446 let lowercase_class = ascii_lowercase_word(class);
447 let case_name = word(case);
448 self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
449 }
450
451 #[inline]
456 #[must_use]
457 pub fn get_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
458 let lowercase_class = ascii_lowercase_word(class);
459 let property_name = word(property);
460 self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
461 }
462
463 #[inline]
466 #[must_use]
467 pub fn get_magic_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
468 let lowercase_class = ascii_lowercase_word(class);
469 let property_name = word(property);
470 self.class_likes.get(&lowercase_class)?.magic_properties.get(&property_name)
471 }
472
473 #[inline]
475 #[must_use]
476 pub fn get_declaring_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
477 let lowercase_class = ascii_lowercase_word(class);
478 let property_name = word(property);
479 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
480 self.class_likes.get(declaring_class)?.properties.get(&property_name)
481 }
482
483 #[inline]
486 #[must_use]
487 pub fn get_declaring_magic_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
488 let lowercase_class = ascii_lowercase_word(class);
489 let property_name = word(property);
490 let declaring_class = self.class_likes.get(&lowercase_class)?.magic_property_ids.get(&property_name)?;
491 self.class_likes.get(declaring_class)?.magic_properties.get(&property_name)
492 }
493 #[inline]
497 #[must_use]
498 pub fn get_property_type(&self, class: &[u8], property: &[u8]) -> Option<&TUnion> {
499 let lowercase_class = ascii_lowercase_word(class);
500 let property_name = word(property);
501 let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
502 let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
503 property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
504 }
505
506 #[must_use]
508 pub fn get_class_constant_type<'meta>(&'meta self, class: &[u8], constant: &[u8]) -> Option<Cow<'meta, TUnion>> {
509 let lowercase_class = ascii_lowercase_word(class);
510 let constant_name = word(constant);
511 let class_meta = self.class_likes.get(&lowercase_class)?;
512
513 if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
515 let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
516 return Some(Cow::Owned(TUnion::from_atomic(atomic)));
517 }
518
519 let constant_meta = class_meta.constants.get(&constant_name)?;
521
522 if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
524 return Some(Cow::Borrowed(&type_meta.type_union));
525 }
526
527 constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
529 }
530 #[inline]
534 #[must_use]
535 pub fn class_extends(&self, child: &[u8], parent: &[u8]) -> bool {
536 let lowercase_child = ascii_lowercase_word(child);
537 let lowercase_parent = ascii_lowercase_word(parent);
538 self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
539 }
540
541 #[inline]
543 #[must_use]
544 pub fn class_implements(&self, class: &[u8], interface: &[u8]) -> bool {
545 let lowercase_class = ascii_lowercase_word(class);
546 let lowercase_interface = ascii_lowercase_word(interface);
547 self.class_likes
548 .get(&lowercase_class)
549 .is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
550 }
551
552 #[inline]
554 #[must_use]
555 pub fn class_uses_trait(&self, class: &[u8], trait_name: &[u8]) -> bool {
556 let lowercase_class = ascii_lowercase_word(class);
557 let lowercase_trait = ascii_lowercase_word(trait_name);
558 self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
559 }
560
561 #[inline]
563 #[must_use]
564 pub fn is_instance_of(&self, child: &[u8], parent: &[u8]) -> bool {
565 if child == parent {
566 return true;
567 }
568
569 let lowercase_child = ascii_lowercase_word(child);
570 let lowercase_parent = ascii_lowercase_word(parent);
571
572 if lowercase_child == lowercase_parent {
573 return true;
574 }
575
576 self.class_likes.get(&lowercase_child).is_some_and(|meta| {
577 meta.all_parent_classes.contains(&lowercase_parent)
578 || meta.all_parent_interfaces.contains(&lowercase_parent)
579 || meta.used_traits.contains(&lowercase_parent)
580 || meta.require_extends.contains(&lowercase_parent)
581 || meta.require_implements.contains(&lowercase_parent)
582 })
583 }
584
585 #[inline]
587 #[must_use]
588 pub fn is_enum_or_final_class(&self, name: &[u8]) -> bool {
589 let lowercase_name = ascii_lowercase_word(name);
590 self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
591 }
592
593 #[inline]
596 #[must_use]
597 pub fn is_inheritable(&self, name: &[u8]) -> bool {
598 let lowercase_name = ascii_lowercase_word(name);
599 match self.symbols.get_kind(lowercase_name) {
600 Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
601 Some(SymbolKind::Enum) => false,
602 Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
603 }
604 }
605
606 #[inline]
608 #[must_use]
609 pub fn get_class_descendants(&self, class: &[u8]) -> WordSet {
610 let lowercase_class = ascii_lowercase_word(class);
611 let mut all_descendants = WordSet::default();
612 let mut queue = vec![&lowercase_class];
613 let mut visited = WordSet::default();
614 visited.insert(lowercase_class);
615
616 while let Some(current_name) = queue.pop() {
617 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
618 for descendant in direct_descendants {
619 if visited.insert(*descendant) {
620 all_descendants.insert(*descendant);
621 queue.push(descendant);
622 }
623 }
624 }
625 }
626
627 all_descendants
628 }
629
630 #[inline]
632 #[must_use]
633 pub fn get_class_ancestors(&self, class: &[u8]) -> WordSet {
634 let lowercase_class = ascii_lowercase_word(class);
635 let mut ancestors = WordSet::default();
636 if let Some(meta) = self.class_likes.get(&lowercase_class) {
637 ancestors.extend(meta.all_parent_classes.iter().copied());
638 ancestors.extend(meta.all_parent_interfaces.iter().copied());
639 }
640 ancestors
641 }
642
643 #[inline]
645 #[must_use]
646 pub fn get_declaring_method_class(&self, class: &[u8], method: &[u8]) -> Option<Word> {
647 let lowercase_class = ascii_lowercase_word(class);
648 let lowercase_method = ascii_lowercase_word(method);
649
650 self.class_likes
651 .get(&lowercase_class)?
652 .declaring_method_ids
653 .get(&lowercase_method)
654 .map(|method_id| method_id.get_class_name())
655 }
656
657 #[must_use]
659 pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
660 let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
661 let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
662
663 let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
664 return *method_id;
665 };
666
667 if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
668 return *declaring_method_id;
669 }
670
671 if class_meta.flags.is_abstract()
672 && let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
673 && let Some((_, first_method_id)) = overridden_map.first()
674 {
675 return *first_method_id;
676 }
677
678 *method_id
679 }
680
681 #[inline]
683 #[must_use]
684 pub fn method_is_overriding(&self, class: &[u8], method: &[u8]) -> bool {
685 let lowercase_class = ascii_lowercase_word(class);
686 let lowercase_method = ascii_lowercase_word(method);
687 self.class_likes
688 .get(&lowercase_class)
689 .is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
690 }
691
692 #[inline]
694 #[must_use]
695 pub fn method_is_abstract(&self, class: &[u8], method: &[u8]) -> bool {
696 let lowercase_class = ascii_lowercase_word(class);
697 let lowercase_method = ascii_lowercase_word(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_abstract)
703 }
704
705 #[inline]
707 #[must_use]
708 pub fn method_is_final(&self, class: &[u8], method: &[u8]) -> bool {
709 let lowercase_class = ascii_lowercase_word(class);
710 let lowercase_method = ascii_lowercase_word(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: &[u8], method: &[u8]) -> Option<Visibility> {
726 let lowercase_class = ascii_lowercase_word(class);
727 let lowercase_method = ascii_lowercase_word(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 method_name = &function_like.name;
766
767 if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
768 for (parent_class_name, parent_method_id) in overridden_map {
769 if class_like.name.as_bytes().eq_ignore_ascii_case(parent_class_name.as_bytes()) {
770 continue; }
772
773 let Some(parent_class) = self.class_likes.get(parent_class_name) else {
774 continue;
775 };
776
777 let parent_method_key = (parent_method_id.get_class_name(), parent_method_id.get_method_name());
778 if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
779 let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
780 if !thrown.is_empty() {
781 return thrown;
782 }
783 }
784 }
785 }
786
787 &[]
788 }
789
790 #[inline]
794 #[must_use]
795 pub fn get_declaring_property_class(&self, class: &[u8], property: &[u8]) -> Option<Word> {
796 let lowercase_class = ascii_lowercase_word(class);
797 let property_name = word(property);
798 self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
799 }
800
801 #[must_use]
803 pub fn get_all_descendants(&self, class: &[u8]) -> WordSet {
804 let lowercase_class = ascii_lowercase_word(class);
805 let mut all_descendants = WordSet::default();
806 let mut queue = vec![&lowercase_class];
807 let mut visited = WordSet::default();
808 visited.insert(lowercase_class);
809
810 while let Some(current_name) = queue.pop() {
811 if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
812 for descendant in direct_descendants {
813 if visited.insert(*descendant) {
814 all_descendants.insert(*descendant);
815 queue.push(descendant);
816 }
817 }
818 }
819 }
820
821 all_descendants
822 }
823
824 #[must_use]
827 pub fn get_anonymous_class_name(file: &File, span: Span) -> Word {
828 crate::get_anonymous_class_name(file, span)
829 }
830
831 #[must_use]
834 pub fn get_anonymous_class(&self, file: &File, span: Span) -> Option<&ClassLikeMetadata> {
835 let name = Self::get_anonymous_class_name(file, span);
836 self.get_class_like(name.as_bytes())
837 }
838
839 #[inline]
849 #[must_use]
850 pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
851 self.file_signatures.get(file_id)
852 }
853
854 #[inline]
865 pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
866 self.file_signatures.insert(file_id, signature)
867 }
868
869 pub fn mark_safe_symbols(&mut self, diff: &CodebaseDiff, references: &SymbolReferences) -> Option<WordSet> {
883 let (invalid_symbols, partially_invalid, invalid_files) = references.get_invalid_symbols(diff)?;
884
885 for keep_symbol in diff.get_keep() {
887 if !invalid_symbols.contains(keep_symbol) {
888 if keep_symbol.1.is_empty() {
889 if !partially_invalid.contains(&keep_symbol.0) {
891 self.safe_symbols.insert(keep_symbol.0);
892 }
893 } else {
894 self.safe_symbol_members.insert(*keep_symbol);
896 }
897 }
898 }
899
900 Some(invalid_files)
901 }
902
903 pub fn extend(&mut self, other: CodebaseMetadata) {
908 for (k, mut v) in other.class_likes {
909 match self.class_likes.entry(k) {
910 Entry::Occupied(mut entry) => {
911 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
912 v.version_constraint.merge(entry.get().version_constraint.clone());
913 entry.insert(v);
914 } else {
915 entry.get_mut().version_constraint.merge(v.version_constraint);
916 }
917 }
918 Entry::Vacant(entry) => {
919 entry.insert(v);
920 }
921 }
922 }
923
924 for (k, mut v) in other.function_likes {
925 match self.function_likes.entry(k) {
926 Entry::Occupied(mut entry) => {
927 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
928 v.version_constraint.merge(entry.get().version_constraint.clone());
929 entry.insert(v);
930 } else {
931 entry.get_mut().version_constraint.merge(v.version_constraint);
932 }
933 }
934 Entry::Vacant(entry) => {
935 entry.insert(v);
936 }
937 }
938 }
939
940 for (k, mut v) in other.constants {
941 match self.constants.entry(k) {
942 Entry::Occupied(mut entry) => {
943 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
944 v.version_constraint.merge(entry.get().version_constraint.clone());
945 entry.insert(v);
946 } else {
947 entry.get_mut().version_constraint.merge(v.version_constraint);
948 }
949 }
950 Entry::Vacant(entry) => {
951 entry.insert(v);
952 }
953 }
954 }
955
956 self.symbols.extend(other.symbols);
957
958 for (k, v) in other.all_class_like_descendants {
959 self.all_class_like_descendants.entry(k).or_default().extend(v);
960 }
961
962 for (k, v) in other.direct_classlike_descendants {
963 self.direct_classlike_descendants.entry(k).or_default().extend(v);
964 }
965
966 self.file_signatures.extend(other.file_signatures);
967 self.safe_symbols.extend(other.safe_symbols);
968 self.safe_symbol_members.extend(other.safe_symbol_members);
969 self.infer_types_from_usage |= other.infer_types_from_usage;
970 self.merge_patch_class_likes(other.patch_class_likes);
971 self.merge_patch_function_likes(other.patch_function_likes);
972 self.merge_patch_constants(other.patch_constants);
973 }
974
975 pub fn extend_ref(&mut self, other: &CodebaseMetadata) {
981 for (k, v) in &other.class_likes {
982 match self.class_likes.entry(*k) {
983 Entry::Occupied(mut entry) => {
984 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
985 let mut new = v.clone();
986 new.version_constraint.merge(entry.get().version_constraint.clone());
987 entry.insert(new);
988 } else {
989 entry.get_mut().version_constraint.merge(v.version_constraint.clone());
990 }
991 }
992 Entry::Vacant(entry) => {
993 entry.insert(v.clone());
994 }
995 }
996 }
997
998 for (k, v) in &other.function_likes {
999 match self.function_likes.entry(*k) {
1000 Entry::Occupied(mut entry) => {
1001 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1002 let mut new = v.clone();
1003 new.version_constraint.merge(entry.get().version_constraint.clone());
1004 entry.insert(new);
1005 } else {
1006 entry.get_mut().version_constraint.merge(v.version_constraint.clone());
1007 }
1008 }
1009 Entry::Vacant(entry) => {
1010 entry.insert(v.clone());
1011 }
1012 }
1013 }
1014
1015 for (k, v) in &other.constants {
1016 match self.constants.entry(*k) {
1017 Entry::Occupied(mut entry) => {
1018 if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
1019 let mut new = v.clone();
1020 new.version_constraint.merge(entry.get().version_constraint.clone());
1021 entry.insert(new);
1022 } else {
1023 entry.get_mut().version_constraint.merge(v.version_constraint.clone());
1024 }
1025 }
1026 Entry::Vacant(entry) => {
1027 entry.insert(v.clone());
1028 }
1029 }
1030 }
1031
1032 self.symbols.extend_ref(&other.symbols);
1033
1034 for (k, v) in &other.all_class_like_descendants {
1035 self.all_class_like_descendants.entry(*k).or_default().extend(v.iter().copied());
1036 }
1037
1038 for (k, v) in &other.direct_classlike_descendants {
1039 self.direct_classlike_descendants.entry(*k).or_default().extend(v.iter().copied());
1040 }
1041
1042 for (k, v) in &other.file_signatures {
1043 self.file_signatures.insert(*k, v.clone());
1044 }
1045 self.safe_symbols.extend(other.safe_symbols.iter().copied());
1046 self.safe_symbol_members.extend(other.safe_symbol_members.iter().copied());
1047 self.infer_types_from_usage |= other.infer_types_from_usage;
1048 self.merge_patch_class_likes(other.patch_class_likes.iter().map(|(k, v)| (*k, v.clone())));
1049 self.merge_patch_function_likes(other.patch_function_likes.iter().map(|(k, v)| (*k, v.clone())));
1050 self.merge_patch_constants(other.patch_constants.iter().map(|(k, v)| (*k, v.clone())));
1051 }
1052
1053 fn merge_patch_class_likes(&mut self, incoming: impl IntoIterator<Item = (Word, ClassLikeMetadata)>) {
1060 for (k, v) in incoming {
1061 match self.patch_class_likes.entry(k) {
1062 Entry::Occupied(mut entry) => {
1063 let diagnostic = duplicate_patch_class_diagnostic(entry.get(), &v);
1064 entry.get_mut().issues.push(diagnostic);
1065 }
1066 Entry::Vacant(entry) => {
1067 entry.insert(v);
1068 }
1069 }
1070 }
1071 }
1072
1073 fn merge_patch_function_likes(&mut self, incoming: impl IntoIterator<Item = ((Word, Word), FunctionLikeMetadata)>) {
1077 for (k, v) in incoming {
1078 match self.patch_function_likes.entry(k) {
1079 Entry::Occupied(mut entry) => {
1080 if k.0.is_empty() {
1081 let diagnostic = duplicate_patch_function_diagnostic(entry.get(), &v);
1082 entry.get_mut().issues.push(diagnostic);
1083 }
1084 }
1085 Entry::Vacant(entry) => {
1086 entry.insert(v);
1087 }
1088 }
1089 }
1090 }
1091
1092 fn merge_patch_constants(&mut self, incoming: impl IntoIterator<Item = (Word, ConstantMetadata)>) {
1094 for (k, v) in incoming {
1095 match self.patch_constants.entry(k) {
1096 Entry::Occupied(mut entry) => {
1097 let diagnostic = duplicate_patch_constant_diagnostic(entry.get(), &v);
1098 entry.get_mut().issues.push(diagnostic);
1099 }
1100 Entry::Vacant(entry) => {
1101 entry.insert(v);
1102 }
1103 }
1104 }
1105 }
1106
1107 pub fn convert_partial_to_patch(&mut self) {
1114 for (k, v) in std::mem::take(&mut self.class_likes) {
1115 self.patch_class_likes.insert(k, v);
1116 }
1117
1118 for (k, v) in std::mem::take(&mut self.function_likes) {
1119 self.patch_function_likes.insert(k, v);
1120 }
1121
1122 for (k, v) in std::mem::take(&mut self.constants) {
1123 self.patch_constants.insert(k, v);
1124 }
1125
1126 self.symbols = Symbols::new();
1127 self.all_class_like_descendants.clear();
1128 self.direct_classlike_descendants.clear();
1129 }
1130
1131 pub fn apply_patches_pass(&mut self) {
1141 let mut inherited_overrides: HashSet<(Word, Word)> = HashSet::default();
1145
1146 let class_keys: Vec<Word> = self.patch_class_likes.keys().copied().collect();
1147 for fqcn in class_keys {
1148 let Some(target) = self.class_likes.get(&fqcn) else {
1149 if let Some(p) = self.patch_class_likes.get_mut(&fqcn) {
1150 let diag = orphan_patch_class_diagnostic(p);
1151 p.issues.push(diag);
1152 }
1153 continue;
1154 };
1155 if target.flags.is_user_defined() {
1158 continue;
1159 }
1160
1161 let mut working = target.clone();
1162 let inherited =
1163 collect_inherited_patch_methods(&working, &self.patch_class_likes[&fqcn], &self.class_likes);
1164 inherited_overrides.extend(inherited.iter().map(|method| (fqcn, *method)));
1165 if let Some(patch_entry) = self.patch_class_likes.get_mut(&fqcn) {
1166 working.apply_patch(patch_entry, &inherited);
1167 }
1168 self.class_likes.insert(fqcn, working);
1169 }
1170
1171 let func_keys: Vec<(Word, Word)> = self.patch_function_likes.keys().copied().collect();
1172 for key in func_keys {
1173 let Some(target) = self.function_likes.get(&key) else {
1174 if inherited_overrides.contains(&key) {
1175 if let Some(p) = self.patch_function_likes.get(&key) {
1180 let materialized = p.clone();
1181 self.function_likes.insert(key, materialized);
1182 }
1183 continue;
1184 }
1185 if key.0.is_empty()
1188 && let Some(p) = self.patch_function_likes.get_mut(&key)
1189 {
1190 let diag = orphan_patch_function_diagnostic(p);
1191 p.issues.push(diag);
1192 }
1193 continue;
1194 };
1195 if target.flags.is_user_defined() {
1196 continue;
1197 }
1198
1199 let mut working = target.clone();
1200 if let Some(patch_entry) = self.patch_function_likes.get_mut(&key) {
1201 working.apply_patch(patch_entry);
1202 }
1203 self.function_likes.insert(key, working);
1204 }
1205
1206 let const_keys: Vec<Word> = self.patch_constants.keys().copied().collect();
1207 for fqcn in const_keys {
1208 let Some(target) = self.constants.get(&fqcn) else {
1209 if let Some(p) = self.patch_constants.get_mut(&fqcn) {
1210 let diag = orphan_patch_constant_diagnostic(p);
1211 p.issues.push(diag);
1212 }
1213 continue;
1214 };
1215 if target.flags.is_user_defined() {
1216 continue;
1217 }
1218
1219 let mut working = target.clone();
1220 if let Some(patch_entry) = self.patch_constants.get(&fqcn) {
1221 working.apply_patch(patch_entry);
1222 }
1223 self.constants.insert(fqcn, working);
1224 }
1225 }
1226
1227 #[must_use]
1243 pub fn extract_owned_keys(&self, merged: &CodebaseMetadata) -> CodebaseEntryKeys {
1244 let class_like_names = self
1245 .class_likes
1246 .iter()
1247 .filter(|(name, meta)| merged.class_likes.get(*name).is_some_and(|m| m.span == meta.span))
1248 .map(|(name, _)| *name)
1249 .collect();
1250
1251 let function_like_keys = self
1252 .function_likes
1253 .iter()
1254 .filter(|(key, meta)| merged.function_likes.get(*key).is_some_and(|m| m.span == meta.span))
1255 .map(|(key, _)| *key)
1256 .collect();
1257
1258 let constant_names = self
1259 .constants
1260 .iter()
1261 .filter(|(name, meta)| merged.constants.get(*name).is_some_and(|m| m.span == meta.span))
1262 .map(|(name, _)| *name)
1263 .collect();
1264
1265 let file_ids = self.file_signatures.keys().copied().collect();
1267
1268 CodebaseEntryKeys { class_like_names, function_like_keys, constant_names, file_ids }
1269 }
1270
1271 pub fn remove_entries_by_keys(&mut self, keys: &CodebaseEntryKeys) {
1276 for k in &keys.class_like_names {
1277 self.class_likes.remove(k);
1278 self.symbols.remove(*k);
1279 }
1280
1281 for k in &keys.function_like_keys {
1282 self.function_likes.remove(k);
1283 }
1284
1285 for k in &keys.constant_names {
1286 self.constants.remove(k);
1287 }
1288
1289 for k in &keys.file_ids {
1290 self.file_signatures.remove(k);
1291 }
1292
1293 let removed_files: HashSet<FileId> = keys.file_ids.iter().copied().collect();
1296 self.patch_class_likes.retain(|_, m| !removed_files.contains(&m.span.file_id));
1297 self.patch_function_likes.retain(|_, m| !removed_files.contains(&m.span.file_id));
1298 self.patch_constants.retain(|_, m| !removed_files.contains(&m.span.file_id));
1299 }
1300
1301 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
1303 let mut issues = IssueCollection::new();
1304
1305 for meta in self.class_likes.values_mut() {
1306 if user_defined && !meta.flags.is_user_defined() {
1307 continue;
1308 }
1309 issues.extend(meta.take_issues());
1310 }
1311
1312 for meta in self.function_likes.values_mut() {
1313 if user_defined && !meta.flags.is_user_defined() {
1314 continue;
1315 }
1316 issues.extend(meta.take_issues());
1317 }
1318
1319 for meta in self.constants.values_mut() {
1320 if user_defined && !meta.flags.is_user_defined() {
1321 continue;
1322 }
1323 issues.extend(meta.take_issues());
1324 }
1325
1326 for meta in self.patch_class_likes.values_mut() {
1330 issues.extend(meta.take_issues());
1331 }
1332
1333 for meta in self.patch_function_likes.values_mut() {
1334 issues.extend(meta.take_issues());
1335 }
1336
1337 for meta in self.patch_constants.values_mut() {
1338 issues.extend(meta.take_issues());
1339 }
1340
1341 issues
1342 }
1343
1344 #[must_use]
1348 pub fn get_all_file_ids(&self) -> Vec<FileId> {
1349 self.file_signatures.keys().copied().collect()
1350 }
1351}
1352
1353fn collect_inherited_patch_methods(
1358 target: &ClassLikeMetadata,
1359 patch: &ClassLikeMetadata,
1360 class_likes: &WordMap<ClassLikeMetadata>,
1361) -> WordSet {
1362 if patch.methods.is_empty() {
1363 return WordSet::default();
1364 }
1365 let ancestor_methods = class_like::collect_ancestor_methods(target, class_likes);
1366 patch.methods.iter().filter(|m| ancestor_methods.contains(*m)).copied().collect()
1367}
1368
1369fn duplicate_patch_class_diagnostic(kept: &ClassLikeMetadata, dropped: &ClassLikeMetadata) -> Issue {
1370 Issue::error(format!(
1371 "Multiple patches target `{}`; at most one patch may target a given symbol.",
1372 kept.original_name
1373 ))
1374 .with_code(ScanningIssueKind::PatchDuplicateTarget)
1375 .with_annotation(Annotation::primary(dropped.span).with_message("Duplicate patch for this symbol."))
1376 .with_annotation(Annotation::secondary(kept.span).with_message("Already patched here."))
1377 .with_help("Merge the conflicting declarations into a single patch, or remove all but one.")
1378}
1379
1380fn duplicate_patch_function_diagnostic(kept: &FunctionLikeMetadata, dropped: &FunctionLikeMetadata) -> Issue {
1381 Issue::error(format!(
1382 "Multiple patches target function `{}`; at most one patch may target a given symbol.",
1383 kept.name
1384 ))
1385 .with_code(ScanningIssueKind::PatchDuplicateTarget)
1386 .with_annotation(Annotation::primary(dropped.span).with_message("Duplicate patch for this function."))
1387 .with_annotation(Annotation::secondary(kept.span).with_message("Already patched here."))
1388 .with_help("Merge the conflicting declarations into a single patch, or remove all but one.")
1389}
1390
1391fn duplicate_patch_constant_diagnostic(kept: &ConstantMetadata, dropped: &ConstantMetadata) -> Issue {
1392 Issue::error(format!(
1393 "Multiple patches target constant `{}`; at most one patch may target a given symbol.",
1394 kept.name
1395 ))
1396 .with_code(ScanningIssueKind::PatchDuplicateTarget)
1397 .with_annotation(Annotation::primary(dropped.span).with_message("Duplicate patch for this constant."))
1398 .with_annotation(Annotation::secondary(kept.span).with_message("Already patched here."))
1399 .with_help("Merge the conflicting declarations into a single patch, or remove all but one.")
1400}
1401
1402fn orphan_patch_class_diagnostic(meta: &ClassLikeMetadata) -> Issue {
1403 Issue::error(format!(
1404 "Patch declares `{}` but no vendored or built-in definition exists to patch.",
1405 meta.original_name,
1406 ))
1407 .with_code(ScanningIssueKind::PatchIntroducesNewSymbol)
1408 .with_annotation(Annotation::primary(meta.span))
1409 .with_help(
1410 "The patch may be misnamed or out-of-date relative to the vendored or built-in definition; \
1411 check the symbol name and verify the patch still matches the upstream source.",
1412 )
1413}
1414
1415fn orphan_patch_function_diagnostic(meta: &FunctionLikeMetadata) -> Issue {
1416 Issue::error(format!(
1417 "Patch declares function `{}` but no vendored or built-in definition exists to patch.",
1418 meta.name,
1419 ))
1420 .with_code(ScanningIssueKind::PatchIntroducesNewSymbol)
1421 .with_annotation(Annotation::primary(meta.span))
1422 .with_help(
1423 "The patch may be misnamed or out-of-date relative to the vendored or built-in definition; \
1424 check the function name and verify the patch still matches the upstream source.",
1425 )
1426}
1427
1428fn orphan_patch_constant_diagnostic(meta: &ConstantMetadata) -> Issue {
1429 Issue::error(format!(
1430 "Patch declares constant `{}` but no vendored or built-in definition exists to patch.",
1431 meta.name,
1432 ))
1433 .with_code(ScanningIssueKind::PatchIntroducesNewSymbol)
1434 .with_annotation(Annotation::primary(meta.span))
1435 .with_help(
1436 "The patch may be misnamed or out-of-date relative to the vendored or built-in definition; \
1437 check the constant name and verify the patch still matches the upstream source.",
1438 )
1439}
1440
1441fn should_replace_metadata(
1452 existing_flags: MetadataFlags,
1453 existing_span: Span,
1454 new_flags: MetadataFlags,
1455 new_span: Span,
1456) -> bool {
1457 let new_is_user_defined = new_flags.is_user_defined();
1458 let existing_is_user_defined = existing_flags.is_user_defined();
1459
1460 if new_is_user_defined != existing_is_user_defined {
1461 return new_is_user_defined;
1462 }
1463
1464 let new_is_patch = new_flags.is_patch();
1465 let existing_is_patch = existing_flags.is_patch();
1466
1467 if new_is_patch != existing_is_patch {
1468 return new_is_patch;
1469 }
1470
1471 let new_is_external = new_flags.is_external();
1472 let existing_is_external = existing_flags.is_external();
1473
1474 if new_is_external != existing_is_external {
1475 return new_is_external;
1476 }
1477
1478 let new_is_built_in = new_flags.is_built_in();
1479 let existing_is_built_in = existing_flags.is_built_in();
1480
1481 if new_is_built_in != existing_is_built_in {
1482 return new_is_built_in;
1483 }
1484
1485 let new_is_polyfill = new_flags.is_polyfill();
1486 let existing_is_polyfill = existing_flags.is_polyfill();
1487
1488 if new_is_polyfill != existing_is_polyfill {
1489 return !new_is_polyfill;
1490 }
1491
1492 new_span < existing_span
1493}
1494
1495#[cfg(test)]
1496mod should_replace_metadata_tests {
1497 use super::*;
1498
1499 #[test]
1500 fn non_polyfill_replaces_polyfill() {
1501 let polyfill = MetadataFlags::POLYFILL;
1502 let real = MetadataFlags::empty();
1503 assert!(should_replace_metadata(polyfill, Span::dummy(0, 100), real, Span::dummy(0, 100)));
1504 assert!(!should_replace_metadata(real, Span::dummy(0, 100), polyfill, Span::dummy(0, 100)));
1505 }
1506
1507 #[test]
1508 fn polyfill_does_not_replace_non_polyfill_even_with_smaller_span() {
1509 let real = MetadataFlags::empty();
1510 let polyfill = MetadataFlags::POLYFILL;
1511 assert!(!should_replace_metadata(real, Span::dummy(500, 600), polyfill, Span::dummy(0, 10)));
1512 }
1513
1514 #[test]
1515 fn user_defined_beats_polyfill_flag() {
1516 let polyfill_user = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
1517 let plain = MetadataFlags::empty();
1518 assert!(!should_replace_metadata(polyfill_user, Span::dummy(0, 10), plain, Span::dummy(0, 10)));
1519 assert!(should_replace_metadata(plain, Span::dummy(0, 10), polyfill_user, Span::dummy(0, 10)));
1520 }
1521
1522 #[test]
1523 fn two_user_defined_fall_through_to_polyfill_check() {
1524 let a = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
1525 let b = MetadataFlags::USER_DEFINED;
1526 assert!(should_replace_metadata(a, Span::dummy(0, 10), b, Span::dummy(0, 10)));
1527 assert!(!should_replace_metadata(b, Span::dummy(0, 10), a, Span::dummy(0, 10)));
1528 }
1529
1530 #[test]
1531 fn two_non_polyfills_fall_through_to_priority_rules() {
1532 let user = MetadataFlags::USER_DEFINED;
1533 let builtin = MetadataFlags::BUILTIN;
1534 assert!(!should_replace_metadata(user, Span::dummy(0, 10), builtin, Span::dummy(0, 10)));
1535 assert!(should_replace_metadata(builtin, Span::dummy(0, 10), user, Span::dummy(0, 10)));
1536 }
1537
1538 #[test]
1539 fn patch_beats_vendored() {
1540 let vendored = MetadataFlags::empty();
1541 let patch = MetadataFlags::PATCH;
1542 assert!(should_replace_metadata(vendored, Span::dummy(0, 100), patch, Span::dummy(0, 100)));
1543 assert!(!should_replace_metadata(patch, Span::dummy(0, 100), vendored, Span::dummy(0, 100)));
1544 }
1545
1546 #[test]
1547 fn patch_beats_builtin() {
1548 let builtin = MetadataFlags::BUILTIN;
1549 let patch = MetadataFlags::PATCH;
1550 assert!(should_replace_metadata(builtin, Span::dummy(0, 100), patch, Span::dummy(0, 100)));
1551 assert!(!should_replace_metadata(patch, Span::dummy(0, 100), builtin, Span::dummy(0, 100)));
1552 }
1553
1554 #[test]
1555 fn external_beats_builtin_and_vendored() {
1556 let external = MetadataFlags::EXTERNAL;
1557 let builtin = MetadataFlags::BUILTIN;
1558 let vendored = MetadataFlags::empty();
1559
1560 assert!(should_replace_metadata(builtin, Span::dummy(0, 100), external, Span::dummy(500, 600)));
1561 assert!(!should_replace_metadata(external, Span::dummy(500, 600), builtin, Span::dummy(0, 100)));
1562 assert!(should_replace_metadata(vendored, Span::dummy(0, 100), external, Span::dummy(500, 600)));
1563 assert!(!should_replace_metadata(external, Span::dummy(500, 600), vendored, Span::dummy(0, 100)));
1564 }
1565
1566 #[test]
1567 fn patch_and_user_defined_beat_external() {
1568 let external = MetadataFlags::EXTERNAL;
1569 let patch = MetadataFlags::PATCH;
1570 let user = MetadataFlags::USER_DEFINED;
1571
1572 assert!(should_replace_metadata(external, Span::dummy(0, 100), patch, Span::dummy(500, 600)));
1573 assert!(!should_replace_metadata(patch, Span::dummy(500, 600), external, Span::dummy(0, 100)));
1574 assert!(should_replace_metadata(external, Span::dummy(0, 100), user, Span::dummy(500, 600)));
1575 assert!(!should_replace_metadata(user, Span::dummy(500, 600), external, Span::dummy(0, 100)));
1576 }
1577
1578 #[test]
1579 fn user_defined_beats_patch() {
1580 let user = MetadataFlags::USER_DEFINED;
1581 let patch = MetadataFlags::PATCH;
1582 assert!(!should_replace_metadata(user, Span::dummy(0, 100), patch, Span::dummy(0, 100)));
1583 assert!(should_replace_metadata(patch, Span::dummy(0, 100), user, Span::dummy(0, 100)));
1584 }
1585
1586 #[test]
1587 fn patch_does_not_beat_user_defined_even_with_smaller_span() {
1588 let user = MetadataFlags::USER_DEFINED;
1589 let patch = MetadataFlags::PATCH;
1590 assert!(!should_replace_metadata(user, Span::dummy(500, 600), patch, Span::dummy(0, 10)));
1591 }
1592
1593 #[test]
1594 #[allow(clippy::expect_used)]
1595 fn patch_function_like_leaves_vendor_owning_slot() {
1596 use crate::metadata::function_like::FunctionLikeKind;
1601
1602 let name = word("foo");
1603 let key = (empty_word(), name);
1604 let vendor_span = Span::dummy(0, 100);
1605 let patch_span = Span::dummy(500, 600);
1606
1607 let vendor =
1608 FunctionLikeMetadata::new(FunctionLikeKind::Function, name, name, vendor_span, MetadataFlags::empty());
1609 let mut codebase = CodebaseMetadata::new();
1610 codebase.function_likes.insert(key, vendor);
1611
1612 let patch = FunctionLikeMetadata::new(FunctionLikeKind::Function, name, name, patch_span, MetadataFlags::PATCH);
1613 codebase.patch_function_likes.insert(key, patch);
1614
1615 codebase.apply_patches_pass();
1616
1617 let merged = codebase.function_likes.get(&key).expect("function-like must remain after patch");
1618 assert_eq!(merged.span, vendor_span, "patch must not move the slot's span");
1619 assert!(!merged.flags.is_patch(), "patch must not flip the slot's flags");
1620 }
1621
1622 #[test]
1623 fn patch_does_not_apply_to_user_defined_class() {
1624 let class_name = word("MyClass");
1625 let method_existing = word("doIt");
1626
1627 let mut user_class =
1628 ClassLikeMetadata::new(class_name, class_name, Span::dummy(0, 100), None, MetadataFlags::USER_DEFINED);
1629 user_class.methods.insert(method_existing);
1630
1631 let mut codebase = CodebaseMetadata::new();
1632 codebase.class_likes.insert(class_name, user_class);
1633
1634 let mut patch_class =
1635 ClassLikeMetadata::new(class_name, class_name, Span::dummy(0, 50), None, MetadataFlags::PATCH);
1636 let method_new = word("patchedMethod");
1637 patch_class.methods.insert(method_new);
1638
1639 codebase.patch_class_likes.insert(class_name, patch_class);
1640
1641 codebase.apply_patches_pass();
1642
1643 let class = &codebase.class_likes[&class_name];
1644 assert!(!class.methods.contains(&method_new));
1646 assert!(class.methods.contains(&method_existing));
1648 assert!(class.flags.is_user_defined());
1649 assert!(class.issues.is_empty());
1651 }
1652}