use std::borrow::Cow;
use std::collections::hash_map::Entry;
use foldhash::HashMap;
use foldhash::HashSet;
use serde::Deserialize;
use serde::Serialize;
use mago_atom::Atom;
use mago_atom::AtomMap;
use mago_atom::AtomSet;
use mago_atom::ascii_lowercase_atom;
use mago_atom::ascii_lowercase_constant_name_atom;
use mago_atom::atom;
use mago_atom::empty_atom;
use mago_atom::u32_atom;
use mago_atom::u64_atom;
use mago_database::file::FileId;
use mago_reporting::IssueCollection;
use mago_span::Position;
use mago_span::Span;
use crate::diff::CodebaseDiff;
use crate::identifier::method::MethodIdentifier;
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;
#[derive(Debug, Clone)]
pub struct CodebaseEntryKeys {
pub class_like_names: Vec<Atom>,
pub function_like_keys: Vec<(Atom, Atom)>,
pub constant_names: Vec<Atom>,
pub file_ids: Vec<FileId>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[non_exhaustive]
pub struct CodebaseMetadata {
pub infer_types_from_usage: bool,
pub class_likes: AtomMap<ClassLikeMetadata>,
pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
pub symbols: Symbols,
pub constants: AtomMap<ConstantMetadata>,
pub all_class_like_descendants: AtomMap<AtomSet>,
pub direct_classlike_descendants: AtomMap<AtomSet>,
pub safe_symbols: AtomSet,
pub safe_symbol_members: HashSet<(Atom, Atom)>,
pub file_signatures: HashMap<FileId, FileSignature>,
}
impl CodebaseMetadata {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn class_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class))
}
#[inline]
#[must_use]
pub fn interface_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Interface))
}
#[inline]
#[must_use]
pub fn trait_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Trait))
}
#[inline]
#[must_use]
pub fn enum_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Enum))
}
#[inline]
#[must_use]
pub fn class_like_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
self.symbols.contains(lowercase_name)
}
#[inline]
#[must_use]
pub fn namespace_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
self.symbols.contains_namespace(lowercase_name)
}
#[inline]
#[must_use]
pub fn class_or_trait_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
matches!(self.symbols.get_kind(lowercase_name), Some(SymbolKind::Class | SymbolKind::Trait))
}
#[inline]
#[must_use]
pub fn class_or_interface_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(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_atom(&method_id.get_class_name());
let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
let identifier = (lowercase_class, lowercase_method);
self.function_likes.contains_key(&identifier)
}
#[inline]
#[must_use]
pub fn function_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(name);
let identifier = (empty_atom(), lowercase_name);
self.function_likes.contains_key(&identifier)
}
#[inline]
#[must_use]
pub fn constant_exists(&self, name: &str) -> bool {
let lowercase_name = ascii_lowercase_constant_name_atom(name);
self.constants.contains_key(&lowercase_name)
}
#[inline]
#[must_use]
pub fn method_exists(&self, class: &str, method: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, property: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(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: &str, constant: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let constant_name = atom(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: &str, method: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, property: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(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: &str) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_atom(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: &str) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_atom(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: &str) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_atom(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: &str) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_atom(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: &str) -> Option<&ClassLikeMetadata> {
let lowercase_name = ascii_lowercase_atom(name);
self.class_likes.get(&lowercase_name)
}
#[inline]
#[must_use]
pub fn get_function(&self, name: &str) -> Option<&FunctionLikeMetadata> {
let lowercase_name = ascii_lowercase_atom(name);
let identifier = (empty_atom(), lowercase_name);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(method);
let identifier = (lowercase_class, lowercase_method);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_closure(&self, file_id: &FileId, position: &Position) -> Option<&FunctionLikeMetadata> {
let file_ref = u64_atom(file_id.as_u64());
let closure_ref = u32_atom(position.offset);
let identifier = (file_ref, closure_ref);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_method_by_id(&self, method_id: &MethodIdentifier) -> Option<&FunctionLikeMetadata> {
let lowercase_class = ascii_lowercase_atom(&method_id.get_class_name());
let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
let identifier = (lowercase_class, lowercase_method);
self.function_likes.get(&identifier)
}
#[inline]
#[must_use]
pub fn get_declaring_method(&self, class: &str, method: &str) -> Option<&FunctionLikeMetadata> {
let method_id = MethodIdentifier::new(atom(class), atom(method));
let declaring_method_id = self.get_declaring_method_identifier(&method_id);
self.get_method(&declaring_method_id.get_class_name(), &declaring_method_id.get_method_name())
}
#[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),
FunctionLikeIdentifier::Method(class, method) => self.get_method(class, method),
FunctionLikeIdentifier::Closure(file_id, position) => self.get_closure(file_id, position),
}
}
#[inline]
#[must_use]
pub fn get_constant(&self, name: &str) -> Option<&ConstantMetadata> {
let lowercase_name = ascii_lowercase_constant_name_atom(name);
self.constants.get(&lowercase_name)
}
#[inline]
#[must_use]
pub fn get_class_constant(&self, class: &str, constant: &str) -> Option<&ClassLikeConstantMetadata> {
let lowercase_class = ascii_lowercase_atom(class);
let constant_name = atom(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: &str, case: &str) -> Option<&EnumCaseMetadata> {
let lowercase_class = ascii_lowercase_atom(class);
let case_name = atom(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: &str, property: &str) -> Option<&PropertyMetadata> {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(property);
self.class_likes.get(&lowercase_class)?.properties.get(&property_name)
}
#[inline]
#[must_use]
pub fn get_declaring_property(&self, class: &str, property: &str) -> Option<&PropertyMetadata> {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(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: &str, property: &str) -> Option<&TUnion> {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(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<'a>(&'a self, class: &str, constant: &str) -> Option<Cow<'a, TUnion>> {
let lowercase_class = ascii_lowercase_atom(class);
let constant_name = atom(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: &str, constant: &str) -> Option<&TAtomic> {
let lowercase_class = ascii_lowercase_atom(class);
let constant_name = atom(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: &str, parent: &str) -> bool {
let lowercase_child = ascii_lowercase_atom(child);
let lowercase_parent = ascii_lowercase_atom(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: &str, parent: &str) -> bool {
let lowercase_child = ascii_lowercase_atom(child);
let lowercase_parent = ascii_lowercase_atom(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: &str, interface: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_interface = ascii_lowercase_atom(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: &str, interface: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_interface = ascii_lowercase_atom(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: &str, trait_name: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_trait = ascii_lowercase_atom(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: &str, class_name: &str) -> bool {
let lowercase_trait = ascii_lowercase_atom(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)))
}
#[inline]
#[must_use]
pub fn is_instance_of(&self, child: &str, parent: &str) -> bool {
if child == parent {
return true;
}
let lowercase_child = ascii_lowercase_atom(child);
let lowercase_parent = ascii_lowercase_atom(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: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(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: &str) -> bool {
let lowercase_name = ascii_lowercase_atom(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: &str) -> AtomSet {
let lowercase_class = ascii_lowercase_atom(class);
let mut all_descendants = AtomSet::default();
let mut queue = vec![&lowercase_class];
let mut visited = AtomSet::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: &str) -> AtomSet {
let lowercase_class = ascii_lowercase_atom(class);
let mut ancestors = AtomSet::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: &str, method: &str) -> Option<Atom> {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, method: &str) -> Option<Atom> {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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_atom(&method_id.get_class_name());
let lowercase_method = ascii_lowercase_atom(&method_id.get_method_name());
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: &str, method: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, method: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, method: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, method: &str) -> bool {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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: &str, method: &str) -> Option<Visibility> {
let lowercase_class = ascii_lowercase_atom(class);
let lowercase_method = ascii_lowercase_atom(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<'a>(
&'a self,
class_like: Option<&'a ClassLikeMetadata>,
function_like: &'a FunctionLikeMetadata,
) -> &'a [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 Some(method_name) = function_like.name.as_ref() else {
return &[];
};
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.eq_ignore_ascii_case(parent_class_name) {
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: &str, property: &str) -> Option<Atom> {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(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: &str, property: &str) -> Option<Atom> {
let lowercase_class = ascii_lowercase_atom(class);
let property_name = atom(property);
self.class_likes.get(&lowercase_class)?.appearing_property_ids.get(&property_name).copied()
}
#[must_use]
pub fn get_all_descendants(&self, class: &str) -> AtomSet {
let lowercase_class = ascii_lowercase_atom(class);
let mut all_descendants = AtomSet::default();
let mut queue = vec![&lowercase_class];
let mut visited = AtomSet::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(span: mago_span::Span) -> Atom {
use std::io::Write;
let mut buffer = [0u8; 64];
let mut writer = &mut buffer[..];
unsafe {
write!(writer, "class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset)
.unwrap_unchecked();
};
let written_len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
atom(unsafe { std::str::from_utf8(&buffer[..written_len]).unwrap_unchecked() })
}
#[must_use]
pub fn get_anonymous_class(&self, span: mago_span::Span) -> Option<&ClassLikeMetadata> {
let name = Self::get_anonymous_class_name(span);
if self.class_exists(&name) { self.class_likes.get(&name) } else { None }
}
#[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) -> bool {
let Some((invalid_symbols, partially_invalid)) = references.get_invalid_symbols(diff) else {
return false;
};
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);
}
}
}
true
}
pub fn extend(&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) {
entry.insert(v);
}
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
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) {
entry.insert(v);
}
}
Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
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) {
entry.insert(v);
}
}
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;
}
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) {
entry.insert(v.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) {
entry.insert(v.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) {
entry.insert(v.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().cloned());
self.infer_types_from_usage |= other.infer_types_from_usage;
}
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);
}
}
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(),
}
}
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);
}
}
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());
}
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: AtomMap::default(),
function_likes: HashMap::default(),
symbols: Symbols::new(),
infer_types_from_usage: false,
constants: AtomMap::default(),
all_class_like_descendants: AtomMap::default(),
direct_classlike_descendants: AtomMap::default(),
safe_symbols: AtomSet::default(),
safe_symbol_members: HashSet::default(),
file_signatures: HashMap::default(),
}
}
}
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_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;
}
new_span < existing_span
}