use std::borrow::Cow;
use foldhash::fast::RandomState;
use indexmap::IndexMap;
use mago_atom::Atom;
use mago_codex::metadata::class_like::ClassLikeMetadata;
use mago_codex::metadata::function_like::FunctionLikeMetadata;
use mago_codex::misc::GenericParent;
use mago_codex::ttype::TType;
use mago_codex::ttype::atomic::object::TObject;
use mago_codex::ttype::comparator::ComparisonResult;
use mago_codex::ttype::comparator::union_comparator;
use mago_codex::ttype::expander::StaticClassType;
use mago_codex::ttype::get_specialized_template_type;
use mago_codex::ttype::template::GenericTemplate;
use mago_codex::ttype::template::TemplateBound;
use mago_codex::ttype::template::TemplateResult;
use mago_codex::ttype::template::bounds::get_most_specific_type_from_bounds;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::Span;
use crate::code::IssueCode;
use crate::context::Context;
use crate::invocation::Invocation;
use crate::invocation::InvocationTarget;
use crate::invocation::MethodTargetContext;
use crate::invocation::template_inference::infer_templates_for_method_call;
use crate::utils::misc::unique_vec;
use crate::utils::template::get_template_types_for_class_member;
pub fn populate_template_result_from_invocation<'ctx, 'arena>(
context: &mut Context<'ctx, 'arena>,
invocation: &Invocation<'ctx, '_, 'arena>,
template_result: &mut TemplateResult,
) {
let InvocationTarget::FunctionLike { metadata, method_context, .. } = &invocation.target else {
return;
};
for (template_name, template_details) in &metadata.template_types {
template_result.template_types.entry(*template_name).or_default().push(template_details.clone());
}
let Some(method_metadata) = &metadata.method_metadata else {
return;
};
let Some(method_context) = method_context else {
return;
};
if method_metadata.is_static {
let Some(identifier) = method_context.declaring_method_id else {
return;
};
let Some(declaring_class_metadata) = context.codebase.get_class_like(&identifier.get_class_name()) else {
return;
};
for (template_name, template_details) in &declaring_class_metadata.template_types {
if !template_result.template_types.contains_key(template_name) {
template_result.template_types.entry(*template_name).or_default().push(template_details.clone());
}
}
if declaring_class_metadata.name != method_context.class_like_metadata.name {
for (template_name, _) in &declaring_class_metadata.template_types {
let template_type = get_specialized_template_type(
context.codebase,
*template_name,
declaring_class_metadata.name,
method_context.class_like_metadata,
None,
);
if let Some(template_type) = template_type {
template_result.add_lower_bound(
*template_name,
GenericParent::ClassLike(declaring_class_metadata.name),
template_type,
);
}
}
}
if let StaticClassType::Object(TObject::Named(instance_type)) = &method_context.class_type
&& !instance_type.name.eq_ignore_ascii_case(&declaring_class_metadata.original_name)
&& let Some(calling_class_metadata) = context.codebase.get_class_like(&instance_type.name)
{
for (template_name, _) in &declaring_class_metadata.template_types {
if template_result.lower_bounds.get(template_name).is_some_and(|m| !m.is_empty()) {
continue;
}
let template_type = get_specialized_template_type(
context.codebase,
*template_name,
declaring_class_metadata.name,
calling_class_metadata,
instance_type.type_parameters.as_deref(),
);
if let Some(template_type) = template_type {
template_result.add_lower_bound(
*template_name,
GenericParent::ClassLike(declaring_class_metadata.name),
template_type,
);
}
}
}
return;
}
for (template_name, template_details) in &method_context.class_like_metadata.template_types {
if !template_result.template_types.contains_key(template_name) {
template_result.template_types.entry(*template_name).or_default().push(template_details.clone());
}
}
let StaticClassType::Object(TObject::Named(instance_type)) = &method_context.class_type else {
return;
};
if let Some(type_parameters) = &instance_type.type_parameters {
for (template_index, template_type) in type_parameters.iter().enumerate() {
let Some(template_name) = method_context
.class_like_metadata
.template_types
.iter()
.enumerate()
.find_map(|(index, (name, _))| if index == template_index { Some(*name) } else { None })
else {
break;
};
template_result.add_lower_bound(
template_name,
GenericParent::ClassLike(method_context.class_like_metadata.name),
template_type.clone(),
);
}
}
if !instance_type.name.eq_ignore_ascii_case(&method_context.class_like_metadata.original_name)
&& let Some(calling_class_metadata) = context.codebase.get_class_like(&instance_type.name)
{
for (template_name, _) in &method_context.class_like_metadata.template_types {
if template_result.lower_bounds.get(template_name).is_some_and(|m| !m.is_empty()) {
continue;
}
let template_type = get_specialized_template_type(
context.codebase,
*template_name,
method_context.class_like_metadata.name,
calling_class_metadata,
instance_type.type_parameters.as_deref(),
);
if let Some(template_type) = template_type {
template_result.add_lower_bound(
*template_name,
GenericParent::ClassLike(method_context.class_like_metadata.name),
template_type,
);
}
}
}
let Some(identifier) = method_context.declaring_method_id else {
return;
};
let Some(metadata) = context.codebase.get_class_like(&identifier.get_class_name()) else {
return;
};
infer_templates_for_method_call(context, instance_type, method_context, method_metadata, metadata, template_result);
}
pub(super) fn get_class_template_parameters_from_result(
template_result: &TemplateResult,
context: &Context<'_, '_>,
) -> IndexMap<Atom, Vec<GenericTemplate>, RandomState> {
let mut class_generic_parameters: IndexMap<Atom, Vec<GenericTemplate>, RandomState> =
IndexMap::with_hasher(RandomState::default());
for (template_name, type_map) in &template_result.lower_bounds {
for (generic_parent, lower_bounds) in type_map {
if matches!(generic_parent, GenericParent::ClassLike(_)) && !lower_bounds.is_empty() {
let specific_bound_type = get_most_specific_type_from_bounds(lower_bounds, context.codebase);
class_generic_parameters
.entry(*template_name)
.or_default()
.push(GenericTemplate::new(*generic_parent, specific_bound_type));
}
}
}
class_generic_parameters
}
pub(super) fn refine_template_result_for_function_like<'ctx>(
template_result: &mut TemplateResult,
context: &Context<'ctx, '_>,
method_target_context: Option<&MethodTargetContext<'ctx>>,
base_class_metadata: Option<&'ctx ClassLikeMetadata>,
calling_class_like_metadata: Option<&'ctx ClassLikeMetadata>,
function_like_metadata: &'ctx FunctionLikeMetadata,
class_template_parameters: &IndexMap<Atom, Vec<GenericTemplate>, RandomState>,
) {
if !template_result.template_types.is_empty() {
return;
}
let resolved_template_types = get_template_types_for_class_member(
context,
base_class_metadata,
method_target_context.as_ref().map(|mci| mci.class_like_metadata.name),
calling_class_like_metadata,
&function_like_metadata.template_types,
class_template_parameters,
);
if resolved_template_types.is_empty() {
return;
}
template_result.template_types = resolved_template_types
.into_iter()
.map(|(template_name, type_map)| {
(
template_name,
type_map
.into_iter()
.map(|(source, template_type)| GenericTemplate::new(source, template_type))
.collect(),
)
})
.collect::<IndexMap<_, _, RandomState>>();
}
pub(super) fn check_template_result(context: &mut Context<'_, '_>, template_result: &mut TemplateResult, span: Span) {
if template_result.lower_bounds.is_empty() {
return;
}
let codebase = context.codebase;
for (template_name, defining_map) in &template_result.upper_bounds {
for (defining_entity, upper_bound) in defining_map {
let lower_bounds = template_result
.lower_bounds
.entry(*template_name)
.or_default()
.entry(*defining_entity)
.or_insert_with(|| vec![TemplateBound::of_type(upper_bound.bound_type.clone())]);
let (lower_bound_type, upper_bound_type) = if template_result.upper_bounds_unintersectable_types.len() > 1 {
(
Cow::Borrowed(&template_result.upper_bounds_unintersectable_types[0]),
Cow::Borrowed(&template_result.upper_bounds_unintersectable_types[1]),
)
} else {
(
Cow::Owned(get_most_specific_type_from_bounds(lower_bounds, codebase)),
Cow::Borrowed(&upper_bound.bound_type),
)
};
let mut comparison_result = ComparisonResult::new();
let is_contained = union_comparator::is_contained_by(
codebase,
&lower_bound_type,
&upper_bound_type,
false,
false,
false,
&mut comparison_result,
);
if !is_contained {
let issue_kind = if comparison_result.type_coerced.unwrap_or(false)
&& comparison_result.type_coerced_from_as_mixed.unwrap_or(false)
{
IssueCode::MixedArgument
} else {
IssueCode::InvalidArgument
};
context.collector.report_with_code(
issue_kind,
Issue::error(format!("Incompatible template bounds for `{template_name}`."))
.with_annotation(Annotation::primary(span).with_message(format!(
"Inferred type `{}` is not compatible with declared bound `{}`",
lower_bound_type.get_id(),
upper_bound_type.get_id(),
)))
.with_note(format!("Could not reconcile bounds for template parameter `{template_name}`."))
.with_help(
"Check the types used for arguments or properties related to this template parameter.",
),
);
}
}
}
for (template_name, lower_bounds_map) in &template_result.lower_bounds {
for lower_bounds in lower_bounds_map.values() {
if lower_bounds.len() <= 1 {
continue;
}
let bounds_with_equality: Vec<_> =
lower_bounds.iter().filter(|bound| bound.equality_bound_classlike.is_some()).collect();
if !bounds_with_equality.is_empty() {
let equality_types: Vec<_> =
unique_vec(bounds_with_equality.iter().map(|bound| bound.bound_type.get_id().as_str()));
if equality_types.len() > 1 {
context.collector.report_with_code(
IssueCode::ConflictingTemplateEqualityBounds,
Issue::error(format!(
"Conflicting equality requirements found for template `{template_name}`.",
))
.with_annotation(Annotation::primary(span).with_message(format!(
"Template `{template_name}` cannot be equal to all of: `{}`.",
equality_types.join("`, `"),
)))
.with_help(
"Check the argument types provided for this template parameter; they must resolve to a single compatible type."
),
);
continue;
}
}
if let Some(first_equality_bound) = bounds_with_equality.first() {
for lower_bound in lower_bounds {
if lower_bound.equality_bound_classlike.is_some() {
continue;
}
let is_contained = union_comparator::is_contained_by(
codebase,
&lower_bound.bound_type,
&first_equality_bound.bound_type,
false,
false,
false,
&mut ComparisonResult::new(),
);
if !is_contained {
context.collector.report_with_code(
IssueCode::IncompatibleTemplateLowerBound,
Issue::error(format!(
"Incompatible bounds found for template `{template_name}`.",
))
.with_annotation(Annotation::primary(span).with_message(format!(
"Type `{}` required by a lower bound is not compatible with the required equality type `{}`.",
lower_bound.bound_type.get_id(),
first_equality_bound.bound_type.get_id(),
)))
.with_help(
"Check the argument types provided; they must satisfy all lower and equality bounds simultaneously."
),
);
}
}
}
}
}
}