mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
550 lines
19 KiB
Rust
550 lines
19 KiB
Rust
//! Compute the dyn-compatibility of a trait
|
|
|
|
use std::ops::ControlFlow;
|
|
|
|
use hir_def::{
|
|
AssocItemId, ConstId, CrateRootModuleId, FunctionId, GenericDefId, HasModule, TraitId,
|
|
TypeAliasId, TypeOrConstParamId, TypeParamId, hir::generics::LocalTypeOrConstParamId,
|
|
lang_item::LangItem, signatures::TraitFlags,
|
|
};
|
|
use intern::Symbol;
|
|
use rustc_hash::FxHashSet;
|
|
use rustc_type_ir::{
|
|
AliasTyKind, ClauseKind, PredicatePolarity, TypeSuperVisitable as _, TypeVisitable as _,
|
|
Upcast, elaborate,
|
|
inherent::{IntoKind, SliceLike},
|
|
};
|
|
use smallvec::SmallVec;
|
|
|
|
use crate::{
|
|
ImplTraitId,
|
|
db::{HirDatabase, InternedOpaqueTyId},
|
|
lower_nextsolver::associated_ty_item_bounds,
|
|
next_solver::{
|
|
Clause, Clauses, DbInterner, GenericArgs, ParamEnv, SolverDefId, TraitPredicate, TraitRef,
|
|
TypingMode, infer::DbInternerInferExt, mk_param,
|
|
},
|
|
traits::next_trait_solve_in_ctxt,
|
|
};
|
|
|
|
#[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> {
|
|
let interner = DbInterner::new_with(db, Some(trait_.krate(db)), None);
|
|
for super_trait in elaborate::supertrait_def_ids(interner, trait_.into()) {
|
|
if let Some(v) = db.dyn_compatibility_of_trait(super_trait.0) {
|
|
return if super_trait.0 == trait_ {
|
|
Some(v)
|
|
} else {
|
|
Some(DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait.0))
|
|
};
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn dyn_compatibility_with_callback<F>(
|
|
db: &dyn HirDatabase,
|
|
trait_: TraitId,
|
|
cb: &mut F,
|
|
) -> ControlFlow<()>
|
|
where
|
|
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
|
|
{
|
|
let interner = DbInterner::new_with(db, Some(trait_.krate(db)), None);
|
|
for super_trait in elaborate::supertrait_def_ids(interner, trait_.into()).skip(1) {
|
|
if db.dyn_compatibility_of_trait(super_trait.0).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 interner = DbInterner::new_with(db, Some(krate), None);
|
|
let predicates = db.generic_predicates_ns(def);
|
|
// FIXME: We should use `explicit_predicates_of` here, which hasn't been implemented to
|
|
// rust-analyzer yet
|
|
// https://github.com/rust-lang/rust/blob/ddaf12390d3ffb7d5ba74491a48f3cd528e5d777/compiler/rustc_hir_analysis/src/collect/predicates_of.rs#L490
|
|
elaborate::elaborate(interner, predicates.iter().copied()).any(|pred| {
|
|
match pred.kind().skip_binder() {
|
|
ClauseKind::Trait(trait_pred) => {
|
|
if sized == trait_pred.def_id().0
|
|
&& let rustc_type_ir::TyKind::Param(param_ty) =
|
|
trait_pred.trait_ref.self_ty().kind()
|
|
&& param_ty.index == 0
|
|
{
|
|
true
|
|
} else {
|
|
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_ns(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) => Some(associated_ty_item_bounds(db, id)),
|
|
_ => None,
|
|
})
|
|
.any(|bounds| {
|
|
bounds.skip_binder().iter().any(|pred| match pred.skip_binder() {
|
|
rustc_type_ir::ExistentialPredicate::Trait(it) => it.args.iter().any(|arg| {
|
|
contains_illegal_self_type_reference(db, trait_, &arg, AllowSelfProjection::Yes)
|
|
}),
|
|
rustc_type_ir::ExistentialPredicate::Projection(it) => it.args.iter().any(|arg| {
|
|
contains_illegal_self_type_reference(db, trait_, &arg, AllowSelfProjection::Yes)
|
|
}),
|
|
rustc_type_ir::ExistentialPredicate::AutoTrait(_) => false,
|
|
})
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum AllowSelfProjection {
|
|
Yes,
|
|
No,
|
|
}
|
|
|
|
fn predicate_references_self<'db>(
|
|
db: &'db dyn HirDatabase,
|
|
trait_: TraitId,
|
|
predicate: &Clause<'db>,
|
|
allow_self_projection: AllowSelfProjection,
|
|
) -> bool {
|
|
match predicate.kind().skip_binder() {
|
|
ClauseKind::Trait(trait_pred) => trait_pred.trait_ref.args.iter().skip(1).any(|arg| {
|
|
contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection)
|
|
}),
|
|
ClauseKind::Projection(proj_pred) => {
|
|
proj_pred.projection_term.args.iter().skip(1).any(|arg| {
|
|
contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection)
|
|
})
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn contains_illegal_self_type_reference<'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 {
|
|
let interner = DbInterner::new_with(self.db, None, None);
|
|
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(
|
|
elaborate::supertrait_def_ids(interner, self.trait_.into())
|
|
.map(|super_trait| super_trait.0)
|
|
.collect(),
|
|
)
|
|
}
|
|
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(db, trait_, &ty, AllowSelfProjection::Yes))
|
|
{
|
|
cb(MethodViolationCode::ReferencesSelfInput)?;
|
|
}
|
|
|
|
if contains_illegal_self_type_reference(
|
|
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
|
|
if let ClauseKind::Trait(TraitPredicate {
|
|
trait_ref: pred_trait_ref,
|
|
polarity: PredicatePolarity::Positive,
|
|
}) = pred
|
|
&& let trait_data = db.trait_signature(pred_trait_ref.def_id.0)
|
|
&& trait_data.flags.contains(TraitFlags::AUTO)
|
|
&& let rustc_type_ir::TyKind::Param(crate::next_solver::ParamTy { index: 0, .. }) =
|
|
pred_trait_ref.self_ty().kind()
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if contains_illegal_self_type_reference(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_id = TypeParamId::from_unchecked(TypeOrConstParamId {
|
|
parent: trait_.into(),
|
|
local_id: LocalTypeOrConstParamId::from_raw(la_arena::RawIdx::from_u32(0)),
|
|
});
|
|
let self_param_ty = crate::next_solver::Ty::new(
|
|
interner,
|
|
rustc_type_ir::TyKind::Param(crate::next_solver::ParamTy { index: 0, id: self_param_id }),
|
|
);
|
|
|
|
// `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`
|
|
// FIXME: That seems problematic to fake a generic param like that?
|
|
let unsized_self_ty =
|
|
crate::next_solver::Ty::new_param(interner, self_param_id, 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 =
|
|
TraitRef::new(interner, unsize_did.into(), [self_param_ty, unsized_self_ty]);
|
|
|
|
// U: Trait<Arg1, ..., ArgN>
|
|
let args = GenericArgs::for_item(interner, trait_.into(), |name, index, kind, _| {
|
|
if index == 0 { unsized_self_ty.into() } else { mk_param(interner, index, name, kind) }
|
|
});
|
|
let trait_predicate = TraitRef::new_from_args(interner, trait_.into(), args);
|
|
|
|
let meta_sized_predicate =
|
|
TraitRef::new(interner, meta_sized_did.into(), [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 =
|
|
TraitRef::new(interner, dispatch_from_dyn_did.into(), [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(interner, 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;
|