use std::borrow::Cow;
use std::collections::hash_map::Entry;
use foldhash::HashMap;
use foldhash::HashSet;
use mago_database::file::File;
use mago_database::file::FileId;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_reporting::IssueCollection;
use mago_span::Span;
use mago_word::Word;
use mago_word::WordMap;
use mago_word::WordSet;
use mago_word::ascii_lowercase_constant_name_word;
use mago_word::ascii_lowercase_word;
use mago_word::empty_word;
use mago_word::word;
use crate::diff::CodebaseDiff;
use crate::identifier::method::MethodIdentifier;
use crate::issue::ScanningIssueKind;
use crate::metadata::class_like::ClassLikeMetadata;
use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
use crate::metadata::constant::ConstantMetadata;
use crate::metadata::enum_case::EnumCaseMetadata;
use crate::metadata::flags::MetadataFlags;
use crate::metadata::function_like::FunctionLikeMetadata;
use crate::metadata::property::PropertyMetadata;
use crate::metadata::ttype::TypeMetadata;
use crate::reference::SymbolReferences;
use crate::signature::FileSignature;
use crate::symbol::SymbolKind;
use crate::symbol::Symbols;
use crate::ttype::atomic::TAtomic;
use crate::ttype::atomic::object::TObject;
use crate::ttype::union::TUnion;
use crate::visibility::Visibility;
pub mod attribute;
pub mod class_like;
pub mod class_like_constant;
pub mod constant;
pub mod enum_case;
pub mod flags;
pub mod function_like;
pub mod parameter;
pub mod property;
pub mod property_hook;
pub mod ttype;
pub mod version_constraint;
#[derive(Debug, Clone)]
pub struct CodebaseEntryKeys {
pub class_like_names: Vec<Word>,
pub function_like_keys: Vec<(Word, Word)>,
pub constant_names: Vec<Word>,
pub file_ids: Vec<FileId>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[allow(clippy::unsafe_derive_deserialize)]
pub struct CodebaseMetadata {
pub infer_types_from_usage: bool,
pub class_likes: WordMap<ClassLikeMetadata>,
pub function_likes: HashMap<(Word, Word), FunctionLikeMetadata>,
pub symbols: Symbols,
pub constants: WordMap<ConstantMetadata>,
pub all_class_like_descendants: WordMap<WordSet>,
pub direct_classlike_descendants: WordMap<WordSet>,
pub safe_symbols: WordSet,
pub safe_symbol_members: HashSet<(Word, Word)>,
pub file_signatures: HashMap<FileId, FileSignature>,
pub patch_class_likes: WordMap<ClassLikeMetadata>,
pub patch_function_likes: HashMap<(Word, Word), FunctionLikeMetadata>,
pub patch_constants: WordMap<ConstantMetadata>,
}
impl CodebaseMetadata {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn class_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class))
}
#[inline]
#[must_use]
pub fn interface_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Interface))
}
#[inline]
#[must_use]
pub fn trait_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Trait))
}
#[inline]
#[must_use]
pub fn enum_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Enum))
}
#[inline]
#[must_use]
pub fn class_like_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
self.symbols.contains(lowercase_name)
}
#[inline]
#[must_use]
pub fn namespace_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
self.symbols.contains_namespace(lowercase_name)
}
#[inline]
#[must_use]
pub fn class_or_trait_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
}
#[inline]
#[must_use]
pub fn class_or_interface_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Interface))
}
#[inline]
#[must_use]
pub fn method_identifier_exists(&self, method_id: &MethodIdentifier) -> bool {
let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
let identifier = (lowercase_class, lowercase_method);
self.function_likes.contains_key(&identifier)
}
#[inline]
#[must_use]
pub fn function_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
let identifier = (empty_word(), lowercase_name);
self.function_likes.contains_key(&identifier)
}
#[inline]
#[must_use]
pub fn constant_exists(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_constant_name_word(name);
self.constants.contains_key(&lowercase_name)
}
#[inline]
#[must_use]
pub fn method_exists(&self, class: &[u8], method: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
self.class_likes
.get(&lowercase_class)
.is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowercase_method))
}
#[inline]
#[must_use]
pub fn property_exists(&self, class: &[u8], property: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
self.class_likes
.get(&lowercase_class)
.is_some_and(|meta| meta.appearing_property_ids.contains_key(&property_name))
}
#[inline]
#[must_use]
pub fn class_constant_exists(&self, class: &[u8], constant: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let constant_name = word(constant);
self.class_likes.get(&lowercase_class).is_some_and(|meta| {
meta.constants.contains_key(&constant_name) || meta.enum_cases.contains_key(&constant_name)
})
}
#[inline]
#[must_use]
pub fn method_is_declared_in_class(&self, class: &[u8], method: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
self.class_likes
.get(&lowercase_class)
.and_then(|meta| meta.declaring_method_ids.get(&lowercase_method))
.is_some_and(|method_id| method_id.get_class_name() == lowercase_class)
}
#[inline]
#[must_use]
pub fn property_is_declared_in_class(&self, class: &[u8], property: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.properties.contains_key(&property_name))
}
#[inline]
#[must_use]
pub fn get_class(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_word(name);
if self.symbols.contains_class(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
}
#[inline]
#[must_use]
pub fn get_interface(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_word(name);
if self.symbols.contains_interface(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
}
#[inline]
#[must_use]
pub fn get_trait(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_word(name);
if self.symbols.contains_trait(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
}
#[inline]
#[must_use]
pub fn get_enum(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_word(name);
if self.symbols.contains_enum(lowercase_name) { self.class_likes.get(&lowercase_name) } else { None }
}
#[inline]
#[must_use]
pub fn get_class_like(&self, name: &[u8]) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_word(name);
self.class_likes.get(&lowercase_name)
}
#[inline]
#[must_use]
pub fn get_function(&self, name: &[u8]) -> Option<&FunctionLikeMetadata> {
let lowercase_name = ascii_lowercase_word(name);
let identifier = (empty_word(), lowercase_name);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_method(&self, class: &[u8], method: &[u8]) -> Option<&FunctionLikeMetadata> {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
let identifier = (lowercase_class, lowercase_method);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_closure(&self, synthetic_name: &Word) -> Option<&FunctionLikeMetadata> {
self.function_likes.get(&(empty_word(), *synthetic_name))
}
#[inline]
#[must_use]
pub fn get_closure_at(&self, file: &File, span: Span) -> Option<&FunctionLikeMetadata> {
let name = crate::build_synthetic_name("closure", file, span);
self.get_closure(&name)
}
#[inline]
#[must_use]
pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
let identifier = (lowercase_class, lowercase_method);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_declaring_method(&self, class: &[u8], method: &[u8]) -> Option<&FunctionLikeMetadata> {
let method_id = MethodIdentifier::new(word(class), word(method));
let declaring_method_id = self.get_declaring_method_identifier(&method_id);
self.get_method(
declaring_method_id.get_class_name().as_bytes(),
declaring_method_id.get_method_name().as_bytes(),
)
}
#[inline]
#[must_use]
pub fn get_function_like(
&self,
identifier: &crate::identifier::function_like::FunctionLikeIdentifier,
) -> Option<&FunctionLikeMetadata> {
use crate::identifier::function_like::FunctionLikeIdentifier;
match identifier {
FunctionLikeIdentifier::Function(name) => self.get_function(name.as_bytes()),
FunctionLikeIdentifier::Method(class, method) => self.get_method(class.as_bytes(), method.as_bytes()),
FunctionLikeIdentifier::Closure(name) => self.get_closure(name),
}
}
#[inline]
#[must_use]
pub fn get_constant(&self, name: &[u8]) -> Option<&ConstantMetadata> {
let lowercase_name = ascii_lowercase_constant_name_word(name);
self.constants.get(&lowercase_name)
}
#[inline]
#[must_use]
pub fn span_of(&self, name: &[u8]) -> Option<Span> {
if let Some(meta) = self.get_class_like(name) {
return Some(meta.name_span.unwrap_or(meta.span));
}
if let Some(meta) = self.get_function(name) {
return Some(meta.name_span.unwrap_or(meta.span));
}
self.get_constant(name).map(|meta| meta.span)
}
#[inline]
#[must_use]
pub fn get_class_constant(&self, class: &[u8], constant: &[u8]) -> Option<&ClassLikeConstantMetadata> {
let lowercase_class = ascii_lowercase_word(class);
let constant_name = word(constant);
self.class_likes.get(&lowercase_class).and_then(|meta| meta.constants.get(&constant_name))
}
#[inline]
#[must_use]
pub fn get_enum_case(&self, class: &[u8], case: &[u8]) -> Option<&EnumCaseMetadata> {
let lowercase_class = ascii_lowercase_word(class);
let case_name = word(case);
self.class_likes.get(&lowercase_class).and_then(|meta| meta.enum_cases.get(&case_name))
}
#[inline]
#[must_use]
pub fn get_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
}
#[inline]
#[must_use]
pub fn get_declaring_property(&self, class: &[u8], property: &[u8]) -> Option<&PropertyMetadata> {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
self.class_likes.get(declaring_class)?.properties.get(&property_name)
}
#[inline]
#[must_use]
pub fn get_property_type(&self, class: &[u8], property: &[u8]) -> Option<&TUnion> {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
let declaring_class = self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name)?;
let property_meta = self.class_likes.get(declaring_class)?.properties.get(&property_name)?;
property_meta.type_metadata.as_ref().map(|tm| &tm.type_union)
}
#[must_use]
pub fn get_class_constant_type<'meta>(&'meta self, class: &[u8], constant: &[u8]) -> Option<Cow<'meta, TUnion>> {
let lowercase_class = ascii_lowercase_word(class);
let constant_name = word(constant);
let class_meta = self.class_likes.get(&lowercase_class)?;
if class_meta.kind.is_enum() && class_meta.enum_cases.contains_key(&constant_name) {
let atomic = TAtomic::Object(TObject::new_enum_case(class_meta.original_name, constant_name));
return Some(Cow::Owned(TUnion::from_atomic(atomic)));
}
let constant_meta = class_meta.constants.get(&constant_name)?;
if let Some(type_meta) = constant_meta.type_metadata.as_ref() {
return Some(Cow::Borrowed(&type_meta.type_union));
}
constant_meta.inferred_type.as_ref().map(|atomic| Cow::Owned(TUnion::from_atomic(atomic.clone())))
}
#[inline]
#[must_use]
pub fn get_class_constant_literal_value(&self, class: &[u8], constant: &[u8]) -> Option<&TAtomic> {
let lowercase_class = ascii_lowercase_word(class);
let constant_name = word(constant);
self.class_likes
.get(&lowercase_class)
.and_then(|meta| meta.constants.get(&constant_name))
.and_then(|constant_meta| constant_meta.inferred_type.as_ref())
}
#[inline]
#[must_use]
pub fn class_extends(&self, child: &[u8], parent: &[u8]) -> bool {
let lowercase_child = ascii_lowercase_word(child);
let lowercase_parent = ascii_lowercase_word(parent);
self.class_likes.get(&lowercase_child).is_some_and(|meta| meta.all_parent_classes.contains(&lowercase_parent))
}
#[inline]
#[must_use]
pub fn class_directly_extends(&self, child: &[u8], parent: &[u8]) -> bool {
let lowercase_child = ascii_lowercase_word(child);
let lowercase_parent = ascii_lowercase_word(parent);
self.class_likes
.get(&lowercase_child)
.is_some_and(|meta| meta.direct_parent_class.as_ref() == Some(&lowercase_parent))
}
#[inline]
#[must_use]
pub fn class_implements(&self, class: &[u8], interface: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_interface = ascii_lowercase_word(interface);
self.class_likes
.get(&lowercase_class)
.is_some_and(|meta| meta.all_parent_interfaces.contains(&lowercase_interface))
}
#[inline]
#[must_use]
pub fn class_directly_implements(&self, class: &[u8], interface: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_interface = ascii_lowercase_word(interface);
self.class_likes
.get(&lowercase_class)
.is_some_and(|meta| meta.direct_parent_interfaces.contains(&lowercase_interface))
}
#[inline]
#[must_use]
pub fn class_uses_trait(&self, class: &[u8], trait_name: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_trait = ascii_lowercase_word(trait_name);
self.class_likes.get(&lowercase_class).is_some_and(|meta| meta.used_traits.contains(&lowercase_trait))
}
#[inline]
#[must_use]
pub fn trait_requires_extends(&self, trait_name: &[u8], class_name: &[u8]) -> bool {
let lowercase_trait = ascii_lowercase_word(trait_name);
self.class_likes.get(&lowercase_trait).is_some_and(|meta| {
meta.require_extends.iter().any(|required| self.is_instance_of(class_name, required.as_bytes()))
})
}
#[inline]
#[must_use]
pub fn is_instance_of(&self, child: &[u8], parent: &[u8]) -> bool {
if child == parent {
return true;
}
let lowercase_child = ascii_lowercase_word(child);
let lowercase_parent = ascii_lowercase_word(parent);
if lowercase_child == lowercase_parent {
return true;
}
self.class_likes.get(&lowercase_child).is_some_and(|meta| {
meta.all_parent_classes.contains(&lowercase_parent)
|| meta.all_parent_interfaces.contains(&lowercase_parent)
|| meta.used_traits.contains(&lowercase_parent)
|| meta.require_extends.contains(&lowercase_parent)
|| meta.require_implements.contains(&lowercase_parent)
})
}
#[inline]
#[must_use]
pub fn is_enum_or_final_class(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
self.class_likes.get(&lowercase_name).is_some_and(|meta| meta.kind.is_enum() || meta.flags.is_final())
}
#[inline]
#[must_use]
pub fn is_inheritable(&self, name: &[u8]) -> bool {
let lowercase_name = ascii_lowercase_word(name);
match self.symbols.get_kind(lowercase_name) {
Some(SymbolKind::Class) => self.class_likes.get(&lowercase_name).is_some_and(|meta| !meta.flags.is_final()),
Some(SymbolKind::Enum) => false,
Some(SymbolKind::Interface | SymbolKind::Trait) | None => true,
}
}
#[inline]
#[must_use]
pub fn get_class_descendants(&self, class: &[u8]) -> WordSet {
let lowercase_class = ascii_lowercase_word(class);
let mut all_descendants = WordSet::default();
let mut queue = vec![&lowercase_class];
let mut visited = WordSet::default();
visited.insert(lowercase_class);
while let Some(current_name) = queue.pop() {
if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
for descendant in direct_descendants {
if visited.insert(*descendant) {
all_descendants.insert(*descendant);
queue.push(descendant);
}
}
}
}
all_descendants
}
#[inline]
#[must_use]
pub fn get_class_ancestors(&self, class: &[u8]) -> WordSet {
let lowercase_class = ascii_lowercase_word(class);
let mut ancestors = WordSet::default();
if let Some(meta) = self.class_likes.get(&lowercase_class) {
ancestors.extend(meta.all_parent_classes.iter().copied());
ancestors.extend(meta.all_parent_interfaces.iter().copied());
}
ancestors
}
#[inline]
#[must_use]
pub fn get_declaring_method_class(&self, class: &[u8], method: &[u8]) -> Option<Word> {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
self.class_likes
.get(&lowercase_class)?
.declaring_method_ids
.get(&lowercase_method)
.map(|method_id| method_id.get_class_name())
}
#[inline]
#[must_use]
pub fn get_appearing_method_class(&self, class: &[u8], method: &[u8]) -> Option<Word> {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
self.class_likes
.get(&lowercase_class)?
.appearing_method_ids
.get(&lowercase_method)
.map(|method_id| method_id.get_class_name())
}
#[must_use]
pub fn get_declaring_method_identifier(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
let lowercase_class = ascii_lowercase_word(method_id.get_class_name().as_bytes());
let lowercase_method = ascii_lowercase_word(method_id.get_method_name().as_bytes());
let Some(class_meta) = self.class_likes.get(&lowercase_class) else {
return *method_id;
};
if let Some(declaring_method_id) = class_meta.declaring_method_ids.get(&lowercase_method) {
return *declaring_method_id;
}
if class_meta.flags.is_abstract()
&& let Some(overridden_map) = class_meta.overridden_method_ids.get(&lowercase_method)
&& let Some((_, first_method_id)) = overridden_map.first()
{
return *first_method_id;
}
*method_id
}
#[inline]
#[must_use]
pub fn method_is_overriding(&self, class: &[u8], method: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
self.class_likes
.get(&lowercase_class)
.is_some_and(|meta| meta.overridden_method_ids.contains_key(&lowercase_method))
}
#[inline]
#[must_use]
pub fn method_is_abstract(&self, class: &[u8], method: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
let identifier = (lowercase_class, lowercase_method);
self.function_likes
.get(&identifier)
.and_then(|meta| meta.method_metadata.as_ref())
.is_some_and(|method_meta| method_meta.is_abstract)
}
#[inline]
#[must_use]
pub fn method_is_static(&self, class: &[u8], method: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
let identifier = (lowercase_class, lowercase_method);
self.function_likes
.get(&identifier)
.and_then(|meta| meta.method_metadata.as_ref())
.is_some_and(|method_meta| method_meta.is_static)
}
#[inline]
#[must_use]
pub fn method_is_final(&self, class: &[u8], method: &[u8]) -> bool {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
let identifier = (lowercase_class, lowercase_method);
self.function_likes
.get(&identifier)
.and_then(|meta| meta.method_metadata.as_ref())
.is_some_and(|method_meta| method_meta.is_final)
}
#[inline]
#[must_use]
pub fn get_method_visibility(&self, class: &[u8], method: &[u8]) -> Option<Visibility> {
let lowercase_class = ascii_lowercase_word(class);
let lowercase_method = ascii_lowercase_word(method);
if let Some(class_meta) = self.class_likes.get(&lowercase_class)
&& let Some(overridden_visibility) = class_meta.trait_visibility_map.get(&lowercase_method)
{
return Some(*overridden_visibility);
}
let declaring_class = self.get_declaring_method_class(class, method)?;
let identifier = (declaring_class, lowercase_method);
self.function_likes
.get(&identifier)
.and_then(|meta| meta.method_metadata.as_ref())
.map(|method_meta| method_meta.visibility)
}
#[must_use]
pub fn get_function_like_thrown_types<'meta>(
&'meta self,
class_like: Option<&'meta ClassLikeMetadata>,
function_like: &'meta FunctionLikeMetadata,
) -> &'meta [TypeMetadata] {
if !function_like.thrown_types.is_empty() {
return function_like.thrown_types.as_slice();
}
if !function_like.kind.is_method() {
return &[];
}
let Some(class_like) = class_like else {
return &[];
};
let method_name = &function_like.name;
if let Some(overridden_map) = class_like.overridden_method_ids.get(method_name) {
for (parent_class_name, parent_method_id) in overridden_map {
if class_like.name.as_bytes().eq_ignore_ascii_case(parent_class_name.as_bytes()) {
continue; }
let Some(parent_class) = self.class_likes.get(parent_class_name) else {
continue;
};
let parent_method_key = (parent_method_id.get_class_name(), parent_method_id.get_method_name());
if let Some(parent_method) = self.function_likes.get(&parent_method_key) {
let thrown = self.get_function_like_thrown_types(Some(parent_class), parent_method);
if !thrown.is_empty() {
return thrown;
}
}
}
}
&[]
}
#[inline]
#[must_use]
pub fn get_declaring_property_class(&self, class: &[u8], property: &[u8]) -> Option<Word> {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
self.class_likes.get(&lowercase_class)?.declaring_property_ids.get(&property_name).copied()
}
#[inline]
#[must_use]
pub fn get_appearing_property_class(&self, class: &[u8], property: &[u8]) -> Option<Word> {
let lowercase_class = ascii_lowercase_word(class);
let property_name = word(property);
self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
}
#[must_use]
pub fn get_all_descendants(&self, class: &[u8]) -> WordSet {
let lowercase_class = ascii_lowercase_word(class);
let mut all_descendants = WordSet::default();
let mut queue = vec![&lowercase_class];
let mut visited = WordSet::default();
visited.insert(lowercase_class);
while let Some(current_name) = queue.pop() {
if let Some(direct_descendants) = self.direct_classlike_descendants.get(current_name) {
for descendant in direct_descendants {
if visited.insert(*descendant) {
all_descendants.insert(*descendant);
queue.push(descendant);
}
}
}
}
all_descendants
}
#[must_use]
pub fn get_anonymous_class_name(file: &File, span: Span) -> Word {
crate::get_anonymous_class_name(file, span)
}
#[must_use]
pub fn get_anonymous_class(&self, file: &File, span: Span) -> Option<&ClassLikeMetadata> {
let name = Self::get_anonymous_class_name(file, span);
self.get_class_like(name.as_bytes())
}
#[inline]
#[must_use]
pub fn get_file_signature(&self, file_id: &FileId) -> Option<&FileSignature> {
self.file_signatures.get(file_id)
}
#[inline]
pub fn set_file_signature(&mut self, file_id: FileId, signature: FileSignature) -> Option<FileSignature> {
self.file_signatures.insert(file_id, signature)
}
#[inline]
pub fn remove_file_signature(&mut self, file_id: &FileId) -> Option<FileSignature> {
self.file_signatures.remove(file_id)
}
pub fn mark_safe_symbols(&mut self, diff: &CodebaseDiff, references: &SymbolReferences) -> Option<bool> {
let (invalid_symbols, partially_invalid) = references.get_invalid_symbols(diff)?;
for keep_symbol in diff.get_keep() {
if !invalid_symbols.contains(keep_symbol) {
if keep_symbol.1.is_empty() {
if !partially_invalid.contains(&keep_symbol.0) {
self.safe_symbols.insert(keep_symbol.0);
}
} else {
self.safe_symbol_members.insert(*keep_symbol);
}
}
}
Some(invalid_symbols.contains(&(empty_word(), empty_word())))
}
pub fn extend(&mut self, other: CodebaseMetadata) {
for (k, mut v) in other.class_likes {
match self.class_likes.entry(k) {
Entry::Occupied(mut entry) => {
if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
v.version_constraint.merge(entry.get().version_constraint.clone());
entry.insert(v);
} else {
entry.get_mut().version_constraint.merge(v.version_constraint);
}
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
for (k, mut v) in other.function_likes {
match self.function_likes.entry(k) {
Entry::Occupied(mut entry) => {
if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
v.version_constraint.merge(entry.get().version_constraint.clone());
entry.insert(v);
} else {
entry.get_mut().version_constraint.merge(v.version_constraint);
}
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
for (k, mut v) in other.constants {
match self.constants.entry(k) {
Entry::Occupied(mut entry) => {
if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
v.version_constraint.merge(entry.get().version_constraint.clone());
entry.insert(v);
} else {
entry.get_mut().version_constraint.merge(v.version_constraint);
}
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
self.symbols.extend(other.symbols);
for (k, v) in other.all_class_like_descendants {
self.all_class_like_descendants.entry(k).or_default().extend(v);
}
for (k, v) in other.direct_classlike_descendants {
self.direct_classlike_descendants.entry(k).or_default().extend(v);
}
self.file_signatures.extend(other.file_signatures);
self.safe_symbols.extend(other.safe_symbols);
self.safe_symbol_members.extend(other.safe_symbol_members);
self.infer_types_from_usage |= other.infer_types_from_usage;
self.merge_patch_class_likes(other.patch_class_likes);
self.merge_patch_function_likes(other.patch_function_likes);
self.merge_patch_constants(other.patch_constants);
}
pub fn extend_ref(&mut self, other: &CodebaseMetadata) {
for (k, v) in &other.class_likes {
match self.class_likes.entry(*k) {
Entry::Occupied(mut entry) => {
if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
let mut new = v.clone();
new.version_constraint.merge(entry.get().version_constraint.clone());
entry.insert(new);
} else {
entry.get_mut().version_constraint.merge(v.version_constraint.clone());
}
}
Entry::Vacant(entry) => {
entry.insert(v.clone());
}
}
}
for (k, v) in &other.function_likes {
match self.function_likes.entry(*k) {
Entry::Occupied(mut entry) => {
if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
let mut new = v.clone();
new.version_constraint.merge(entry.get().version_constraint.clone());
entry.insert(new);
} else {
entry.get_mut().version_constraint.merge(v.version_constraint.clone());
}
}
Entry::Vacant(entry) => {
entry.insert(v.clone());
}
}
}
for (k, v) in &other.constants {
match self.constants.entry(*k) {
Entry::Occupied(mut entry) => {
if should_replace_metadata(entry.get().flags, entry.get().span, v.flags, v.span) {
let mut new = v.clone();
new.version_constraint.merge(entry.get().version_constraint.clone());
entry.insert(new);
} else {
entry.get_mut().version_constraint.merge(v.version_constraint.clone());
}
}
Entry::Vacant(entry) => {
entry.insert(v.clone());
}
}
}
self.symbols.extend_ref(&other.symbols);
for (k, v) in &other.all_class_like_descendants {
self.all_class_like_descendants.entry(*k).or_default().extend(v.iter().copied());
}
for (k, v) in &other.direct_classlike_descendants {
self.direct_classlike_descendants.entry(*k).or_default().extend(v.iter().copied());
}
for (k, v) in &other.file_signatures {
self.file_signatures.insert(*k, v.clone());
}
self.safe_symbols.extend(other.safe_symbols.iter().copied());
self.safe_symbol_members.extend(other.safe_symbol_members.iter().copied());
self.infer_types_from_usage |= other.infer_types_from_usage;
self.merge_patch_class_likes(other.patch_class_likes.iter().map(|(k, v)| (*k, v.clone())));
self.merge_patch_function_likes(other.patch_function_likes.iter().map(|(k, v)| (*k, v.clone())));
self.merge_patch_constants(other.patch_constants.iter().map(|(k, v)| (*k, v.clone())));
}
fn merge_patch_class_likes(&mut self, incoming: impl IntoIterator<Item = (Word, ClassLikeMetadata)>) {
for (k, v) in incoming {
match self.patch_class_likes.entry(k) {
Entry::Occupied(mut entry) => {
let diagnostic = duplicate_patch_class_diagnostic(entry.get(), &v);
entry.get_mut().issues.push(diagnostic);
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
}
fn merge_patch_function_likes(&mut self, incoming: impl IntoIterator<Item = ((Word, Word), FunctionLikeMetadata)>) {
for (k, v) in incoming {
match self.patch_function_likes.entry(k) {
Entry::Occupied(mut entry) => {
if k.0.is_empty() {
let diagnostic = duplicate_patch_function_diagnostic(entry.get(), &v);
entry.get_mut().issues.push(diagnostic);
}
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
}
fn merge_patch_constants(&mut self, incoming: impl IntoIterator<Item = (Word, ConstantMetadata)>) {
for (k, v) in incoming {
match self.patch_constants.entry(k) {
Entry::Occupied(mut entry) => {
let diagnostic = duplicate_patch_constant_diagnostic(entry.get(), &v);
entry.get_mut().issues.push(diagnostic);
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
}
pub fn convert_partial_to_patch(&mut self) {
for (k, v) in std::mem::take(&mut self.class_likes) {
self.patch_class_likes.insert(k, v);
}
for (k, v) in std::mem::take(&mut self.function_likes) {
self.patch_function_likes.insert(k, v);
}
for (k, v) in std::mem::take(&mut self.constants) {
self.patch_constants.insert(k, v);
}
self.symbols = Symbols::new();
self.all_class_like_descendants.clear();
self.direct_classlike_descendants.clear();
}
pub fn apply_patches_pass(&mut self) {
let mut inherited_overrides: HashSet<(Word, Word)> = HashSet::default();
let class_keys: Vec<Word> = self.patch_class_likes.keys().copied().collect();
for fqcn in class_keys {
let Some(target) = self.class_likes.get(&fqcn) else {
if let Some(p) = self.patch_class_likes.get_mut(&fqcn) {
let diag = orphan_patch_class_diagnostic(p);
p.issues.push(diag);
}
continue;
};
if target.flags.is_user_defined() {
continue;
}
let mut working = target.clone();
let inherited =
collect_inherited_patch_methods(&working, &self.patch_class_likes[&fqcn], &self.class_likes);
inherited_overrides.extend(inherited.iter().map(|method| (fqcn, *method)));
if let Some(patch_entry) = self.patch_class_likes.get_mut(&fqcn) {
working.apply_patch(patch_entry, &inherited);
}
self.class_likes.insert(fqcn, working);
}
let func_keys: Vec<(Word, Word)> = self.patch_function_likes.keys().copied().collect();
for key in func_keys {
let Some(target) = self.function_likes.get(&key) else {
if inherited_overrides.contains(&key) {
if let Some(p) = self.patch_function_likes.get(&key) {
let materialized = p.clone();
self.function_likes.insert(key, materialized);
}
continue;
}
if key.0.is_empty()
&& let Some(p) = self.patch_function_likes.get_mut(&key)
{
let diag = orphan_patch_function_diagnostic(p);
p.issues.push(diag);
}
continue;
};
if target.flags.is_user_defined() {
continue;
}
let mut working = target.clone();
if let Some(patch_entry) = self.patch_function_likes.get_mut(&key) {
working.apply_patch(patch_entry);
}
self.function_likes.insert(key, working);
}
let const_keys: Vec<Word> = self.patch_constants.keys().copied().collect();
for fqcn in const_keys {
let Some(target) = self.constants.get(&fqcn) else {
if let Some(p) = self.patch_constants.get_mut(&fqcn) {
let diag = orphan_patch_constant_diagnostic(p);
p.issues.push(diag);
}
continue;
};
if target.flags.is_user_defined() {
continue;
}
let mut working = target.clone();
if let Some(patch_entry) = self.patch_constants.get(&fqcn) {
working.apply_patch(patch_entry);
}
self.constants.insert(fqcn, working);
}
}
pub fn remove_entries_of(&mut self, file_metadata: &CodebaseMetadata) {
for k in file_metadata.class_likes.keys() {
self.class_likes.remove(k);
}
for k in file_metadata.function_likes.keys() {
self.function_likes.remove(k);
}
for k in file_metadata.constants.keys() {
self.constants.remove(k);
}
for k in file_metadata.class_likes.keys() {
self.symbols.remove(*k);
}
for k in file_metadata.file_signatures.keys() {
self.file_signatures.remove(k);
}
let removed_files: HashSet<FileId> = file_metadata.file_signatures.keys().copied().collect();
self.patch_class_likes.retain(|_, m| !removed_files.contains(&m.span.file_id));
self.patch_function_likes.retain(|_, m| !removed_files.contains(&m.span.file_id));
self.patch_constants.retain(|_, m| !removed_files.contains(&m.span.file_id));
}
#[must_use]
pub fn extract_keys(&self) -> CodebaseEntryKeys {
CodebaseEntryKeys {
class_like_names: self.class_likes.keys().copied().collect(),
function_like_keys: self.function_likes.keys().copied().collect(),
constant_names: self.constants.keys().copied().collect(),
file_ids: self.file_signatures.keys().copied().collect(),
}
}
#[must_use]
pub fn extract_owned_keys(&self, merged: &CodebaseMetadata) -> CodebaseEntryKeys {
let class_like_names = self
.class_likes
.iter()
.filter(|(name, meta)| merged.class_likes.get(*name).is_some_and(|m| m.span == meta.span))
.map(|(name, _)| *name)
.collect();
let function_like_keys = self
.function_likes
.iter()
.filter(|(key, meta)| merged.function_likes.get(*key).is_some_and(|m| m.span == meta.span))
.map(|(key, _)| *key)
.collect();
let constant_names = self
.constants
.iter()
.filter(|(name, meta)| merged.constants.get(*name).is_some_and(|m| m.span == meta.span))
.map(|(name, _)| *name)
.collect();
let file_ids = self.file_signatures.keys().copied().collect();
CodebaseEntryKeys { class_like_names, function_like_keys, constant_names, file_ids }
}
pub fn remove_entries_by_keys(&mut self, keys: &CodebaseEntryKeys) {
for k in &keys.class_like_names {
self.class_likes.remove(k);
self.symbols.remove(*k);
}
for k in &keys.function_like_keys {
self.function_likes.remove(k);
}
for k in &keys.constant_names {
self.constants.remove(k);
}
for k in &keys.file_ids {
self.file_signatures.remove(k);
}
let removed_files: HashSet<FileId> = keys.file_ids.iter().copied().collect();
self.patch_class_likes.retain(|_, m| !removed_files.contains(&m.span.file_id));
self.patch_function_likes.retain(|_, m| !removed_files.contains(&m.span.file_id));
self.patch_constants.retain(|_, m| !removed_files.contains(&m.span.file_id));
}
pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
let mut issues = IssueCollection::new();
for meta in self.class_likes.values_mut() {
if user_defined && !meta.flags.is_user_defined() {
continue;
}
issues.extend(meta.take_issues());
}
for meta in self.function_likes.values_mut() {
if user_defined && !meta.flags.is_user_defined() {
continue;
}
issues.extend(meta.take_issues());
}
for meta in self.constants.values_mut() {
if user_defined && !meta.flags.is_user_defined() {
continue;
}
issues.extend(meta.take_issues());
}
for meta in self.patch_class_likes.values_mut() {
issues.extend(meta.take_issues());
}
for meta in self.patch_function_likes.values_mut() {
issues.extend(meta.take_issues());
}
for meta in self.patch_constants.values_mut() {
issues.extend(meta.take_issues());
}
issues
}
#[must_use]
pub fn get_all_file_ids(&self) -> Vec<FileId> {
self.file_signatures.keys().copied().collect()
}
}
impl Default for CodebaseMetadata {
#[inline]
fn default() -> Self {
Self {
class_likes: WordMap::default(),
function_likes: HashMap::default(),
symbols: Symbols::new(),
infer_types_from_usage: false,
constants: WordMap::default(),
all_class_like_descendants: WordMap::default(),
direct_classlike_descendants: WordMap::default(),
safe_symbols: WordSet::default(),
safe_symbol_members: HashSet::default(),
file_signatures: HashMap::default(),
patch_class_likes: WordMap::default(),
patch_function_likes: HashMap::default(),
patch_constants: WordMap::default(),
}
}
}
fn collect_inherited_patch_methods(
target: &ClassLikeMetadata,
patch: &ClassLikeMetadata,
class_likes: &WordMap<ClassLikeMetadata>,
) -> WordSet {
if patch.methods.is_empty() {
return WordSet::default();
}
let ancestor_methods = class_like::collect_ancestor_methods(target, class_likes);
patch.methods.iter().filter(|m| ancestor_methods.contains(*m)).copied().collect()
}
fn duplicate_patch_class_diagnostic(kept: &ClassLikeMetadata, dropped: &ClassLikeMetadata) -> Issue {
Issue::error(format!(
"Multiple patches target `{}`; at most one patch may target a given symbol.",
kept.original_name
))
.with_code(ScanningIssueKind::PatchDuplicateTarget)
.with_annotation(Annotation::primary(dropped.span).with_message("Duplicate patch for this symbol."))
.with_annotation(Annotation::secondary(kept.span).with_message("Already patched here."))
.with_help("Merge the conflicting declarations into a single patch, or remove all but one.")
}
fn duplicate_patch_function_diagnostic(kept: &FunctionLikeMetadata, dropped: &FunctionLikeMetadata) -> Issue {
Issue::error(format!(
"Multiple patches target function `{}`; at most one patch may target a given symbol.",
kept.name
))
.with_code(ScanningIssueKind::PatchDuplicateTarget)
.with_annotation(Annotation::primary(dropped.span).with_message("Duplicate patch for this function."))
.with_annotation(Annotation::secondary(kept.span).with_message("Already patched here."))
.with_help("Merge the conflicting declarations into a single patch, or remove all but one.")
}
fn duplicate_patch_constant_diagnostic(kept: &ConstantMetadata, dropped: &ConstantMetadata) -> Issue {
Issue::error(format!(
"Multiple patches target constant `{}`; at most one patch may target a given symbol.",
kept.name
))
.with_code(ScanningIssueKind::PatchDuplicateTarget)
.with_annotation(Annotation::primary(dropped.span).with_message("Duplicate patch for this constant."))
.with_annotation(Annotation::secondary(kept.span).with_message("Already patched here."))
.with_help("Merge the conflicting declarations into a single patch, or remove all but one.")
}
fn orphan_patch_class_diagnostic(meta: &ClassLikeMetadata) -> Issue {
Issue::error(format!(
"Patch declares `{}` but no vendored or built-in definition exists to patch.",
meta.original_name,
))
.with_code(ScanningIssueKind::PatchIntroducesNewSymbol)
.with_annotation(Annotation::primary(meta.span))
.with_help(
"The patch may be misnamed or out-of-date relative to the vendored or built-in definition; \
check the symbol name and verify the patch still matches the upstream source.",
)
}
fn orphan_patch_function_diagnostic(meta: &FunctionLikeMetadata) -> Issue {
Issue::error(format!(
"Patch declares function `{}` but no vendored or built-in definition exists to patch.",
meta.name,
))
.with_code(ScanningIssueKind::PatchIntroducesNewSymbol)
.with_annotation(Annotation::primary(meta.span))
.with_help(
"The patch may be misnamed or out-of-date relative to the vendored or built-in definition; \
check the function name and verify the patch still matches the upstream source.",
)
}
fn orphan_patch_constant_diagnostic(meta: &ConstantMetadata) -> Issue {
Issue::error(format!(
"Patch declares constant `{}` but no vendored or built-in definition exists to patch.",
meta.name,
))
.with_code(ScanningIssueKind::PatchIntroducesNewSymbol)
.with_annotation(Annotation::primary(meta.span))
.with_help(
"The patch may be misnamed or out-of-date relative to the vendored or built-in definition; \
check the constant name and verify the patch still matches the upstream source.",
)
}
fn should_replace_metadata(
existing_flags: MetadataFlags,
existing_span: Span,
new_flags: MetadataFlags,
new_span: Span,
) -> bool {
let new_is_user_defined = new_flags.is_user_defined();
let existing_is_user_defined = existing_flags.is_user_defined();
if new_is_user_defined != existing_is_user_defined {
return new_is_user_defined;
}
let new_is_patch = new_flags.is_patch();
let existing_is_patch = existing_flags.is_patch();
if new_is_patch != existing_is_patch {
return new_is_patch;
}
let new_is_built_in = new_flags.is_built_in();
let existing_is_built_in = existing_flags.is_built_in();
if new_is_built_in != existing_is_built_in {
return new_is_built_in;
}
let new_is_polyfill = new_flags.is_polyfill();
let existing_is_polyfill = existing_flags.is_polyfill();
if new_is_polyfill != existing_is_polyfill {
return !new_is_polyfill;
}
new_span < existing_span
}
#[cfg(test)]
mod should_replace_metadata_tests {
use super::*;
#[test]
fn non_polyfill_replaces_polyfill() {
let polyfill = MetadataFlags::POLYFILL;
let real = MetadataFlags::empty();
assert!(should_replace_metadata(polyfill, Span::dummy(0, 100), real, Span::dummy(0, 100)));
assert!(!should_replace_metadata(real, Span::dummy(0, 100), polyfill, Span::dummy(0, 100)));
}
#[test]
fn polyfill_does_not_replace_non_polyfill_even_with_smaller_span() {
let real = MetadataFlags::empty();
let polyfill = MetadataFlags::POLYFILL;
assert!(!should_replace_metadata(real, Span::dummy(500, 600), polyfill, Span::dummy(0, 10)));
}
#[test]
fn user_defined_beats_polyfill_flag() {
let polyfill_user = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
let plain = MetadataFlags::empty();
assert!(!should_replace_metadata(polyfill_user, Span::dummy(0, 10), plain, Span::dummy(0, 10)));
assert!(should_replace_metadata(plain, Span::dummy(0, 10), polyfill_user, Span::dummy(0, 10)));
}
#[test]
fn two_user_defined_fall_through_to_polyfill_check() {
let a = MetadataFlags::POLYFILL | MetadataFlags::USER_DEFINED;
let b = MetadataFlags::USER_DEFINED;
assert!(should_replace_metadata(a, Span::dummy(0, 10), b, Span::dummy(0, 10)));
assert!(!should_replace_metadata(b, Span::dummy(0, 10), a, Span::dummy(0, 10)));
}
#[test]
fn two_non_polyfills_fall_through_to_priority_rules() {
let user = MetadataFlags::USER_DEFINED;
let builtin = MetadataFlags::BUILTIN;
assert!(!should_replace_metadata(user, Span::dummy(0, 10), builtin, Span::dummy(0, 10)));
assert!(should_replace_metadata(builtin, Span::dummy(0, 10), user, Span::dummy(0, 10)));
}
#[test]
fn patch_beats_vendored() {
let vendored = MetadataFlags::empty();
let patch = MetadataFlags::PATCH;
assert!(should_replace_metadata(vendored, Span::dummy(0, 100), patch, Span::dummy(0, 100)));
assert!(!should_replace_metadata(patch, Span::dummy(0, 100), vendored, Span::dummy(0, 100)));
}
#[test]
fn patch_beats_builtin() {
let builtin = MetadataFlags::BUILTIN;
let patch = MetadataFlags::PATCH;
assert!(should_replace_metadata(builtin, Span::dummy(0, 100), patch, Span::dummy(0, 100)));
assert!(!should_replace_metadata(patch, Span::dummy(0, 100), builtin, Span::dummy(0, 100)));
}
#[test]
fn user_defined_beats_patch() {
let user = MetadataFlags::USER_DEFINED;
let patch = MetadataFlags::PATCH;
assert!(!should_replace_metadata(user, Span::dummy(0, 100), patch, Span::dummy(0, 100)));
assert!(should_replace_metadata(patch, Span::dummy(0, 100), user, Span::dummy(0, 100)));
}
#[test]
fn patch_does_not_beat_user_defined_even_with_smaller_span() {
let user = MetadataFlags::USER_DEFINED;
let patch = MetadataFlags::PATCH;
assert!(!should_replace_metadata(user, Span::dummy(500, 600), patch, Span::dummy(0, 10)));
}
#[test]
#[allow(clippy::expect_used)]
fn patch_function_like_leaves_vendor_owning_slot() {
use crate::metadata::function_like::FunctionLikeKind;
let name = word("foo");
let key = (empty_word(), name);
let vendor_span = Span::dummy(0, 100);
let patch_span = Span::dummy(500, 600);
let vendor =
FunctionLikeMetadata::new(FunctionLikeKind::Function, name, name, vendor_span, MetadataFlags::empty());
let mut codebase = CodebaseMetadata::new();
codebase.function_likes.insert(key, vendor);
let patch = FunctionLikeMetadata::new(FunctionLikeKind::Function, name, name, patch_span, MetadataFlags::PATCH);
codebase.patch_function_likes.insert(key, patch);
codebase.apply_patches_pass();
let merged = codebase.function_likes.get(&key).expect("function-like must remain after patch");
assert_eq!(merged.span, vendor_span, "patch must not move the slot's span");
assert!(!merged.flags.is_patch(), "patch must not flip the slot's flags");
}
#[test]
fn patch_does_not_apply_to_user_defined_class() {
let class_name = word("MyClass");
let method_existing = word("doIt");
let mut user_class =
ClassLikeMetadata::new(class_name, class_name, Span::dummy(0, 100), None, MetadataFlags::USER_DEFINED);
user_class.methods.insert(method_existing);
let mut codebase = CodebaseMetadata::new();
codebase.class_likes.insert(class_name, user_class);
let mut patch_class =
ClassLikeMetadata::new(class_name, class_name, Span::dummy(0, 50), None, MetadataFlags::PATCH);
let method_new = word("patchedMethod");
patch_class.methods.insert(method_new);
codebase.patch_class_likes.insert(class_name, patch_class);
codebase.apply_patches_pass();
let class = &codebase.class_likes[&class_name];
assert!(!class.methods.contains(&method_new));
assert!(class.methods.contains(&method_existing));
assert!(class.flags.is_user_defined());
assert!(class.issues.is_empty());
}
}