blob: 8bd555f5c08f33ad10beb8f2076bfb81cf4abf9b [file] [log] [blame]
//! Compute the dyn-compatibility of a trait
use std::ops::ControlFlow;
use chalk_ir::{
DebruijnIndex,
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
};
use chalk_solve::rust_ir::InlineBound;
use hir_def::{
AssocItemId, ConstId, CrateRootModuleId, FunctionId, GenericDefId, HasModule, TraitId,
TypeAliasId, lang_item::LangItem, signatures::TraitFlags,
};
use intern::Symbol;
use rustc_hash::FxHashSet;
use rustc_type_ir::{
AliasTyKind, ClauseKind, PredicatePolarity, TypeSuperVisitable as _, TypeVisitable as _,
Upcast,
inherent::{IntoKind, SliceLike},
};
use smallvec::SmallVec;
use crate::{
AliasEq, AliasTy, Binders, BoundVar, ImplTraitId, Interner, ProjectionTyExt, Ty, TyKind,
WhereClause, all_super_traits,
db::{HirDatabase, InternedOpaqueTyId},
from_assoc_type_id, from_chalk_trait_id,
generics::trait_self_param_idx,
next_solver::{
Clauses, DbInterner, GenericArgs, ParamEnv, SolverDefId, TraitPredicate, TypingMode,
infer::DbInternerInferExt, mk_param,
},
traits::next_trait_solve_in_ctxt,
utils::elaborate_clause_supertraits,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DynCompatibilityViolation {
SizedSelf,
SelfReferential,
Method(FunctionId, MethodViolationCode),
AssocConst(ConstId),
GAT(TypeAliasId),
// This doesn't exist in rustc, but added for better visualization
HasNonCompatibleSuperTrait(TraitId),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MethodViolationCode {
StaticMethod,
ReferencesSelfInput,
ReferencesSelfOutput,
ReferencesImplTraitInTrait,
AsyncFn,
WhereClauseReferencesSelf,
Generic,
UndispatchableReceiver,
}
pub fn dyn_compatibility(
db: &dyn HirDatabase,
trait_: TraitId,
) -> Option<DynCompatibilityViolation> {
for super_trait in all_super_traits(db, trait_).into_iter().skip(1).rev() {
if db.dyn_compatibility_of_trait(super_trait).is_some() {
return Some(DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait));
}
}
db.dyn_compatibility_of_trait(trait_)
}
pub fn dyn_compatibility_with_callback<F>(
db: &dyn HirDatabase,
trait_: TraitId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
{
for super_trait in all_super_traits(db, trait_).into_iter().skip(1).rev() {
if db.dyn_compatibility_of_trait(super_trait).is_some() {
cb(DynCompatibilityViolation::HasNonCompatibleSuperTrait(trait_))?;
}
}
dyn_compatibility_of_trait_with_callback(db, trait_, cb)
}
pub fn dyn_compatibility_of_trait_with_callback<F>(
db: &dyn HirDatabase,
trait_: TraitId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
{
// Check whether this has a `Sized` bound
if generics_require_sized_self(db, trait_.into()) {
cb(DynCompatibilityViolation::SizedSelf)?;
}
// Check if there exist bounds that referencing self
if predicates_reference_self(db, trait_) {
cb(DynCompatibilityViolation::SelfReferential)?;
}
if bounds_reference_self(db, trait_) {
cb(DynCompatibilityViolation::SelfReferential)?;
}
// rustc checks for non-lifetime binders here, but we don't support HRTB yet
let trait_data = trait_.trait_items(db);
for (_, assoc_item) in &trait_data.items {
dyn_compatibility_violation_for_assoc_item(db, trait_, *assoc_item, cb)?;
}
ControlFlow::Continue(())
}
pub fn dyn_compatibility_of_trait_query(
db: &dyn HirDatabase,
trait_: TraitId,
) -> Option<DynCompatibilityViolation> {
let mut res = None;
_ = dyn_compatibility_of_trait_with_callback(db, trait_, &mut |osv| {
res = Some(osv);
ControlFlow::Break(())
});
res
}
pub fn generics_require_sized_self(db: &dyn HirDatabase, def: GenericDefId) -> bool {
let krate = def.module(db).krate();
let Some(sized) = LangItem::Sized.resolve_trait(db, krate) else {
return false;
};
let Some(trait_self_param_idx) = trait_self_param_idx(db, def) else {
return false;
};
let predicates = &*db.generic_predicates(def);
let predicates = predicates.iter().map(|p| p.skip_binders().skip_binders().clone());
elaborate_clause_supertraits(db, predicates).any(|pred| match pred {
WhereClause::Implemented(trait_ref) => {
if from_chalk_trait_id(trait_ref.trait_id) == sized
&& let TyKind::BoundVar(it) =
*trait_ref.self_type_parameter(Interner).kind(Interner)
{
// Since `generic_predicates` is `Binder<Binder<..>>`, the `DebrujinIndex` of
// self-parameter is `1`
return it
.index_if_bound_at(DebruijnIndex::ONE)
.is_some_and(|idx| idx == trait_self_param_idx);
}
false
}
_ => false,
})
}
// rustc gathers all the spans that references `Self` for error rendering,
// but we don't have good way to render such locations.
// So, just return single boolean value for existence of such `Self` reference
fn predicates_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool {
db.generic_predicates(trait_.into())
.iter()
.any(|pred| predicate_references_self(db, trait_, pred, AllowSelfProjection::No))
}
// Same as the above, `predicates_reference_self`
fn bounds_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool {
let trait_data = trait_.trait_items(db);
trait_data
.items
.iter()
.filter_map(|(_, it)| match *it {
AssocItemId::TypeAliasId(id) => {
let assoc_ty_data = db.associated_ty_data(id);
Some(assoc_ty_data)
}
_ => None,
})
.any(|assoc_ty_data| {
assoc_ty_data.binders.skip_binders().bounds.iter().any(|bound| {
let def = from_assoc_type_id(assoc_ty_data.id).into();
match bound.skip_binders() {
InlineBound::TraitBound(it) => it.args_no_self.iter().any(|arg| {
contains_illegal_self_type_reference(
db,
def,
trait_,
arg,
DebruijnIndex::ONE,
AllowSelfProjection::Yes,
)
}),
InlineBound::AliasEqBound(it) => it.parameters.iter().any(|arg| {
contains_illegal_self_type_reference(
db,
def,
trait_,
arg,
DebruijnIndex::ONE,
AllowSelfProjection::Yes,
)
}),
}
})
})
}
#[derive(Clone, Copy)]
enum AllowSelfProjection {
Yes,
No,
}
fn predicate_references_self(
db: &dyn HirDatabase,
trait_: TraitId,
predicate: &Binders<Binders<WhereClause>>,
allow_self_projection: AllowSelfProjection,
) -> bool {
match predicate.skip_binders().skip_binders() {
WhereClause::Implemented(trait_ref) => {
trait_ref.substitution.iter(Interner).skip(1).any(|arg| {
contains_illegal_self_type_reference(
db,
trait_.into(),
trait_,
arg,
DebruijnIndex::ONE,
allow_self_projection,
)
})
}
WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(proj), .. }) => {
proj.substitution.iter(Interner).skip(1).any(|arg| {
contains_illegal_self_type_reference(
db,
trait_.into(),
trait_,
arg,
DebruijnIndex::ONE,
allow_self_projection,
)
})
}
_ => false,
}
}
fn contains_illegal_self_type_reference<T: TypeVisitable<Interner>>(
db: &dyn HirDatabase,
def: GenericDefId,
trait_: TraitId,
t: &T,
outer_binder: DebruijnIndex,
allow_self_projection: AllowSelfProjection,
) -> bool {
let Some(trait_self_param_idx) = trait_self_param_idx(db, def) else {
return false;
};
struct IllegalSelfTypeVisitor<'a> {
db: &'a dyn HirDatabase,
trait_: TraitId,
super_traits: Option<SmallVec<[TraitId; 4]>>,
trait_self_param_idx: usize,
allow_self_projection: AllowSelfProjection,
}
impl TypeVisitor<Interner> for IllegalSelfTypeVisitor<'_> {
type BreakTy = ();
fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = Self::BreakTy> {
self
}
fn interner(&self) -> Interner {
Interner
}
fn visit_ty(&mut self, ty: &Ty, outer_binder: DebruijnIndex) -> ControlFlow<Self::BreakTy> {
match ty.kind(Interner) {
TyKind::BoundVar(BoundVar { debruijn, index }) => {
if *debruijn == outer_binder && *index == self.trait_self_param_idx {
ControlFlow::Break(())
} else {
ty.super_visit_with(self.as_dyn(), outer_binder)
}
}
TyKind::Alias(AliasTy::Projection(proj)) => match self.allow_self_projection {
AllowSelfProjection::Yes => {
let trait_ = proj.trait_(self.db);
if self.super_traits.is_none() {
self.super_traits = Some(all_super_traits(self.db, self.trait_));
}
if self.super_traits.as_ref().is_some_and(|s| s.contains(&trait_)) {
ControlFlow::Continue(())
} else {
ty.super_visit_with(self.as_dyn(), outer_binder)
}
}
AllowSelfProjection::No => ty.super_visit_with(self.as_dyn(), outer_binder),
},
_ => ty.super_visit_with(self.as_dyn(), outer_binder),
}
}
fn visit_const(
&mut self,
constant: &chalk_ir::Const<Interner>,
outer_binder: DebruijnIndex,
) -> std::ops::ControlFlow<Self::BreakTy> {
constant.data(Interner).ty.super_visit_with(self.as_dyn(), outer_binder)
}
}
let mut visitor = IllegalSelfTypeVisitor {
db,
trait_,
super_traits: None,
trait_self_param_idx,
allow_self_projection,
};
t.visit_with(visitor.as_dyn(), outer_binder).is_break()
}
fn contains_illegal_self_type_reference_ns<
'db,
T: rustc_type_ir::TypeVisitable<DbInterner<'db>>,
>(
db: &'db dyn HirDatabase,
trait_: TraitId,
t: &T,
allow_self_projection: AllowSelfProjection,
) -> bool {
struct IllegalSelfTypeVisitor<'db> {
db: &'db dyn HirDatabase,
trait_: TraitId,
super_traits: Option<SmallVec<[TraitId; 4]>>,
allow_self_projection: AllowSelfProjection,
}
impl<'db> rustc_type_ir::TypeVisitor<DbInterner<'db>> for IllegalSelfTypeVisitor<'db> {
type Result = ControlFlow<()>;
fn visit_ty(
&mut self,
ty: <DbInterner<'db> as rustc_type_ir::Interner>::Ty,
) -> Self::Result {
match ty.kind() {
rustc_type_ir::TyKind::Param(param) if param.index == 0 => ControlFlow::Break(()),
rustc_type_ir::TyKind::Param(_) => ControlFlow::Continue(()),
rustc_type_ir::TyKind::Alias(AliasTyKind::Projection, proj) => match self
.allow_self_projection
{
AllowSelfProjection::Yes => {
let trait_ = proj.trait_def_id(DbInterner::new_with(self.db, None, None));
let trait_ = match trait_ {
SolverDefId::TraitId(id) => id,
_ => unreachable!(),
};
if self.super_traits.is_none() {
self.super_traits = Some(all_super_traits(self.db, self.trait_));
}
if self.super_traits.as_ref().is_some_and(|s| s.contains(&trait_)) {
ControlFlow::Continue(())
} else {
ty.super_visit_with(self)
}
}
AllowSelfProjection::No => ty.super_visit_with(self),
},
_ => ty.super_visit_with(self),
}
}
}
let mut visitor =
IllegalSelfTypeVisitor { db, trait_, super_traits: None, allow_self_projection };
t.visit_with(&mut visitor).is_break()
}
fn dyn_compatibility_violation_for_assoc_item<F>(
db: &dyn HirDatabase,
trait_: TraitId,
item: AssocItemId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
{
// Any item that has a `Self : Sized` requisite is otherwise
// exempt from the regulations.
if generics_require_sized_self(db, item.into()) {
return ControlFlow::Continue(());
}
match item {
AssocItemId::ConstId(it) => cb(DynCompatibilityViolation::AssocConst(it)),
AssocItemId::FunctionId(it) => {
virtual_call_violations_for_method(db, trait_, it, &mut |mvc| {
cb(DynCompatibilityViolation::Method(it, mvc))
})
}
AssocItemId::TypeAliasId(it) => {
let def_map = CrateRootModuleId::from(trait_.krate(db)).def_map(db);
if def_map.is_unstable_feature_enabled(&intern::sym::generic_associated_type_extended) {
ControlFlow::Continue(())
} else {
let generic_params = db.generic_params(item.into());
if !generic_params.is_empty() {
cb(DynCompatibilityViolation::GAT(it))
} else {
ControlFlow::Continue(())
}
}
}
}
}
fn virtual_call_violations_for_method<F>(
db: &dyn HirDatabase,
trait_: TraitId,
func: FunctionId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(MethodViolationCode) -> ControlFlow<()>,
{
let func_data = db.function_signature(func);
if !func_data.has_self_param() {
cb(MethodViolationCode::StaticMethod)?;
}
if func_data.is_async() {
cb(MethodViolationCode::AsyncFn)?;
}
let sig = db.callable_item_signature_ns(func.into());
if sig.skip_binder().inputs().iter().skip(1).any(|ty| {
contains_illegal_self_type_reference_ns(db, trait_, &ty, AllowSelfProjection::Yes)
}) {
cb(MethodViolationCode::ReferencesSelfInput)?;
}
if contains_illegal_self_type_reference_ns(
db,
trait_,
&sig.skip_binder().output(),
AllowSelfProjection::Yes,
) {
cb(MethodViolationCode::ReferencesSelfOutput)?;
}
if !func_data.is_async()
&& let Some(mvc) = contains_illegal_impl_trait_in_trait(db, &sig)
{
cb(mvc)?;
}
let generic_params = db.generic_params(func.into());
if generic_params.len_type_or_consts() > 0 {
cb(MethodViolationCode::Generic)?;
}
if func_data.has_self_param() && !receiver_is_dispatchable(db, trait_, func, &sig) {
cb(MethodViolationCode::UndispatchableReceiver)?;
}
let predicates = &*db.generic_predicates_without_parent_ns(func.into());
for pred in predicates {
let pred = pred.kind().skip_binder();
if matches!(pred, ClauseKind::TypeOutlives(_)) {
continue;
}
// Allow `impl AutoTrait` predicates
let interner = DbInterner::new_with(db, Some(trait_.krate(db)), None);
if let ClauseKind::Trait(TraitPredicate {
trait_ref: pred_trait_ref,
polarity: PredicatePolarity::Positive,
}) = pred
&& let SolverDefId::TraitId(trait_id) = pred_trait_ref.def_id
&& let trait_data = db.trait_signature(trait_id)
&& trait_data.flags.contains(TraitFlags::AUTO)
&& pred_trait_ref.self_ty()
== crate::next_solver::Ty::new(
interner,
rustc_type_ir::TyKind::Param(crate::next_solver::ParamTy { index: 0 }),
)
{
continue;
}
if contains_illegal_self_type_reference_ns(db, trait_, &pred, AllowSelfProjection::Yes) {
cb(MethodViolationCode::WhereClauseReferencesSelf)?;
break;
}
}
ControlFlow::Continue(())
}
fn receiver_is_dispatchable<'db>(
db: &dyn HirDatabase,
trait_: TraitId,
func: FunctionId,
sig: &crate::next_solver::EarlyBinder<
'db,
crate::next_solver::Binder<'db, rustc_type_ir::FnSig<DbInterner<'db>>>,
>,
) -> bool {
let sig = sig.instantiate_identity();
let interner: DbInterner<'_> = DbInterner::new_with(db, Some(trait_.krate(db)), None);
let self_param_ty = crate::next_solver::Ty::new(
interner,
rustc_type_ir::TyKind::Param(crate::next_solver::ParamTy { index: 0 }),
);
// `self: Self` can't be dispatched on, but this is already considered dyn-compatible
// See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437
if sig.inputs().iter().next().is_some_and(|p| p.skip_binder() == self_param_ty) {
return true;
}
let Some(&receiver_ty) = sig.inputs().skip_binder().as_slice().first() else {
return false;
};
let krate = func.module(db).krate();
let traits = (
LangItem::Unsize.resolve_trait(db, krate),
LangItem::DispatchFromDyn.resolve_trait(db, krate),
);
let (Some(unsize_did), Some(dispatch_from_dyn_did)) = traits else {
return false;
};
let meta_sized_did = LangItem::MetaSized.resolve_trait(db, krate);
let Some(meta_sized_did) = meta_sized_did else {
return false;
};
// Type `U`
let unsized_self_ty = crate::next_solver::Ty::new_param(interner, u32::MAX, Symbol::empty());
// `Receiver[Self => U]`
let unsized_receiver_ty = receiver_for_self_ty(interner, func, receiver_ty, unsized_self_ty);
let param_env = {
let generic_predicates = &*db.generic_predicates_ns(func.into());
// Self: Unsize<U>
let unsize_predicate = crate::next_solver::TraitRef::new(
interner,
SolverDefId::TraitId(unsize_did),
[self_param_ty, unsized_self_ty],
);
// U: Trait<Arg1, ..., ArgN>
let trait_def_id = SolverDefId::TraitId(trait_);
let args = GenericArgs::for_item(interner, trait_def_id, |name, index, kind, _| {
if index == 0 { unsized_self_ty.into() } else { mk_param(index, name, kind) }
});
let trait_predicate =
crate::next_solver::TraitRef::new_from_args(interner, trait_def_id, args);
let meta_sized_predicate = crate::next_solver::TraitRef::new(
interner,
SolverDefId::TraitId(meta_sized_did),
[unsized_self_ty],
);
ParamEnv {
clauses: Clauses::new_from_iter(
interner,
generic_predicates.iter().copied().chain([
unsize_predicate.upcast(interner),
trait_predicate.upcast(interner),
meta_sized_predicate.upcast(interner),
]),
),
}
};
// Receiver: DispatchFromDyn<Receiver[Self => U]>
let predicate = crate::next_solver::TraitRef::new(
interner,
SolverDefId::TraitId(dispatch_from_dyn_did),
[receiver_ty, unsized_receiver_ty],
);
let goal = crate::next_solver::Goal::new(interner, param_env, predicate);
let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
// the receiver is dispatchable iff the obligation holds
let res = next_trait_solve_in_ctxt(&infcx, goal);
res.map_or(false, |res| matches!(res.1, rustc_type_ir::solve::Certainty::Yes))
}
fn receiver_for_self_ty<'db>(
interner: DbInterner<'db>,
func: FunctionId,
receiver_ty: crate::next_solver::Ty<'db>,
self_ty: crate::next_solver::Ty<'db>,
) -> crate::next_solver::Ty<'db> {
let args = crate::next_solver::GenericArgs::for_item(
interner,
SolverDefId::FunctionId(func),
|name, index, kind, _| {
if index == 0 { self_ty.into() } else { mk_param(index, name, kind) }
},
);
crate::next_solver::EarlyBinder::bind(receiver_ty).instantiate(interner, args)
}
fn contains_illegal_impl_trait_in_trait<'db>(
db: &'db dyn HirDatabase,
sig: &crate::next_solver::EarlyBinder<
'db,
crate::next_solver::Binder<'db, rustc_type_ir::FnSig<DbInterner<'db>>>,
>,
) -> Option<MethodViolationCode> {
struct OpaqueTypeCollector(FxHashSet<InternedOpaqueTyId>);
impl<'db> rustc_type_ir::TypeVisitor<DbInterner<'db>> for OpaqueTypeCollector {
type Result = ControlFlow<()>;
fn visit_ty(
&mut self,
ty: <DbInterner<'db> as rustc_type_ir::Interner>::Ty,
) -> Self::Result {
if let rustc_type_ir::TyKind::Alias(AliasTyKind::Opaque, op) = ty.kind() {
let id = match op.def_id {
SolverDefId::InternedOpaqueTyId(id) => id,
_ => unreachable!(),
};
self.0.insert(id);
}
ty.super_visit_with(self)
}
}
let ret = sig.skip_binder().output();
let mut visitor = OpaqueTypeCollector(FxHashSet::default());
_ = ret.visit_with(&mut visitor);
// Since we haven't implemented RPITIT in proper way like rustc yet,
// just check whether `ret` contains RPIT for now
for opaque_ty in visitor.0 {
let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty);
if matches!(impl_trait_id, ImplTraitId::ReturnTypeImplTrait(..)) {
return Some(MethodViolationCode::ReferencesImplTraitInTrait);
}
}
None
}
#[cfg(test)]
mod tests;