use foldhash::HashMap;
use foldhash::fast::RandomState;
use indexmap::IndexMap;
use mago_atom::Atom;
use mago_atom::AtomMap;
use mago_codex::metadata::class_like::ClassLikeMetadata;
use mago_codex::metadata::class_like::TemplateTypes;
use mago_codex::misc::GenericParent;
use mago_codex::ttype::add_optional_union_type;
use mago_codex::ttype::atomic::TAtomic;
use mago_codex::ttype::atomic::generic::TGenericParameter;
use mago_codex::ttype::expander;
use mago_codex::ttype::expander::StaticClassType;
use mago_codex::ttype::expander::TypeExpansionOptions;
use mago_codex::ttype::get_mixed;
use mago_codex::ttype::template::GenericTemplate;
use mago_codex::ttype::union::TUnion;
use mago_codex::ttype::wrap_atomic;
use crate::context::Context;
pub type TemplateLowerBounds = HashMap<Atom, HashMap<GenericParent, TUnion>>;
pub fn get_template_types_for_class_member(
context: &Context<'_, '_>,
declaring_class_meta: Option<&ClassLikeMetadata>,
appearing_class_name: Option<Atom>,
calling_class_meta: Option<&ClassLikeMetadata>,
existing_template_types: &TemplateTypes,
class_template_parameters: &IndexMap<Atom, Vec<GenericTemplate>, RandomState>,
) -> TemplateLowerBounds {
let codebase = context.codebase;
let mut template_types: IndexMap<Atom, Vec<GenericTemplate>, RandomState> =
existing_template_types.iter().map(|(name, template)| (*name, vec![template.clone()])).collect();
if let Some(declaring_class_meta) = declaring_class_meta {
let declaring_class_name = declaring_class_meta.name;
if let Some(calling_meta) = calling_class_meta
&& calling_meta.name != declaring_class_name
&& !calling_meta.template_extended_parameters.is_empty()
{
let calling_template_extended = &calling_meta.template_extended_parameters;
for (extended_class_name, type_map) in calling_template_extended {
if extended_class_name == &declaring_class_name {
for (template_name, provided_type_arc) in type_map {
let resolved_type = if provided_type_arc.has_template_types() {
let mut resolved_union = None;
for atomic_type in provided_type_arc.types.as_ref() {
let resolved_atomic_type_union = if let TAtomic::GenericParameter(TGenericParameter {
defining_entity: GenericParent::ClassLike(defining_entity),
parameter_name,
..
}) = atomic_type
{
let mut combined_parameters = class_template_parameters.clone();
combined_parameters.extend(template_types.clone());
get_generic_parameter_for_offset(
*defining_entity,
*parameter_name,
calling_template_extended,
&combined_parameters.into_iter().collect::<AtomMap<_>>(),
)
} else {
wrap_atomic(atomic_type.clone())
};
resolved_union = Some(add_optional_union_type(
resolved_atomic_type_union,
resolved_union.as_ref(),
codebase,
));
}
resolved_union.unwrap_or_else(get_mixed)
} else {
provided_type_arc.clone()
};
template_types
.entry(*template_name)
.or_default()
.push(GenericTemplate::new(GenericParent::ClassLike(declaring_class_name), resolved_type));
}
}
}
} else if !declaring_class_meta.template_types.is_empty() {
for (template_name, template) in &declaring_class_meta.template_types {
let concrete_type = class_template_parameters.get(template_name).and_then(|parameters| {
parameters
.iter()
.find(|t| t.defining_entity == template.defining_entity)
.map(|t| t.constraint.clone())
});
let resolved_type = concrete_type.unwrap_or_else(|| template.constraint.clone());
template_types
.entry(*template_name)
.or_default()
.push(GenericTemplate::new(template.defining_entity, resolved_type));
}
}
}
let mut expanded_template_types: TemplateLowerBounds = HashMap::default();
for (template_name, type_map_vec) in template_types {
let final_map_entry: &mut HashMap<GenericParent, TUnion> =
expanded_template_types.entry(template_name).or_default();
for GenericTemplate { defining_entity: template_source, constraint: mut template_type, .. } in type_map_vec {
expander::expand_union(
codebase,
&mut template_type,
&TypeExpansionOptions {
self_class: appearing_class_name,
static_class_type: if let Some(calling_meta) = calling_class_meta {
StaticClassType::Name(calling_meta.name)
} else {
StaticClassType::None
},
parent_class: declaring_class_meta.and_then(|m| m.direct_parent_class),
function_is_final: calling_class_meta.is_some_and(|m| m.flags.is_final()),
expand_templates: true,
..Default::default()
},
);
final_map_entry.insert(template_source, template_type);
}
}
expanded_template_types
}
pub fn get_generic_parameter_for_offset(
class_like_name: Atom,
template_name: Atom,
template_extended_parameters: &AtomMap<IndexMap<Atom, TUnion, RandomState>>,
found_generic_parameters: &AtomMap<Vec<GenericTemplate>>,
) -> TUnion {
if let Some(result_map) = found_generic_parameters.get(&template_name)
&& let Some(found_parameter_type) = result_map
.iter()
.find(|t| t.defining_entity == GenericParent::ClassLike(class_like_name))
.map(|t| &t.constraint)
{
return found_parameter_type.clone();
}
for (extending_class_name, type_map) in template_extended_parameters {
for (extended_template_name, extended_type_union) in type_map {
for extended_atomic_type in extended_type_union.types.as_ref() {
if let TAtomic::GenericParameter(TGenericParameter {
parameter_name: current_parameter_name,
defining_entity: GenericParent::ClassLike(current_defining_class),
..
}) = extended_atomic_type
&& *current_parameter_name == template_name
&& *current_defining_class == class_like_name
{
return get_generic_parameter_for_offset(
*extending_class_name,
*extended_template_name,
template_extended_parameters,
found_generic_parameters,
);
}
}
}
}
get_mixed()
}