use foldhash::HashSet;
use mago_atom::Atom;
use mago_atom::AtomMap;
use mago_atom::AtomSet;
use crate::metadata::CodebaseMetadata;
use crate::metadata::constant::ConstantMetadata;
use crate::metadata::flags::MetadataFlags;
use crate::reference::ReferenceSource;
use crate::reference::SymbolReferences;
use crate::symbol::SymbolIdentifier;
use crate::symbol::Symbols;
use crate::ttype::union::populate_union_type;
mod docblock;
mod hierarchy;
mod merge;
mod methods;
mod properties;
mod signatures;
mod sorter;
mod templates;
pub fn populate_codebase(
codebase: &mut CodebaseMetadata,
symbol_references: &mut SymbolReferences,
safe_symbols: AtomSet,
safe_symbol_members: HashSet<SymbolIdentifier>,
) {
populate_codebase_inner(codebase, symbol_references, safe_symbols, safe_symbol_members, None)
}
pub fn populate_codebase_targeted(
codebase: &mut CodebaseMetadata,
symbol_references: &mut SymbolReferences,
safe_symbols: AtomSet,
safe_symbol_members: HashSet<SymbolIdentifier>,
dirty_symbols: HashSet<SymbolIdentifier>,
) {
populate_codebase_inner(codebase, symbol_references, safe_symbols, safe_symbol_members, Some(dirty_symbols))
}
fn populate_codebase_inner(
codebase: &mut CodebaseMetadata,
symbol_references: &mut SymbolReferences,
safe_symbols: AtomSet,
safe_symbol_members: HashSet<SymbolIdentifier>,
dirty_symbols: Option<HashSet<SymbolIdentifier>>,
) {
let mut class_likes_to_repopulate = AtomSet::default();
if let Some(dirty) = &dirty_symbols {
let mut dirty_class_names = AtomSet::default();
for (name, _) in dirty {
dirty_class_names.insert(*name);
}
for class_name in &dirty_class_names {
if let Some(metadata) = codebase.class_likes.get(class_name)
&& (!metadata.flags.is_populated()
|| (metadata.flags.is_user_defined() && !safe_symbols.contains(class_name)))
{
class_likes_to_repopulate.insert(*class_name);
}
}
for (class_name, metadata) in &codebase.class_likes {
if metadata.flags.is_user_defined()
&& !safe_symbols.contains(class_name)
&& !class_likes_to_repopulate.contains(class_name)
{
class_likes_to_repopulate.insert(*class_name);
}
}
} else {
for (name, metadata) in &codebase.class_likes {
if !metadata.flags.is_populated() || (metadata.flags.is_user_defined() && !safe_symbols.contains(name)) {
class_likes_to_repopulate.insert(*name);
}
}
}
for class_like_name in &class_likes_to_repopulate {
if let Some(classlike_info) = codebase.class_likes.get_mut(class_like_name) {
classlike_info.flags &= !MetadataFlags::POPULATED;
classlike_info.declaring_property_ids.clear();
classlike_info.appearing_property_ids.clear();
classlike_info.declaring_method_ids.clear();
classlike_info.appearing_method_ids.clear();
classlike_info.overridden_method_ids.clear();
classlike_info.overridden_property_ids.clear();
classlike_info.invalid_dependencies.clear();
}
}
let sorted_classes = sorter::sort_class_likes(codebase, &class_likes_to_repopulate);
for class_name in sorted_classes {
hierarchy::populate_class_like_metadata_iterative(class_name, codebase, symbol_references);
}
let incremental = !safe_symbols.is_empty() || !safe_symbol_members.is_empty();
if let Some(dirty) = &dirty_symbols {
for dirty_key in dirty {
if let Some(function_like_metadata) = codebase.function_likes.get_mut(dirty_key) {
let force_repopulation = function_like_metadata.flags.is_user_defined();
if function_like_metadata.flags.is_populated() && !force_repopulation {
continue;
}
let reference_source = if dirty_key.1.is_empty() || function_like_metadata.get_kind().is_closure() {
ReferenceSource::Symbol(true, dirty_key.0)
} else {
ReferenceSource::ClassLikeMember(true, dirty_key.0, dirty_key.1)
};
signatures::populate_function_like_metadata(
function_like_metadata,
&codebase.symbols,
&reference_source,
symbol_references,
force_repopulation,
);
}
}
for (name, function_like_metadata) in &mut codebase.function_likes {
if dirty.contains(name) {
continue;
}
let is_closure_or_arrow =
function_like_metadata.get_kind().is_closure() || function_like_metadata.get_kind().is_arrow_function();
let is_safe = if is_closure_or_arrow {
true
} else if name.1.is_empty() {
safe_symbols.contains(&name.0)
} else {
safe_symbol_members.contains(name) || safe_symbols.contains(&name.0)
};
let force_repopulation = function_like_metadata.flags.is_user_defined() && !is_safe;
if function_like_metadata.flags.is_populated() && !force_repopulation {
continue;
}
let reference_source = if name.1.is_empty() || function_like_metadata.get_kind().is_closure() {
ReferenceSource::Symbol(true, name.0)
} else {
ReferenceSource::ClassLikeMember(true, name.0, name.1)
};
signatures::populate_function_like_metadata(
function_like_metadata,
&codebase.symbols,
&reference_source,
symbol_references,
force_repopulation,
);
}
} else {
for (name, function_like_metadata) in &mut codebase.function_likes {
let is_closure_or_arrow =
function_like_metadata.get_kind().is_closure() || function_like_metadata.get_kind().is_arrow_function();
let is_safe = if is_closure_or_arrow {
true
} else if name.1.is_empty() {
safe_symbols.contains(&name.0)
} else {
safe_symbol_members.contains(name) || safe_symbols.contains(&name.0)
};
let force_repopulation = function_like_metadata.flags.is_user_defined() && !is_safe;
if incremental && function_like_metadata.flags.is_populated() && !force_repopulation {
continue;
}
let reference_source = if name.1.is_empty() || function_like_metadata.get_kind().is_closure() {
ReferenceSource::Symbol(true, name.0)
} else {
ReferenceSource::ClassLikeMember(true, name.0, name.1)
};
signatures::populate_function_like_metadata(
function_like_metadata,
&codebase.symbols,
&reference_source,
symbol_references,
force_repopulation,
);
}
}
if let Some(_dirty) = &dirty_symbols {
for class_name in &class_likes_to_repopulate {
if let Some(metadata) = codebase.class_likes.get_mut(class_name) {
hierarchy::populate_class_like_types(
*class_name,
metadata,
&codebase.symbols,
symbol_references,
true, );
}
}
} else {
for (name, metadata) in &mut codebase.class_likes {
let force_repopulation = metadata.flags.is_user_defined() && !safe_symbols.contains(name);
if incremental && metadata.flags.is_populated() && !force_repopulation {
continue;
}
hierarchy::populate_class_like_types(
*name,
metadata,
&codebase.symbols,
symbol_references,
force_repopulation,
);
}
}
if let Some(dirty) = &dirty_symbols {
let mut dirty_const_names: AtomSet = AtomSet::default();
for (name, member) in dirty {
if member.is_empty() {
dirty_const_names.insert(*name);
}
}
for const_name in dirty_const_names {
if let Some(constant) = codebase.constants.get_mut(&const_name) {
let force_repopulation = constant.flags.is_user_defined();
if constant.flags.is_populated() && !force_repopulation {
continue;
}
populate_constant(const_name, constant, &codebase.symbols, symbol_references, force_repopulation);
}
}
} else {
for (name, constant) in &mut codebase.constants {
let force_repopulation = constant.flags.is_user_defined() && !safe_symbols.contains(name);
if incremental && constant.flags.is_populated() && !force_repopulation {
continue;
}
populate_constant(*name, constant, &codebase.symbols, symbol_references, force_repopulation);
}
}
if !incremental || !class_likes_to_repopulate.is_empty() {
let mut direct_classlike_descendants = AtomMap::default();
let mut all_classlike_descendants = AtomMap::default();
for (class_like_name, class_like_metadata) in &codebase.class_likes {
for parent_interface in &class_like_metadata.all_parent_interfaces {
all_classlike_descendants
.entry(*parent_interface)
.or_insert_with(AtomSet::default)
.insert(*class_like_name);
}
for parent_interface in &class_like_metadata.direct_parent_interfaces {
direct_classlike_descendants
.entry(*parent_interface)
.or_insert_with(AtomSet::default)
.insert(*class_like_name);
}
for parent_class in &class_like_metadata.all_parent_classes {
all_classlike_descendants
.entry(*parent_class)
.or_insert_with(AtomSet::default)
.insert(*class_like_name);
}
for used_trait in &class_like_metadata.used_traits {
all_classlike_descendants.entry(*used_trait).or_default().insert(*class_like_name);
}
if let Some(parent_class) = &class_like_metadata.direct_parent_class {
direct_classlike_descendants
.entry(*parent_class)
.or_insert_with(AtomSet::default)
.insert(*class_like_name);
}
}
for (parent_name, children) in &direct_classlike_descendants {
if let Some(parent_metadata) = codebase.class_likes.get_mut(parent_name) {
parent_metadata.child_class_likes = Some(children.clone());
}
}
codebase.all_class_like_descendants = all_classlike_descendants;
codebase.direct_classlike_descendants = direct_classlike_descendants;
}
if !incremental || !class_likes_to_repopulate.is_empty() {
let dirty_classes = if dirty_symbols.is_some() { Some(&class_likes_to_repopulate) } else { None };
docblock::inherit_method_docblocks(codebase, &safe_symbols, dirty_classes);
}
codebase.safe_symbols = safe_symbols;
codebase.safe_symbol_members = safe_symbol_members;
}
fn populate_constant(
name: Atom,
constant: &mut ConstantMetadata,
symbols: &Symbols,
symbol_references: &mut SymbolReferences,
force_repopulation: bool,
) {
for attribute_metadata in &constant.attributes {
symbol_references.add_symbol_reference_to_symbol(name, attribute_metadata.name, true);
}
if let Some(type_metadata) = &mut constant.type_metadata {
populate_union_type(
&mut type_metadata.type_union,
symbols,
Some(&ReferenceSource::Symbol(true, name)),
symbol_references,
force_repopulation,
);
}
if let Some(inferred_type) = &mut constant.inferred_type {
populate_union_type(
inferred_type,
symbols,
Some(&ReferenceSource::Symbol(true, name)),
symbol_references,
force_repopulation,
);
}
constant.flags |= MetadataFlags::POPULATED;
}