use std::cmp::Ordering;
use foldhash::HashMap;
use foldhash::HashSet;
use mago_word::Word;
use crate::metadata::CodebaseMetadata;
use crate::misc::GenericParent;
use crate::ttype::add_union_type;
use crate::ttype::atomic::TAtomic;
use crate::ttype::atomic::generic::TGenericParameter;
use crate::ttype::combiner::CombinerOptions;
use crate::ttype::get_mixed;
use crate::ttype::template::TemplateBound;
use crate::ttype::union::TUnion;
#[must_use]
#[allow(clippy::implicit_hasher)]
pub fn get_root_template_type(
lower_bounds: &HashMap<Word, HashMap<GenericParent, Vec<TemplateBound>>>,
parameter_name: Word,
defining_entity: &GenericParent,
mut visited_entities: HashSet<GenericParent>,
codebase: &CodebaseMetadata,
) -> Option<TUnion> {
if visited_entities.contains(defining_entity) {
return None;
}
if let Some(mapped) = lower_bounds.get(¶meter_name)
&& let Some(bounds) = mapped.get(defining_entity)
{
let mapped_type = get_most_specific_type_from_bounds(bounds, codebase);
if !mapped_type.is_single() {
return Some(mapped_type);
}
let first_template = &mapped_type.get_single();
if let TAtomic::GenericParameter(TGenericParameter { parameter_name, defining_entity, .. }) = first_template {
visited_entities.insert(*defining_entity);
return Some(
get_root_template_type(lower_bounds, *parameter_name, defining_entity, visited_entities, codebase)
.unwrap_or(mapped_type),
);
}
return Some(mapped_type);
}
None
}
#[must_use]
pub fn get_most_specific_type_from_bounds(lower_bounds: &[TemplateBound], codebase: &CodebaseMetadata) -> TUnion {
let relevant_bounds = get_relevant_bounds(lower_bounds);
if relevant_bounds.is_empty() {
return get_mixed();
}
if relevant_bounds.len() == 1 {
return relevant_bounds[0].bound_type.clone();
}
let mut specific_type = relevant_bounds[0].bound_type.clone();
for bound in relevant_bounds {
specific_type = add_union_type(specific_type, &bound.bound_type, codebase, CombinerOptions::default());
}
specific_type
}
#[must_use]
pub fn get_relevant_bounds(lower_bounds: &[TemplateBound]) -> Vec<&TemplateBound> {
let mut lower_bounds = lower_bounds.iter().collect::<Vec<_>>();
if lower_bounds.len() == 1 {
return lower_bounds;
}
lower_bounds.sort_by(|a, b| a.appearance_depth.partial_cmp(&b.appearance_depth).unwrap_or(Ordering::Equal));
let mut current_depth = None;
let mut had_invariant = false;
let mut last_argument_offset = None;
let mut applicable_bounds = vec![];
for template_bound in lower_bounds {
if let Some(inner) = current_depth {
if inner != template_bound.appearance_depth && !applicable_bounds.is_empty() {
if !had_invariant || last_argument_offset == template_bound.argument_offset {
break;
}
current_depth = Some(template_bound.appearance_depth);
}
} else {
current_depth = Some(template_bound.appearance_depth);
}
had_invariant = if had_invariant { true } else { template_bound.equality_bound_classlike.is_some() };
applicable_bounds.push(template_bound);
last_argument_offset = template_bound.argument_offset;
}
applicable_bounds
}