use crate::checker::EnvResolve;
use crate::checker::TypeEnv;
use crate::checker::infer::InferCtx;
use crate::store::Store;
use syntax::ast::{Expression, Span};
use syntax::program::DefinitionBody;
use syntax::types::{CompoundKind, Type, build_substitution_map, substitute};
pub(crate) fn check_not_comparable(
env: &TypeEnv,
store: &Store,
ty: &Type,
) -> Option<&'static str> {
let resolved = store.deep_resolve_alias(ty);
let ty = &resolved;
if matches!(ty, Type::Function(_)) {
return Some("functions");
}
if ty.has_name("Slice") {
return Some("slices");
}
if ty.has_name("Map") {
return Some("maps");
}
if ty.has_name("Ref") || ty.has_name("Channel") {
return None;
}
if matches!(ty, Type::Var { .. }) {
return None;
}
if ty.is_unknown() {
return Some("interface values");
}
if let Some(underlying) = ty.get_underlying() {
return check_not_comparable(env, store, underlying);
}
if matches!(ty, Type::Parameter(_)) {
return Some("type parameters");
}
if let Some(name) = ty.get_qualified_id()
&& let Some(definition) = store.get_definition(name)
{
let type_args = ty.get_type_params().unwrap_or_default();
let generics = match &definition.body {
DefinitionBody::Struct { generics, .. } | DefinitionBody::Enum { generics, .. } => {
generics.as_slice()
}
_ => &[],
};
let sub_map = generics
.iter()
.map(|g| g.name.clone())
.zip(type_args.iter().cloned())
.collect();
match &definition.body {
DefinitionBody::Struct { fields, .. } => {
for f in fields {
let field_ty = substitute(&f.ty.resolve_in(env), &sub_map);
if check_not_comparable(env, store, &field_ty).is_some() {
return Some("a struct containing non-comparable fields");
}
}
}
DefinitionBody::Enum { variants, .. } => {
for v in variants {
for f in v.fields.iter() {
let field_ty = substitute(&f.ty.resolve_in(env), &sub_map);
if check_not_comparable(env, store, &field_ty).is_some() {
return Some("an enum containing non-comparable fields");
}
}
}
}
DefinitionBody::Interface { .. } => return Some("interface values"),
_ => {}
}
}
if let Type::Tuple(elems) = ty {
for e in elems {
if check_not_comparable(env, store, &e.resolve_in(env)).is_some() {
return Some("a tuple containing non-comparable elements");
}
}
}
None
}
fn is_interface_or_unknown(store: &Store, ty: &Type) -> bool {
let resolved = store.deep_resolve_alias(ty);
resolved.is_unknown() || store.is_interface(&resolved)
}
pub(crate) fn type_has_usable_equals(store: &Store, ty: &Type, current_module: &str) -> bool {
let resolved = store.deep_resolve_alias(ty);
let Some(qualified) = resolved.get_qualified_id() else {
return false;
};
store.usable_equals.usable_from(qualified, current_module)
}
fn type_has_callable_bound_mismatched_equals(
store: &Store,
ty: &Type,
current_module: &str,
) -> bool {
let Some(id) = ty.get_qualified_id() else {
return false;
};
if !store.equals_bound_mismatch.contains(id) {
return false;
}
let method_key = format!("{id}.equals");
store.get_definition(&method_key).is_some_and(|method| {
method.visibility.is_public() || store.module_for_qualified_name(id) == Some(current_module)
})
}
pub(crate) fn bound_implied(store: &Store, type_bounds: &[Type], method_bound: &Type) -> bool {
use super::super::unify::BuiltinBound;
let builtin = |ty: &Type| {
ty.get_qualified_id()
.and_then(BuiltinBound::from_qualified_id)
};
if let Some(method) = builtin(method_bound)
&& type_bounds
.iter()
.any(|tb| builtin(tb).is_some_and(|tb| tb.satisfies(method)))
{
return true;
}
type_bounds
.iter()
.any(|tb| bound_satisfies(store, tb, method_bound))
}
fn interface_closure_any(
store: &Store,
start: &Type,
mut predicate: impl FnMut(&Type) -> bool,
) -> bool {
let mut stack = vec![store.deep_resolve_alias(start)];
let mut seen: Vec<Type> = Vec::new();
while let Some(current) = stack.pop() {
if predicate(¤t) {
return true;
}
if seen.contains(¤t) {
continue;
}
seen.push(current.clone());
let Some(id) = current.get_qualified_id() else {
continue;
};
let Some(interface) = store.get_interface(id) else {
continue;
};
let map = build_substitution_map(
&interface.generics,
current.get_type_params().unwrap_or_default(),
);
for parent in &interface.parents {
stack.push(store.deep_resolve_alias(&substitute(parent, &map)));
}
}
false
}
fn bound_satisfies(store: &Store, start: &Type, target: &Type) -> bool {
let target = store.deep_resolve_alias(target);
interface_closure_any(store, start, |current| current == &target)
}
pub(crate) fn bounds_conflict(store: &Store, type_bounds: &[Type], impl_bound: &Type) -> bool {
let impl_bound = store.deep_resolve_alias(impl_bound);
let Some(impl_base) = impl_bound.get_qualified_id().map(str::to_string) else {
return false;
};
type_bounds
.iter()
.any(|tb| closure_conflicts(store, tb, &impl_base, &impl_bound))
}
fn closure_conflicts(store: &Store, start: &Type, target_base: &str, target: &Type) -> bool {
interface_closure_any(store, start, |current| {
current.get_qualified_id() == Some(target_base) && current != target
})
}
impl InferCtx<'_, '_> {
pub(super) fn ensure_comparable(
&mut self,
ty: &Type,
span: &Span,
operands_match: bool,
) -> bool {
let store = self.store;
let resolved = ty.resolve_in(&self.env);
if resolved.is_error() {
return true;
}
if let Type::Parameter(name) = &resolved
&& self.parameter_satisfies_bound(name, super::super::unify::BuiltinBound::Comparable)
{
return true;
}
let Some(reason) = check_not_comparable(&self.env, store, &resolved) else {
return true;
};
if is_interface_or_unknown(store, &resolved) {
self.sink.push(diagnostics::infer::not_comparable_interface(
&resolved, *span,
));
} else if operands_match && let Some(element) = self.container_equals_element(&resolved) {
match self.not_equatable_reason(&element) {
Some(element_reason) => self.sink.push(
diagnostics::infer::not_comparable_no_equals(&resolved, element_reason, *span),
),
None => self
.sink
.push(diagnostics::infer::not_comparable_use_equals(
&resolved, reason, *span,
)),
}
} else {
self.sink
.push(diagnostics::infer::not_comparable(&resolved, reason, *span));
}
false
}
pub(super) fn not_equatable_reason(&self, ty: &Type) -> Option<&'static str> {
let resolved = self.store.deep_resolve_alias(&ty.resolve_in(&self.env));
let current_module = self.cursor.module_id.as_str();
if let Type::Parameter(name) = &resolved
&& self.parameter_satisfies_bound(name, super::super::unify::BuiltinBound::Comparable)
{
return None;
}
if type_has_usable_equals(self.store, &resolved, current_module) {
return None;
}
let Some(reason) = check_not_comparable(&self.env, self.store, &resolved) else {
return type_has_callable_bound_mismatched_equals(
self.store,
&resolved,
current_module,
)
.then_some("a type whose `equals` requires stricter bounds");
};
match resolved.as_compound() {
Some((CompoundKind::Slice, args)) => self.not_equatable_reason(args.first()?),
Some((CompoundKind::Map, args)) => {
if self.map_key_not_comparable(args.first()?) {
return Some("a map with a non-comparable key");
}
self.not_equatable_reason(args.get(1)?)
}
_ => Some(reason),
}
}
fn map_key_not_comparable(&self, key: &Type) -> bool {
let resolved = self.store.deep_resolve_alias(&key.resolve_in(&self.env));
if let Type::Parameter(name) = &resolved
&& self.parameter_satisfies_bound(name, super::super::unify::BuiltinBound::Comparable)
{
return false;
}
check_not_comparable(&self.env, self.store, &resolved).is_some()
}
pub(super) fn gate_container_equals(&mut self, receiver_ty: &Type, span: Span) {
let receiver = self.store.deep_resolve_alias(receiver_ty);
if !receiver.is_slice() && !receiver.is_map() {
return;
}
if let Some(reason) = self.not_equatable_reason(&receiver) {
self.sink
.push(diagnostics::infer::not_equatable(&receiver, reason, span));
}
}
pub(in crate::checker::infer) fn is_native_container(&self, ty: &Type) -> bool {
let resolved = self.store.deep_resolve_alias(&ty.resolve_in(&self.env));
resolved.is_slice() || resolved.is_map()
}
fn container_equals_element(&self, ty: &Type) -> Option<Type> {
let resolved = self.store.deep_resolve_alias(&ty.resolve_in(&self.env));
match resolved.as_compound() {
Some((CompoundKind::Slice, args)) => args.first().cloned(),
Some((CompoundKind::Map, args)) => args.get(1).cloned(),
_ => None,
}
}
pub(super) fn check_native_equals_ufcs(&mut self, callee: &Expression, args: &[Expression]) {
let Expression::Identifier { value, .. } = callee.unwrap_parens() else {
return;
};
if value.as_str() != "Slice.equals" && value.as_str() != "Map.equals" {
return;
}
let Some(receiver) = args.first() else {
return;
};
let receiver_ty = receiver.get_type().resolve_in(&self.env).strip_refs();
self.gate_container_equals(&receiver_ty, receiver.get_span());
}
}