use rustc_hash::FxHashMap;
use mir_types::{Atomic, Name, Type, Variance};
use crate::db::{
class_template_params, extends_or_implements, inherited_template_bindings, MirDatabase,
};
fn sup_param_is_free(ty: &Type) -> bool {
ty.is_mixed()
|| ty
.types
.iter()
.all(|a| matches!(a, Atomic::TTemplateParam { .. }))
}
fn variance_compatible(
db: &dyn MirDatabase,
fqcn: &str,
sub_params: &[Type],
sup_params: &[Type],
) -> bool {
if sub_params.len() != sup_params.len() {
return false;
}
let Some(tps) = class_template_params(db, fqcn) else {
return false;
};
tps.iter()
.zip(sub_params)
.zip(sup_params)
.all(|((tp, sub_p), sup_p)| match tp.variance {
Variance::Covariant => is_subtype(db, sub_p, sup_p),
Variance::Contravariant => is_subtype(db, sup_p, sub_p),
Variance::Invariant => sub_p == sup_p,
})
}
fn variance_compatible_across_hierarchy(
db: &dyn MirDatabase,
sub_fqcn: &str,
sub_params: &[Type],
sup_fqcn: &str,
sup_params: &[Type],
) -> bool {
if sub_fqcn == sup_fqcn {
return false;
}
let Some(sub_tps) = class_template_params(db, sub_fqcn) else {
return false;
};
if !sub_tps.is_empty() && sub_tps.len() != sub_params.len() {
return false;
}
let own_bindings: FxHashMap<Name, Type> = sub_tps
.iter()
.zip(sub_params)
.map(|(tp, ty)| (tp.name, ty.clone()))
.collect();
let ancestor_bindings = inherited_template_bindings(db, sub_fqcn, &own_bindings);
let Some(sup_tps) = class_template_params(db, sup_fqcn) else {
return false;
};
let resolved_sup_params: Vec<Type> = sup_tps
.iter()
.map(|tp| {
ancestor_bindings
.get(&tp.name)
.cloned()
.unwrap_or_else(Type::mixed)
})
.collect();
variance_compatible(db, sup_fqcn, &resolved_sup_params, sup_params)
}
pub(crate) fn is_subtype(db: &dyn MirDatabase, sub: &Type, sup: &Type) -> bool {
if sub.is_subtype_structural(sup) {
return true;
}
if sup.is_mixed() {
return true;
}
if sub.is_never() {
return true;
}
sub.types.iter().all(|a| {
if let Atomic::TNamedObject { fqcn: sub_fqcn, .. } = a {
if crate::db::class_kind(db, sub_fqcn.as_ref()).is_some_and(|k| k.is_trait) {
return true;
}
}
sup.types.iter().any(|b| {
if mir_types::union::atomic_subtype(a, b) {
return true;
}
match (a, b) {
(
Atomic::TNamedObject {
fqcn: sub_fqcn,
type_params: sub_params,
},
Atomic::TNamedObject {
fqcn: sup_fqcn,
type_params: sup_params,
},
) => {
let params_ok = sup_params.is_empty()
|| sub_params == sup_params
|| sup_params.iter().all(sup_param_is_free)
|| (!sub_params.is_empty()
&& sub_params.iter().all(|p| p.is_mixed() || p.is_never()))
|| (sub_fqcn == sup_fqcn
&& variance_compatible(db, sub_fqcn.as_ref(), sub_params, sup_params))
|| variance_compatible_across_hierarchy(
db,
sub_fqcn.as_ref(),
sub_params,
sup_fqcn.as_ref(),
sup_params,
);
params_ok && extends_or_implements(db, sub_fqcn.as_ref(), sup_fqcn.as_ref())
}
(Atomic::TNamedObject { fqcn: sub_fqcn, .. }, Atomic::TIntersection { parts }) => {
parts.iter().all(|part| {
part.types.iter().any(|part_atomic| match part_atomic {
Atomic::TNamedObject {
fqcn: part_fqcn, ..
} => extends_or_implements(db, sub_fqcn.as_ref(), part_fqcn.as_ref()),
_ => false,
})
})
}
(
Atomic::TIntersection { parts: sub_parts },
Atomic::TIntersection { parts: sup_parts },
) => sup_parts.iter().all(|sup_part| {
sub_parts
.iter()
.any(|sub_part| is_subtype(db, sub_part, sup_part))
}),
(Atomic::TIntersection { parts }, b) => {
let sup_single = Type::single(b.clone());
parts.iter().any(|part| is_subtype(db, part, &sup_single))
}
(
Atomic::TKeyedArray {
properties,
is_list,
..
},
Atomic::TList { value: lv },
) => *is_list && properties.values().all(|p| is_subtype(db, &p.ty, lv)),
(
Atomic::TInt
| Atomic::TLiteralInt(_)
| Atomic::TPositiveInt
| Atomic::TNegativeInt
| Atomic::TNonNegativeInt
| Atomic::TIntRange { .. },
Atomic::TFloat,
) => true,
(Atomic::TIntegralFloat, Atomic::TFloat) => true,
(Atomic::TClassString(Some(sub_cls)), Atomic::TClassString(Some(sup_cls))) => {
sub_cls == sup_cls
|| extends_or_implements(db, sub_cls.as_ref(), sup_cls.as_ref())
}
(Atomic::TClassString(Some(sub_cls)), Atomic::TInterfaceString(None)) => {
is_interface(db, sub_cls.as_ref())
}
(
Atomic::TClassString(Some(sub_cls)),
Atomic::TInterfaceString(Some(sup_iface)),
) => {
is_interface(db, sub_cls.as_ref())
&& (sub_cls == sup_iface
|| extends_or_implements(db, sub_cls.as_ref(), sup_iface.as_ref()))
}
(Atomic::TClassString(None), Atomic::TInterfaceString(_)) => true,
(
Atomic::TInterfaceString(Some(sub_iface)),
Atomic::TInterfaceString(Some(sup_iface)),
) => {
sub_iface == sup_iface
|| extends_or_implements(db, sub_iface.as_ref(), sup_iface.as_ref())
}
(
Atomic::TInterfaceString(Some(sub_iface)),
Atomic::TClassString(Some(sup_cls)),
) => {
sub_iface == sup_cls
|| extends_or_implements(db, sub_iface.as_ref(), sup_cls.as_ref())
}
_ => false,
}
})
})
}
fn is_interface(db: &dyn MirDatabase, fqcn: &str) -> bool {
crate::db::class_kind(db, fqcn).is_some_and(|k| k.is_interface)
}