use rustc_hash::FxHashMap as HashMap;
use syntax::EcoString;
use syntax::ast::{Annotation, Generic, Span};
use syntax::program::DefinitionBody;
use syntax::types::{Symbol, Type, substitute, unqualified_name};
use super::TaskState;
use crate::checker::infer::expressions::comparison::bounds_conflict;
use crate::store::Store;
type ImplBound = (usize, Span, Type);
type BoundsByPosition = (Vec<ImplBound>, Vec<Vec<Type>>);
impl TaskState<'_> {
pub(super) fn check_conflicting_impl_bounds(
&mut self,
store: &Store,
receiver_annotation: &Annotation,
receiver_qualified: &Symbol,
impl_generics: &[Generic],
) -> bool {
let Some((impl_bounds, type_bounds)) = self.resolve_impl_bounds_by_position(
store,
receiver_annotation,
receiver_qualified,
impl_generics,
) else {
return false;
};
let type_label = unqualified_name(receiver_qualified.as_str());
let mut found = false;
for (position, span, impl_bound) in &impl_bounds {
let Some(type_bounds_here) = type_bounds.get(*position) else {
continue;
};
if bounds_conflict(store, type_bounds_here, impl_bound) {
found = true;
let interface = impl_bound
.get_qualified_id()
.map(unqualified_name)
.unwrap_or("");
self.sink
.push(diagnostics::infer::impl_bound_conflicts_with_type(
interface, type_label, *span,
));
}
}
found
}
pub(super) fn check_conflicting_cross_impl_bounds(
&mut self,
store: &Store,
receiver_annotation: &Annotation,
receiver_qualified: &Symbol,
impl_generics: &[Generic],
) {
let Some((impl_bounds, _type_bounds)) = self.resolve_impl_bounds_by_position(
store,
receiver_annotation,
receiver_qualified,
impl_generics,
) else {
return;
};
let type_label = unqualified_name(receiver_qualified.as_str());
for (position, span, impl_bound) in &impl_bounds {
let key: (EcoString, usize) = (receiver_qualified.as_str().into(), *position);
let recorded = self.impl_param_interface_bounds.get(&key);
if recorded.is_some_and(|seen| seen.iter().any(|(_, prev)| prev == span)) {
continue;
}
let earlier = recorded.and_then(|seen| {
seen.iter()
.find(|(ty, _)| bounds_conflict(store, std::slice::from_ref(ty), impl_bound))
.map(|(_, prev)| *prev)
});
if let Some(earlier) = earlier {
let interface = impl_bound
.get_qualified_id()
.map(unqualified_name)
.unwrap_or("");
self.sink.push(
diagnostics::infer::conflicting_impl_interface_instantiations(
interface, type_label, *span, earlier,
),
);
}
self.impl_param_interface_bounds
.entry(key)
.or_default()
.push((impl_bound.clone(), *span));
}
}
fn resolve_impl_bounds_by_position(
&mut self,
store: &Store,
receiver_annotation: &Annotation,
receiver_qualified: &Symbol,
impl_generics: &[Generic],
) -> Option<BoundsByPosition> {
if impl_generics.is_empty() {
return None;
}
let type_generics = match store
.get_definition(receiver_qualified.as_str())
.map(|d| &d.body)
{
Some(
DefinitionBody::Struct { generics, .. } | DefinitionBody::Enum { generics, .. },
) => generics.clone(),
_ => return None,
};
if type_generics.is_empty() {
return None;
}
let Annotation::Constructor {
params: receiver_args,
..
} = receiver_annotation
else {
return None;
};
let position_of = |name: &EcoString| {
receiver_args.iter().position(|arg| {
matches!(arg, Annotation::Constructor { name: n, params, .. }
if params.is_empty() && n == name)
})
};
let alpha: HashMap<EcoString, Type> = impl_generics
.iter()
.filter_map(|ig| {
let position = position_of(&ig.name)?;
let type_param = type_generics.get(position)?.name.clone();
Some((ig.name.clone(), Type::Parameter(type_param)))
})
.collect();
self.scopes.push();
self.put_in_scope(&type_generics);
self.put_in_scope(impl_generics);
let before = self.sink.len();
let type_bounds: Vec<Vec<Type>> = type_generics
.iter()
.map(|generic| {
generic
.bounds
.iter()
.filter_map(|bound| {
self.resolve_type_bound(store, bound, &generic.span, receiver_qualified)
})
.collect()
})
.collect();
let mut impl_bounds: Vec<ImplBound> = Vec::new();
for impl_generic in impl_generics {
let Some(position) = position_of(&impl_generic.name) else {
continue;
};
for bound in &impl_generic.bounds {
if let Some(resolved) =
self.resolve_type_bound(store, bound, &impl_generic.span, receiver_qualified)
{
impl_bounds.push((position, bound.get_span(), substitute(&resolved, &alpha)));
}
}
}
self.sink.truncate(before);
self.scopes.pop();
Some((impl_bounds, type_bounds))
}
pub(super) fn resolve_type_bound(
&mut self,
store: &Store,
bound: &Annotation,
span: &Span,
type_qualified: &Symbol,
) -> Option<Type> {
let bound_ty = self.register_bound_annotation(store, bound, span);
let id = match store.deep_resolve_alias(&bound_ty).get_qualified_id() {
Some(id) => id.to_string(),
None => self.resolve_type_bound_id(store, bound, span, type_qualified)?,
};
let type_module = store.module_for_qualified_name(type_qualified.as_str())?;
let params = match bound {
Annotation::Constructor { params, .. } => params
.iter()
.map(|param| self.resolve_bound_arg(store, param, span, type_module))
.collect(),
_ => Vec::new(),
};
Some(Type::Nominal {
id: id.into(),
params,
underlying_ty: None,
})
}
fn resolve_bound_arg(
&mut self,
store: &Store,
annotation: &Annotation,
span: &Span,
type_module: &str,
) -> Type {
let ty = self.convert_to_type(store, annotation, span);
if !matches!(ty, Type::Error) {
return ty;
}
let Annotation::Constructor { name, params, .. } = annotation else {
return ty;
};
let Some(id) = resolve_definition_id(store, type_module, name) else {
return ty;
};
let params = params
.iter()
.map(|param| self.resolve_bound_arg(store, param, span, type_module))
.collect();
Type::Nominal {
id: id.into(),
params,
underlying_ty: None,
}
}
fn resolve_type_bound_id(
&mut self,
store: &Store,
bound: &Annotation,
_span: &Span,
type_qualified: &Symbol,
) -> Option<String> {
let Annotation::Constructor { name, .. } = bound else {
return None;
};
let type_module = store.module_for_qualified_name(type_qualified.as_str())?;
let (bound_module, bound_name) = match name.split_once('.') {
Some((alias, type_name)) => (
import_module_for_alias(store, type_module, alias)?,
type_name,
),
None => (type_module.to_string(), name.as_str()),
};
let candidate = Symbol::from_parts(&bound_module, bound_name);
matches!(
store.get_definition(candidate.as_str()).map(|d| &d.body),
Some(DefinitionBody::Interface { .. })
)
.then(|| candidate.to_string())
}
}
fn resolve_definition_id(store: &Store, type_module: &str, name: &str) -> Option<String> {
let (module, bare) = match name.split_once('.') {
Some((alias, type_name)) => (
import_module_for_alias(store, type_module, alias)?,
type_name,
),
None => (type_module.to_string(), name),
};
let candidate = Symbol::from_parts(&module, bare);
store
.get_definition(candidate.as_str())
.map(|_| candidate.to_string())
}
fn import_module_for_alias(store: &Store, module: &str, alias: &str) -> Option<String> {
let module = store.get_module(module)?;
module.files.values().find_map(|file| {
file.imports().into_iter().find_map(|import| {
(import.effective_alias(&store.go_package_names).as_deref() == Some(alias))
.then(|| import.name.to_string())
})
})
}