mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 21:54:42 +00:00
Auto merge of #17814 - ShoyuVanilla:object-safety, r=Veykril
feat: Implement object safety and its hovering hint Resolves #17779 - [x] Fill missing implementations - [x] Hover rendering - [x] Implement object safety's own test suite, like layout - [x] Add test cases (from rustc maybe) - [x] Clean up ugly codes - [x] Add doc string
This commit is contained in:
commit
0ae42bd425
19 changed files with 1194 additions and 66 deletions
|
@ -381,9 +381,9 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
|
|||
TyKind::Error.intern(Interner)
|
||||
}
|
||||
|
||||
fn is_object_safe(&self, _trait_id: chalk_ir::TraitId<Interner>) -> bool {
|
||||
// FIXME: implement actual object safety
|
||||
true
|
||||
fn is_object_safe(&self, trait_id: chalk_ir::TraitId<Interner>) -> bool {
|
||||
let trait_ = from_chalk_trait_id(trait_id);
|
||||
crate::object_safety::object_safety(self.db, trait_).is_none()
|
||||
}
|
||||
|
||||
fn closure_kind(
|
||||
|
|
|
@ -2007,7 +2007,7 @@ fn function_traits() {
|
|||
);
|
||||
check_number(
|
||||
r#"
|
||||
//- minicore: coerce_unsized, fn
|
||||
//- minicore: coerce_unsized, fn, dispatch_from_dyn
|
||||
fn add2(x: u8) -> u8 {
|
||||
x + 2
|
||||
}
|
||||
|
@ -2062,7 +2062,7 @@ fn function_traits() {
|
|||
fn dyn_trait() {
|
||||
check_number(
|
||||
r#"
|
||||
//- minicore: coerce_unsized, index, slice
|
||||
//- minicore: coerce_unsized, index, slice, dispatch_from_dyn
|
||||
trait Foo {
|
||||
fn foo(&self) -> u8 { 10 }
|
||||
}
|
||||
|
@ -2085,7 +2085,7 @@ fn dyn_trait() {
|
|||
);
|
||||
check_number(
|
||||
r#"
|
||||
//- minicore: coerce_unsized, index, slice
|
||||
//- minicore: coerce_unsized, index, slice, dispatch_from_dyn
|
||||
trait Foo {
|
||||
fn foo(&self) -> i32 { 10 }
|
||||
}
|
||||
|
@ -2109,7 +2109,7 @@ fn dyn_trait() {
|
|||
);
|
||||
check_number(
|
||||
r#"
|
||||
//- minicore: coerce_unsized, index, slice
|
||||
//- minicore: coerce_unsized, index, slice, dispatch_from_dyn
|
||||
trait A {
|
||||
fn x(&self) -> i32;
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ fn size_of_val() {
|
|||
);
|
||||
check_number(
|
||||
r#"
|
||||
//- minicore: coerce_unsized, fmt, builtin_impls
|
||||
//- minicore: coerce_unsized, fmt, builtin_impls, dispatch_from_dyn
|
||||
extern "rust-intrinsic" {
|
||||
pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use base_db::{
|
|||
use hir_def::{
|
||||
db::DefDatabase, hir::ExprId, layout::TargetDataLayout, AdtId, BlockId, CallableDefId,
|
||||
ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, GeneralConstId, GenericDefId, ImplId,
|
||||
LifetimeParamId, LocalFieldId, StaticId, TypeAliasId, TypeOrConstParamId, VariantId,
|
||||
LifetimeParamId, LocalFieldId, StaticId, TraitId, TypeAliasId, TypeOrConstParamId, VariantId,
|
||||
};
|
||||
use la_arena::ArenaMap;
|
||||
use smallvec::SmallVec;
|
||||
|
@ -24,6 +24,7 @@ use crate::{
|
|||
lower::{GenericDefaults, GenericPredicates},
|
||||
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
|
||||
mir::{BorrowckResult, MirBody, MirLowerError},
|
||||
object_safety::ObjectSafetyViolation,
|
||||
Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner,
|
||||
PolyFnSig, Substitution, TraitEnvironment, TraitRef, Ty, TyDefId, ValueTyDefId,
|
||||
};
|
||||
|
@ -107,6 +108,9 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[salsa::invoke(crate::layout::target_data_layout_query)]
|
||||
fn target_data_layout(&self, krate: CrateId) -> Result<Arc<TargetDataLayout>, Arc<str>>;
|
||||
|
||||
#[salsa::invoke(crate::object_safety::object_safety_of_trait_query)]
|
||||
fn object_safety_of_trait(&self, trait_: TraitId) -> Option<ObjectSafetyViolation>;
|
||||
|
||||
#[salsa::invoke(crate::lower::ty_query)]
|
||||
#[salsa::cycle(crate::lower::ty_recover)]
|
||||
fn ty(&self, def: TyDefId) -> Binders<Ty>;
|
||||
|
|
|
@ -225,6 +225,23 @@ impl Generics {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn trait_self_param_idx(db: &dyn DefDatabase, def: GenericDefId) -> Option<usize> {
|
||||
match def {
|
||||
GenericDefId::TraitId(_) | GenericDefId::TraitAliasId(_) => {
|
||||
let params = db.generic_params(def);
|
||||
params.trait_self_param().map(|idx| idx.into_raw().into_u32() as usize)
|
||||
}
|
||||
GenericDefId::ImplId(_) => None,
|
||||
_ => {
|
||||
let parent_def = parent_generic_def(db, def)?;
|
||||
let parent_params = db.generic_params(parent_def);
|
||||
let parent_self_idx = parent_params.trait_self_param()?.into_raw().into_u32() as usize;
|
||||
let self_params = db.generic_params(def);
|
||||
Some(self_params.len() + parent_self_idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option<GenericDefId> {
|
||||
let container = match def {
|
||||
GenericDefId::FunctionId(it) => it.lookup(db).container,
|
||||
|
|
|
@ -42,6 +42,7 @@ pub mod lang_items;
|
|||
pub mod layout;
|
||||
pub mod method_resolution;
|
||||
pub mod mir;
|
||||
pub mod object_safety;
|
||||
pub mod primitive;
|
||||
pub mod traits;
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ use crate::{
|
|||
},
|
||||
db::HirDatabase,
|
||||
error_lifetime,
|
||||
generics::{generics, Generics},
|
||||
generics::{generics, trait_self_param_idx, Generics},
|
||||
make_binders,
|
||||
mapping::{from_chalk_trait_id, lt_to_placeholder_idx, ToChalk},
|
||||
static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
|
||||
|
@ -1747,21 +1747,7 @@ fn implicitly_sized_clauses<'a, 'subst: 'a>(
|
|||
.lang_item(resolver.krate(), LangItem::Sized)
|
||||
.and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id))?;
|
||||
|
||||
let get_trait_self_idx = |container: ItemContainerId| {
|
||||
if matches!(container, ItemContainerId::TraitId(_)) {
|
||||
let generics = generics(db.upcast(), def);
|
||||
Some(generics.len_self())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let trait_self_idx = match def {
|
||||
GenericDefId::TraitId(_) => Some(0),
|
||||
GenericDefId::FunctionId(it) => get_trait_self_idx(it.lookup(db.upcast()).container),
|
||||
GenericDefId::ConstId(it) => get_trait_self_idx(it.lookup(db.upcast()).container),
|
||||
GenericDefId::TypeAliasId(it) => get_trait_self_idx(it.lookup(db.upcast()).container),
|
||||
_ => None,
|
||||
};
|
||||
let trait_self_idx = trait_self_param_idx(db.upcast(), def);
|
||||
|
||||
Some(
|
||||
substitution
|
||||
|
|
|
@ -849,7 +849,7 @@ fn main() {
|
|||
fn regression_14966() {
|
||||
check_pass(
|
||||
r#"
|
||||
//- minicore: fn, copy, coerce_unsized
|
||||
//- minicore: fn, copy, coerce_unsized, dispatch_from_dyn
|
||||
trait A<T> {
|
||||
fn a(&self) {}
|
||||
}
|
||||
|
|
631
crates/hir-ty/src/object_safety.rs
Normal file
631
crates/hir-ty/src/object_safety.rs
Normal file
|
@ -0,0 +1,631 @@
|
|||
//! Compute the object-safety of a trait
|
||||
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use chalk_ir::{
|
||||
cast::Cast,
|
||||
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
|
||||
DebruijnIndex,
|
||||
};
|
||||
use chalk_solve::rust_ir::InlineBound;
|
||||
use hir_def::{
|
||||
lang_item::LangItem, AssocItemId, ConstId, FunctionId, GenericDefId, HasModule, TraitId,
|
||||
TypeAliasId,
|
||||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
all_super_traits,
|
||||
db::HirDatabase,
|
||||
from_assoc_type_id, from_chalk_trait_id,
|
||||
generics::{generics, trait_self_param_idx},
|
||||
lower::callable_item_sig,
|
||||
to_assoc_type_id, to_chalk_trait_id,
|
||||
utils::elaborate_clause_supertraits,
|
||||
AliasEq, AliasTy, Binders, BoundVar, CallableSig, GoalData, ImplTraitId, Interner, OpaqueTyId,
|
||||
ProjectionTyExt, Solution, Substitution, TraitRef, Ty, TyKind, WhereClause,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ObjectSafetyViolation {
|
||||
SizedSelf,
|
||||
SelfReferential,
|
||||
Method(FunctionId, MethodViolationCode),
|
||||
AssocConst(ConstId),
|
||||
GAT(TypeAliasId),
|
||||
// This doesn't exist in rustc, but added for better visualization
|
||||
HasNonSafeSuperTrait(TraitId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MethodViolationCode {
|
||||
StaticMethod,
|
||||
ReferencesSelfInput,
|
||||
ReferencesSelfOutput,
|
||||
ReferencesImplTraitInTrait,
|
||||
AsyncFn,
|
||||
WhereClauseReferencesSelf,
|
||||
Generic,
|
||||
UndispatchableReceiver,
|
||||
}
|
||||
|
||||
pub fn object_safety(db: &dyn HirDatabase, trait_: TraitId) -> Option<ObjectSafetyViolation> {
|
||||
for super_trait in all_super_traits(db.upcast(), trait_).into_iter().skip(1).rev() {
|
||||
if db.object_safety_of_trait(super_trait).is_some() {
|
||||
return Some(ObjectSafetyViolation::HasNonSafeSuperTrait(super_trait));
|
||||
}
|
||||
}
|
||||
|
||||
db.object_safety_of_trait(trait_)
|
||||
}
|
||||
|
||||
pub fn object_safety_with_callback<F>(
|
||||
db: &dyn HirDatabase,
|
||||
trait_: TraitId,
|
||||
cb: &mut F,
|
||||
) -> ControlFlow<()>
|
||||
where
|
||||
F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>,
|
||||
{
|
||||
for super_trait in all_super_traits(db.upcast(), trait_).into_iter().skip(1).rev() {
|
||||
if db.object_safety_of_trait(super_trait).is_some() {
|
||||
cb(ObjectSafetyViolation::HasNonSafeSuperTrait(trait_))?;
|
||||
}
|
||||
}
|
||||
|
||||
object_safety_of_trait_with_callback(db, trait_, cb)
|
||||
}
|
||||
|
||||
pub fn object_safety_of_trait_with_callback<F>(
|
||||
db: &dyn HirDatabase,
|
||||
trait_: TraitId,
|
||||
cb: &mut F,
|
||||
) -> ControlFlow<()>
|
||||
where
|
||||
F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>,
|
||||
{
|
||||
// Check whether this has a `Sized` bound
|
||||
if generics_require_sized_self(db, trait_.into()) {
|
||||
cb(ObjectSafetyViolation::SizedSelf)?;
|
||||
}
|
||||
|
||||
// Check if there exist bounds that referencing self
|
||||
if predicates_reference_self(db, trait_) {
|
||||
cb(ObjectSafetyViolation::SelfReferential)?;
|
||||
}
|
||||
if bounds_reference_self(db, trait_) {
|
||||
cb(ObjectSafetyViolation::SelfReferential)?;
|
||||
}
|
||||
|
||||
// rustc checks for non-lifetime binders here, but we don't support HRTB yet
|
||||
|
||||
let trait_data = db.trait_data(trait_);
|
||||
for (_, assoc_item) in &trait_data.items {
|
||||
object_safety_violation_for_assoc_item(db, trait_, *assoc_item, cb)?;
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
pub fn object_safety_of_trait_query(
|
||||
db: &dyn HirDatabase,
|
||||
trait_: TraitId,
|
||||
) -> Option<ObjectSafetyViolation> {
|
||||
let mut res = None;
|
||||
object_safety_of_trait_with_callback(db, trait_, &mut |osv| {
|
||||
res = Some(osv);
|
||||
ControlFlow::Break(())
|
||||
});
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn generics_require_sized_self(db: &dyn HirDatabase, def: GenericDefId) -> bool {
|
||||
let krate = def.module(db.upcast()).krate();
|
||||
let Some(sized) = db.lang_item(krate, LangItem::Sized).and_then(|l| l.as_trait()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(trait_self_param_idx) = trait_self_param_idx(db.upcast(), 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 {
|
||||
if 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 = db.trait_data(trait_);
|
||||
trait_data
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|(_, it)| match *it {
|
||||
AssocItemId::TypeAliasId(id) => {
|
||||
let assoc_ty_id = to_assoc_type_id(id);
|
||||
let assoc_ty_data = db.associated_ty_data(assoc_ty_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.upcast(), 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<'a> TypeVisitor<Interner> for IllegalSelfTypeVisitor<'a> {
|
||||
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.upcast(), 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 object_safety_violation_for_assoc_item<F>(
|
||||
db: &dyn HirDatabase,
|
||||
trait_: TraitId,
|
||||
item: AssocItemId,
|
||||
cb: &mut F,
|
||||
) -> ControlFlow<()>
|
||||
where
|
||||
F: FnMut(ObjectSafetyViolation) -> 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(ObjectSafetyViolation::AssocConst(it)),
|
||||
AssocItemId::FunctionId(it) => {
|
||||
virtual_call_violations_for_method(db, trait_, it, &mut |mvc| {
|
||||
cb(ObjectSafetyViolation::Method(it, mvc))
|
||||
})
|
||||
}
|
||||
AssocItemId::TypeAliasId(it) => {
|
||||
let def_map = db.crate_def_map(trait_.krate(db.upcast()));
|
||||
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.len_type_or_consts() > 0 {
|
||||
cb(ObjectSafetyViolation::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_data(func);
|
||||
if !func_data.has_self_param() {
|
||||
cb(MethodViolationCode::StaticMethod)?;
|
||||
}
|
||||
|
||||
if func_data.is_async() {
|
||||
cb(MethodViolationCode::AsyncFn)?;
|
||||
}
|
||||
|
||||
let sig = callable_item_sig(db, func.into());
|
||||
if sig.skip_binders().params().iter().skip(1).any(|ty| {
|
||||
contains_illegal_self_type_reference(
|
||||
db,
|
||||
func.into(),
|
||||
trait_,
|
||||
ty,
|
||||
DebruijnIndex::INNERMOST,
|
||||
AllowSelfProjection::Yes,
|
||||
)
|
||||
}) {
|
||||
cb(MethodViolationCode::ReferencesSelfInput)?;
|
||||
}
|
||||
|
||||
if contains_illegal_self_type_reference(
|
||||
db,
|
||||
func.into(),
|
||||
trait_,
|
||||
sig.skip_binders().ret(),
|
||||
DebruijnIndex::INNERMOST,
|
||||
AllowSelfProjection::Yes,
|
||||
) {
|
||||
cb(MethodViolationCode::ReferencesSelfOutput)?;
|
||||
}
|
||||
|
||||
if !func_data.is_async() {
|
||||
if 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(func.into());
|
||||
let mut parent_predicates = (*db.generic_predicates(trait_.into()))
|
||||
.iter()
|
||||
.map(|b| b.skip_binders().skip_binders().clone())
|
||||
.fold(FxHashMap::default(), |mut acc, item| {
|
||||
acc.entry(item)
|
||||
.and_modify(|cnt| {
|
||||
*cnt += 1;
|
||||
})
|
||||
.or_insert(1);
|
||||
acc
|
||||
});
|
||||
let trait_self_idx = trait_self_param_idx(db.upcast(), func.into());
|
||||
for pred in predicates {
|
||||
let pred = pred.skip_binders().skip_binders();
|
||||
|
||||
// Skip predicates from parent, i.e. the trait that contains this method
|
||||
if let Some(cnt) = parent_predicates.get_mut(pred) {
|
||||
if *cnt > 0 {
|
||||
*cnt -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(pred, WhereClause::TypeOutlives(_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow `impl AutoTrait` predicates
|
||||
if let WhereClause::Implemented(TraitRef { trait_id, substitution }) = pred {
|
||||
let trait_data = db.trait_data(from_chalk_trait_id(*trait_id));
|
||||
if trait_data.is_auto
|
||||
&& substitution
|
||||
.as_slice(Interner)
|
||||
.first()
|
||||
.and_then(|arg| arg.ty(Interner))
|
||||
.and_then(|ty| ty.bound_var(Interner))
|
||||
.is_some_and(|b| {
|
||||
b.debruijn == DebruijnIndex::ONE && Some(b.index) == trait_self_idx
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if contains_illegal_self_type_reference(
|
||||
db,
|
||||
func.into(),
|
||||
trait_,
|
||||
pred,
|
||||
DebruijnIndex::ONE,
|
||||
AllowSelfProjection::Yes,
|
||||
) {
|
||||
cb(MethodViolationCode::WhereClauseReferencesSelf)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn receiver_is_dispatchable(
|
||||
db: &dyn HirDatabase,
|
||||
trait_: TraitId,
|
||||
func: FunctionId,
|
||||
sig: &Binders<CallableSig>,
|
||||
) -> bool {
|
||||
let Some(trait_self_idx) = trait_self_param_idx(db.upcast(), func.into()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// `self: Self` can't be dispatched on, but this is already considered object safe.
|
||||
// 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
|
||||
.skip_binders()
|
||||
.params()
|
||||
.first()
|
||||
.and_then(|receiver| receiver.bound_var(Interner))
|
||||
.is_some_and(|b| {
|
||||
b == BoundVar { debruijn: DebruijnIndex::INNERMOST, index: trait_self_idx }
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let placeholder_subst = generics(db.upcast(), func.into()).placeholder_subst(db);
|
||||
|
||||
let substituted_sig = sig.clone().substitute(Interner, &placeholder_subst);
|
||||
let Some(receiver_ty) = substituted_sig.params().first() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let krate = func.module(db.upcast()).krate();
|
||||
let traits = (
|
||||
db.lang_item(krate, LangItem::Unsize).and_then(|it| it.as_trait()),
|
||||
db.lang_item(krate, LangItem::DispatchFromDyn).and_then(|it| it.as_trait()),
|
||||
);
|
||||
let (Some(unsize_did), Some(dispatch_from_dyn_did)) = traits else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Type `U`
|
||||
let unsized_self_ty =
|
||||
TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::U32)).intern(Interner);
|
||||
// `Receiver[Self => U]`
|
||||
let Some(unsized_receiver_ty) = receiver_for_self_ty(db, func, unsized_self_ty.clone()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let self_ty = placeholder_subst.as_slice(Interner)[trait_self_idx].assert_ty_ref(Interner);
|
||||
let unsized_predicate = WhereClause::Implemented(TraitRef {
|
||||
trait_id: to_chalk_trait_id(unsize_did),
|
||||
substitution: Substitution::from_iter(Interner, [self_ty.clone(), unsized_self_ty.clone()]),
|
||||
});
|
||||
let trait_predicate = WhereClause::Implemented(TraitRef {
|
||||
trait_id: to_chalk_trait_id(trait_),
|
||||
substitution: Substitution::from_iter(
|
||||
Interner,
|
||||
std::iter::once(unsized_self_ty.clone().cast(Interner))
|
||||
.chain(placeholder_subst.iter(Interner).skip(1).cloned()),
|
||||
),
|
||||
});
|
||||
|
||||
let generic_predicates = &*db.generic_predicates(func.into());
|
||||
|
||||
let clauses = std::iter::once(unsized_predicate)
|
||||
.chain(std::iter::once(trait_predicate))
|
||||
.chain(generic_predicates.iter().map(|pred| {
|
||||
pred.clone().substitute(Interner, &placeholder_subst).into_value_and_skipped_binders().0
|
||||
}))
|
||||
.map(|pred| {
|
||||
pred.cast::<chalk_ir::ProgramClause<Interner>>(Interner).into_from_env_clause(Interner)
|
||||
});
|
||||
let env = chalk_ir::Environment::new(Interner).add_clauses(Interner, clauses);
|
||||
|
||||
let obligation = WhereClause::Implemented(TraitRef {
|
||||
trait_id: to_chalk_trait_id(dispatch_from_dyn_did),
|
||||
substitution: Substitution::from_iter(Interner, [receiver_ty.clone(), unsized_receiver_ty]),
|
||||
});
|
||||
let goal = GoalData::DomainGoal(chalk_ir::DomainGoal::Holds(obligation)).intern(Interner);
|
||||
|
||||
let in_env = chalk_ir::InEnvironment::new(&env, goal);
|
||||
|
||||
let mut table = chalk_solve::infer::InferenceTable::<Interner>::new();
|
||||
let canonicalized = table.canonicalize(Interner, in_env);
|
||||
let solution = db.trait_solve(krate, None, canonicalized.quantified);
|
||||
|
||||
matches!(solution, Some(Solution::Unique(_)))
|
||||
}
|
||||
|
||||
fn receiver_for_self_ty(db: &dyn HirDatabase, func: FunctionId, ty: Ty) -> Option<Ty> {
|
||||
let generics = generics(db.upcast(), func.into());
|
||||
let trait_self_idx = trait_self_param_idx(db.upcast(), func.into())?;
|
||||
let subst = generics.placeholder_subst(db);
|
||||
let subst = Substitution::from_iter(
|
||||
Interner,
|
||||
subst.iter(Interner).enumerate().map(|(idx, arg)| {
|
||||
if idx == trait_self_idx {
|
||||
ty.clone().cast(Interner)
|
||||
} else {
|
||||
arg.clone()
|
||||
}
|
||||
}),
|
||||
);
|
||||
let sig = callable_item_sig(db, func.into());
|
||||
let sig = sig.substitute(Interner, &subst);
|
||||
sig.params_and_return.first().cloned()
|
||||
}
|
||||
|
||||
fn contains_illegal_impl_trait_in_trait(
|
||||
db: &dyn HirDatabase,
|
||||
sig: &Binders<CallableSig>,
|
||||
) -> Option<MethodViolationCode> {
|
||||
struct OpaqueTypeCollector(FxHashSet<OpaqueTyId>);
|
||||
|
||||
impl TypeVisitor<Interner> for OpaqueTypeCollector {
|
||||
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> {
|
||||
if let TyKind::OpaqueType(opaque_ty_id, _) = ty.kind(Interner) {
|
||||
self.0.insert(*opaque_ty_id);
|
||||
}
|
||||
ty.super_visit_with(self.as_dyn(), outer_binder)
|
||||
}
|
||||
}
|
||||
|
||||
let ret = sig.skip_binders().ret();
|
||||
let mut visitor = OpaqueTypeCollector(FxHashSet::default());
|
||||
ret.visit_with(visitor.as_dyn(), DebruijnIndex::INNERMOST);
|
||||
|
||||
// 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.into());
|
||||
if matches!(impl_trait_id, ImplTraitId::ReturnTypeImplTrait(..)) {
|
||||
return Some(MethodViolationCode::ReferencesImplTraitInTrait);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
363
crates/hir-ty/src/object_safety/tests.rs
Normal file
363
crates/hir-ty/src/object_safety/tests.rs
Normal file
|
@ -0,0 +1,363 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use hir_def::db::DefDatabase;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use syntax::ToSmolStr;
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{object_safety::object_safety_with_callback, test_db::TestDB};
|
||||
|
||||
use super::{
|
||||
MethodViolationCode::{self, *},
|
||||
ObjectSafetyViolation,
|
||||
};
|
||||
|
||||
use ObjectSafetyViolationKind::*;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum ObjectSafetyViolationKind {
|
||||
SizedSelf,
|
||||
SelfReferential,
|
||||
Method(MethodViolationCode),
|
||||
AssocConst,
|
||||
GAT,
|
||||
HasNonSafeSuperTrait,
|
||||
}
|
||||
|
||||
fn check_object_safety<'a>(
|
||||
ra_fixture: &str,
|
||||
expected: impl IntoIterator<Item = (&'a str, Vec<ObjectSafetyViolationKind>)>,
|
||||
) {
|
||||
let mut expected: FxHashMap<_, _> =
|
||||
expected.into_iter().map(|(id, osvs)| (id, FxHashSet::from_iter(osvs))).collect();
|
||||
let (db, file_ids) = TestDB::with_many_files(ra_fixture);
|
||||
for (trait_id, name) in file_ids.into_iter().flat_map(|file_id| {
|
||||
let module_id = db.module_for_file(file_id);
|
||||
let def_map = module_id.def_map(&db);
|
||||
let scope = &def_map[module_id.local_id].scope;
|
||||
scope
|
||||
.declarations()
|
||||
.filter_map(|def| {
|
||||
if let hir_def::ModuleDefId::TraitId(trait_id) = def {
|
||||
let name =
|
||||
db.trait_data(trait_id).name.display_no_db(file_id.edition()).to_smolstr();
|
||||
Some((trait_id, name))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}) {
|
||||
let Some(expected) = expected.remove(name.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
let mut osvs = FxHashSet::default();
|
||||
object_safety_with_callback(&db, trait_id, &mut |osv| {
|
||||
osvs.insert(match osv {
|
||||
ObjectSafetyViolation::SizedSelf => SizedSelf,
|
||||
ObjectSafetyViolation::SelfReferential => SelfReferential,
|
||||
ObjectSafetyViolation::Method(_, mvc) => Method(mvc),
|
||||
ObjectSafetyViolation::AssocConst(_) => AssocConst,
|
||||
ObjectSafetyViolation::GAT(_) => GAT,
|
||||
ObjectSafetyViolation::HasNonSafeSuperTrait(_) => HasNonSafeSuperTrait,
|
||||
});
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
assert_eq!(osvs, expected, "Object safety violations for `{name}` do not match;");
|
||||
}
|
||||
|
||||
let remains: Vec<_> = expected.keys().collect();
|
||||
assert!(remains.is_empty(), "Following traits do not exist in the test fixture; {remains:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_bounds_can_reference_self() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: eq
|
||||
pub trait Foo {
|
||||
type X: PartialEq;
|
||||
type Y: PartialEq<Self::Y>;
|
||||
type Z: PartialEq<Self::Y>;
|
||||
}
|
||||
"#,
|
||||
[("Foo", vec![])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn associated_consts() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
trait Bar {
|
||||
const X: usize;
|
||||
}
|
||||
"#,
|
||||
[("Bar", vec![AssocConst])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounds_reference_self() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: eq
|
||||
trait X {
|
||||
type U: PartialEq<Self>;
|
||||
}
|
||||
"#,
|
||||
[("X", vec![SelfReferential])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn by_value_self() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Bar {
|
||||
fn bar(self);
|
||||
}
|
||||
|
||||
trait Baz {
|
||||
fn baz(self: Self);
|
||||
}
|
||||
|
||||
trait Quux {
|
||||
// Legal because of the where clause:
|
||||
fn baz(self: Self) where Self : Sized;
|
||||
}
|
||||
"#,
|
||||
[("Bar", vec![]), ("Baz", vec![]), ("Quux", vec![])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_methods() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Bar {
|
||||
fn bar<T>(&self, t: T);
|
||||
}
|
||||
|
||||
trait Quux {
|
||||
fn bar<T>(&self, t: T)
|
||||
where Self : Sized;
|
||||
}
|
||||
|
||||
trait Qax {
|
||||
fn bar<'a>(&self, t: &'a ());
|
||||
}
|
||||
"#,
|
||||
[("Bar", vec![Method(Generic)]), ("Quux", vec![]), ("Qax", vec![])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mentions_self() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Bar {
|
||||
fn bar(&self, x: &Self);
|
||||
}
|
||||
|
||||
trait Baz {
|
||||
fn baz(&self) -> Self;
|
||||
}
|
||||
|
||||
trait Quux {
|
||||
fn quux(&self, s: &Self) -> Self where Self : Sized;
|
||||
}
|
||||
"#,
|
||||
[
|
||||
("Bar", vec![Method(ReferencesSelfInput)]),
|
||||
("Baz", vec![Method(ReferencesSelfOutput)]),
|
||||
("Quux", vec![]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_static() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Foo {
|
||||
fn foo() {}
|
||||
}
|
||||
"#,
|
||||
[("Foo", vec![Method(StaticMethod)])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sized_self() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Bar: Sized {
|
||||
fn bar<T>(&self, t: T);
|
||||
}
|
||||
"#,
|
||||
[("Bar", vec![SizedSelf])],
|
||||
);
|
||||
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Bar
|
||||
where Self : Sized
|
||||
{
|
||||
fn bar<T>(&self, t: T);
|
||||
}
|
||||
"#,
|
||||
[("Bar", vec![SizedSelf])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supertrait_gat() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait GatTrait {
|
||||
type Gat<T>;
|
||||
}
|
||||
|
||||
trait SuperTrait<T>: GatTrait {}
|
||||
"#,
|
||||
[("GatTrait", vec![GAT]), ("SuperTrait", vec![HasNonSafeSuperTrait])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supertrait_mentions_self() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Bar<T> {
|
||||
fn bar(&self, x: &T);
|
||||
}
|
||||
|
||||
trait Baz : Bar<Self> {
|
||||
}
|
||||
"#,
|
||||
[("Bar", vec![]), ("Baz", vec![SizedSelf, SelfReferential])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_issue_19538() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: dispatch_from_dyn
|
||||
trait Foo {
|
||||
fn foo<T>(&self, val: T);
|
||||
}
|
||||
|
||||
trait Bar: Foo {}
|
||||
"#,
|
||||
[("Foo", vec![Method(Generic)]), ("Bar", vec![HasNonSafeSuperTrait])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_issue_22040() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: fmt, eq, dispatch_from_dyn
|
||||
use core::fmt::Debug;
|
||||
|
||||
trait Expr: Debug + PartialEq {
|
||||
fn print_element_count(&self);
|
||||
}
|
||||
"#,
|
||||
[("Expr", vec![SelfReferential])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_issue_102762() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: future, send, sync, dispatch_from_dyn, deref
|
||||
use core::pin::Pin;
|
||||
|
||||
struct Box<T: ?Sized> {}
|
||||
impl<T: ?Sized> core::ops::Deref for Box<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<Box<U>> for Box<T> {}
|
||||
|
||||
struct Vec<T> {}
|
||||
|
||||
pub trait Fetcher: Send + Sync {
|
||||
fn get<'a>(self: &'a Box<Self>) -> Pin<Box<dyn Future<Output = Vec<u8>> + 'a>>
|
||||
where
|
||||
Self: Sync,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
[("Fetcher", vec![Method(UndispatchableReceiver)])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_issue_102933() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: future, dispatch_from_dyn, deref
|
||||
use core::future::Future;
|
||||
|
||||
struct Box<T: ?Sized> {}
|
||||
impl<T: ?Sized> core::ops::Deref for Box<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<Box<U>> for Box<T> {}
|
||||
|
||||
pub trait Service {
|
||||
type Response;
|
||||
type Future: Future<Output = Self::Response>;
|
||||
}
|
||||
|
||||
pub trait A1: Service<Response = i32> {}
|
||||
|
||||
pub trait A2: Service<Future = Box<dyn Future<Output = i32>>> + A1 {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
|
||||
pub trait B1: Service<Future = Box<dyn Future<Output = i32>>> {}
|
||||
|
||||
pub trait B2: Service<Response = i32> + B1 {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
"#,
|
||||
[("A2", vec![]), ("B2", vec![])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_issue_106247() {
|
||||
check_object_safety(
|
||||
r#"
|
||||
//- minicore: sync, dispatch_from_dyn
|
||||
pub trait Trait {
|
||||
fn method(&self) where Self: Sync;
|
||||
}
|
||||
"#,
|
||||
[("Trait", vec![])],
|
||||
);
|
||||
}
|
|
@ -1065,7 +1065,7 @@ fn test() {
|
|||
fn bare_dyn_trait_binders_9639() {
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
//- minicore: fn, coerce_unsized
|
||||
//- minicore: fn, coerce_unsized, dispatch_from_dyn
|
||||
fn infix_parse<T, S>(_state: S, _level_code: &Fn(S)) -> T {
|
||||
loop {}
|
||||
}
|
||||
|
|
|
@ -1448,14 +1448,20 @@ fn foo<X>() -> Foo<impl Future<Output = ()>> {
|
|||
fn dyn_trait() {
|
||||
check_infer(
|
||||
r#"
|
||||
//- minicore: sized
|
||||
//- minicore: deref, dispatch_from_dyn
|
||||
trait Trait<T> {
|
||||
fn foo(&self) -> T;
|
||||
fn foo2(&self) -> i64;
|
||||
}
|
||||
fn bar() -> dyn Trait<u64> {}
|
||||
|
||||
fn test(x: dyn Trait<u64>, y: &dyn Trait<u64>) {
|
||||
struct Box<T: ?Sized> {}
|
||||
impl<T: ?Sized> core::ops::Deref for Box<T> {
|
||||
type Target = T;
|
||||
}
|
||||
|
||||
fn bar() -> Box<dyn Trait<u64>> {}
|
||||
|
||||
fn test(x: Box<dyn Trait<u64>>, y: &dyn Trait<u64>) {
|
||||
x;
|
||||
y;
|
||||
let z = bar();
|
||||
|
@ -1469,27 +1475,27 @@ fn test(x: dyn Trait<u64>, y: &dyn Trait<u64>) {
|
|||
expect![[r#"
|
||||
29..33 'self': &'? Self
|
||||
54..58 'self': &'? Self
|
||||
97..99 '{}': dyn Trait<u64>
|
||||
109..110 'x': dyn Trait<u64>
|
||||
128..129 'y': &'? dyn Trait<u64>
|
||||
148..265 '{ ...2(); }': ()
|
||||
154..155 'x': dyn Trait<u64>
|
||||
161..162 'y': &'? dyn Trait<u64>
|
||||
172..173 'z': dyn Trait<u64>
|
||||
176..179 'bar': fn bar() -> dyn Trait<u64>
|
||||
176..181 'bar()': dyn Trait<u64>
|
||||
187..188 'x': dyn Trait<u64>
|
||||
187..194 'x.foo()': u64
|
||||
200..201 'y': &'? dyn Trait<u64>
|
||||
200..207 'y.foo()': u64
|
||||
213..214 'z': dyn Trait<u64>
|
||||
213..220 'z.foo()': u64
|
||||
226..227 'x': dyn Trait<u64>
|
||||
226..234 'x.foo2()': i64
|
||||
240..241 'y': &'? dyn Trait<u64>
|
||||
240..248 'y.foo2()': i64
|
||||
254..255 'z': dyn Trait<u64>
|
||||
254..262 'z.foo2()': i64
|
||||
198..200 '{}': Box<dyn Trait<u64>>
|
||||
210..211 'x': Box<dyn Trait<u64>>
|
||||
234..235 'y': &'? dyn Trait<u64>
|
||||
254..371 '{ ...2(); }': ()
|
||||
260..261 'x': Box<dyn Trait<u64>>
|
||||
267..268 'y': &'? dyn Trait<u64>
|
||||
278..279 'z': Box<dyn Trait<u64>>
|
||||
282..285 'bar': fn bar() -> Box<dyn Trait<u64>>
|
||||
282..287 'bar()': Box<dyn Trait<u64>>
|
||||
293..294 'x': Box<dyn Trait<u64>>
|
||||
293..300 'x.foo()': u64
|
||||
306..307 'y': &'? dyn Trait<u64>
|
||||
306..313 'y.foo()': u64
|
||||
319..320 'z': Box<dyn Trait<u64>>
|
||||
319..326 'z.foo()': u64
|
||||
332..333 'x': Box<dyn Trait<u64>>
|
||||
332..340 'x.foo2()': i64
|
||||
346..347 'y': &'? dyn Trait<u64>
|
||||
346..354 'y.foo2()': i64
|
||||
360..361 'z': Box<dyn Trait<u64>>
|
||||
360..368 'z.foo2()': i64
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -1534,7 +1540,7 @@ fn test(s: S<u32, i32>) {
|
|||
fn dyn_trait_bare() {
|
||||
check_infer(
|
||||
r#"
|
||||
//- minicore: sized
|
||||
//- minicore: sized, dispatch_from_dyn
|
||||
trait Trait {
|
||||
fn foo(&self) -> u64;
|
||||
}
|
||||
|
@ -1570,7 +1576,7 @@ fn test(x: Trait, y: &Trait) -> u64 {
|
|||
|
||||
check_infer_with_mismatches(
|
||||
r#"
|
||||
//- minicore: fn, coerce_unsized
|
||||
//- minicore: fn, coerce_unsized, dispatch_from_dyn
|
||||
struct S;
|
||||
impl S {
|
||||
fn foo(&self) {}
|
||||
|
@ -3106,7 +3112,7 @@ fn dyn_fn_param_informs_call_site_closure_signature() {
|
|||
cov_mark::check!(dyn_fn_param_informs_call_site_closure_signature);
|
||||
check_types(
|
||||
r#"
|
||||
//- minicore: fn, coerce_unsized
|
||||
//- minicore: fn, coerce_unsized, dispatch_from_dyn
|
||||
struct S;
|
||||
impl S {
|
||||
fn inherent(&self) -> u8 { 0 }
|
||||
|
@ -3151,7 +3157,7 @@ fn infer_box_fn_arg() {
|
|||
// The type mismatch is because we don't define Unsize and CoerceUnsized
|
||||
check_infer_with_mismatches(
|
||||
r#"
|
||||
//- minicore: fn, deref, option
|
||||
//- minicore: fn, deref, option, dispatch_from_dyn
|
||||
#[lang = "owned_box"]
|
||||
pub struct Box<T: ?Sized> {
|
||||
inner: *mut T,
|
||||
|
|
|
@ -66,7 +66,7 @@ use hir_ty::{
|
|||
diagnostics::BodyValidationDiagnostic,
|
||||
error_lifetime, known_const_to_ast,
|
||||
layout::{Layout as TyLayout, RustcEnumVariantIdx, RustcFieldIdx, TagEncoding},
|
||||
method_resolution::{self},
|
||||
method_resolution,
|
||||
mir::{interpret_mir, MutBorrowKind},
|
||||
primitive::UintTy,
|
||||
traits::FnTrait,
|
||||
|
@ -145,6 +145,7 @@ pub use {
|
|||
display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
|
||||
layout::LayoutError,
|
||||
mir::{MirEvalError, MirLowerError},
|
||||
object_safety::{MethodViolationCode, ObjectSafetyViolation},
|
||||
FnAbi, PointerCast, Safety,
|
||||
},
|
||||
// FIXME: Properly encapsulate mir
|
||||
|
@ -2641,6 +2642,10 @@ impl Trait {
|
|||
.count()
|
||||
}
|
||||
|
||||
pub fn object_safety(&self, db: &dyn HirDatabase) -> Option<ObjectSafetyViolation> {
|
||||
hir_ty::object_safety::object_safety(db, self.id)
|
||||
}
|
||||
|
||||
fn all_macro_calls(&self, db: &dyn HirDatabase) -> Box<[(AstId<ast::Item>, MacroCallId)]> {
|
||||
db.trait_data(self.id)
|
||||
.macro_calls
|
||||
|
|
|
@ -84,7 +84,7 @@ fn foo() {
|
|||
fn replace_filter_map_next_dont_work_for_not_sized_issues_16596() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- minicore: iterators
|
||||
//- minicore: iterators, dispatch_from_dyn
|
||||
fn foo() {
|
||||
let mut j = [0].into_iter();
|
||||
let i: &mut dyn Iterator<Item = i32> = &mut j;
|
||||
|
|
|
@ -4,7 +4,8 @@ use std::{mem, ops::Not};
|
|||
use either::Either;
|
||||
use hir::{
|
||||
Adt, AsAssocItem, AsExternAssocItem, CaptureKind, HasCrate, HasSource, HirDisplay, Layout,
|
||||
LayoutError, Name, Semantics, Trait, Type, TypeInfo,
|
||||
LayoutError, MethodViolationCode, Name, ObjectSafetyViolation, Semantics, Trait, Type,
|
||||
TypeInfo,
|
||||
};
|
||||
use ide_db::{
|
||||
base_db::SourceDatabase,
|
||||
|
@ -526,6 +527,14 @@ pub(super) fn definition(
|
|||
_ => None,
|
||||
};
|
||||
|
||||
let object_safety_info = if let Definition::Trait(it) = def {
|
||||
let mut object_safety_info = String::new();
|
||||
render_object_safety(db, &mut object_safety_info, it.object_safety(db));
|
||||
Some(object_safety_info)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut desc = String::new();
|
||||
if let Some(notable_traits) = render_notable_trait_comment(db, notable_traits, edition) {
|
||||
desc.push_str(¬able_traits);
|
||||
|
@ -535,6 +544,10 @@ pub(super) fn definition(
|
|||
desc.push_str(&layout_info);
|
||||
desc.push('\n');
|
||||
}
|
||||
if let Some(object_safety_info) = object_safety_info {
|
||||
desc.push_str(&object_safety_info);
|
||||
desc.push('\n');
|
||||
}
|
||||
desc.push_str(&label);
|
||||
if let Some(value) = value {
|
||||
desc.push_str(" = ");
|
||||
|
@ -964,3 +977,62 @@ fn keyword_hints(
|
|||
_ => KeywordHint::new(token.text().to_owned(), format!("{}_keyword", token.text())),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_object_safety(
|
||||
db: &RootDatabase,
|
||||
buf: &mut String,
|
||||
safety: Option<ObjectSafetyViolation>,
|
||||
) {
|
||||
let Some(osv) = safety else {
|
||||
buf.push_str("// Object Safety: Yes");
|
||||
return;
|
||||
};
|
||||
buf.push_str("// Object Safety: No\n// - Reason: ");
|
||||
match osv {
|
||||
ObjectSafetyViolation::SizedSelf => {
|
||||
buf.push_str("has a `Self: Sized` bound");
|
||||
}
|
||||
ObjectSafetyViolation::SelfReferential => {
|
||||
buf.push_str("has a bound that references `Self`");
|
||||
}
|
||||
ObjectSafetyViolation::Method(func, mvc) => {
|
||||
let name = hir::Function::from(func).name(db);
|
||||
format_to!(
|
||||
buf,
|
||||
"has a method `{}` that is non dispatchable because of:\n// - ",
|
||||
name.as_str()
|
||||
);
|
||||
let desc = match mvc {
|
||||
MethodViolationCode::StaticMethod => "missing a receiver",
|
||||
MethodViolationCode::ReferencesSelfInput => "a parameter references `Self`",
|
||||
MethodViolationCode::ReferencesSelfOutput => "the return type references `Self`",
|
||||
MethodViolationCode::ReferencesImplTraitInTrait => {
|
||||
"the return type contains `impl Trait`"
|
||||
}
|
||||
MethodViolationCode::AsyncFn => "being async",
|
||||
MethodViolationCode::WhereClauseReferencesSelf => {
|
||||
"a where clause references `Self`"
|
||||
}
|
||||
MethodViolationCode::Generic => "a non-lifetime generic parameter",
|
||||
MethodViolationCode::UndispatchableReceiver => "a non-dispatchable receiver type",
|
||||
};
|
||||
buf.push_str(desc);
|
||||
}
|
||||
ObjectSafetyViolation::AssocConst(const_) => {
|
||||
let name = hir::Const::from(const_).name(db);
|
||||
if let Some(name) = name {
|
||||
format_to!(buf, "has an associated constant `{}`", name.as_str());
|
||||
} else {
|
||||
buf.push_str("has an associated constant");
|
||||
}
|
||||
}
|
||||
ObjectSafetyViolation::GAT(alias) => {
|
||||
let name = hir::TypeAlias::from(alias).name(db);
|
||||
format_to!(buf, "has a generic associated type `{}`", name.as_str());
|
||||
}
|
||||
ObjectSafetyViolation::HasNonSafeSuperTrait(super_trait) => {
|
||||
let name = hir::Trait::from(super_trait).name(db);
|
||||
format_to!(buf, "has a object unsafe supertrait `{}`", name.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5442,7 +5442,7 @@ const FOO$0: Option<&i32> = Some(2).as_ref();
|
|||
fn hover_const_eval_dyn_trait() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: fmt, coerce_unsized, builtin_impls
|
||||
//- minicore: fmt, coerce_unsized, builtin_impls, dispatch_from_dyn
|
||||
use core::fmt::Debug;
|
||||
|
||||
const FOO$0: &dyn Debug = &2i32;
|
||||
|
@ -7107,6 +7107,7 @@ impl T$0 for () {}
|
|||
```
|
||||
|
||||
```rust
|
||||
// Object Safety: Yes
|
||||
trait T {}
|
||||
```
|
||||
"#]],
|
||||
|
@ -7126,6 +7127,7 @@ impl T$0 for () {}
|
|||
```
|
||||
|
||||
```rust
|
||||
// Object Safety: Yes
|
||||
trait T {}
|
||||
```
|
||||
"#]],
|
||||
|
@ -7149,6 +7151,9 @@ impl T$0 for () {}
|
|||
```
|
||||
|
||||
```rust
|
||||
// Object Safety: No
|
||||
// - Reason: has a method `func` that is non dispatchable because of:
|
||||
// - missing a receiver
|
||||
trait T { /* … */ }
|
||||
```
|
||||
"#]],
|
||||
|
@ -7172,6 +7177,9 @@ impl T$0 for () {}
|
|||
```
|
||||
|
||||
```rust
|
||||
// Object Safety: No
|
||||
// - Reason: has a method `func` that is non dispatchable because of:
|
||||
// - missing a receiver
|
||||
trait T {
|
||||
fn func();
|
||||
const FLAG: i32;
|
||||
|
@ -7199,6 +7207,9 @@ impl T$0 for () {}
|
|||
```
|
||||
|
||||
```rust
|
||||
// Object Safety: No
|
||||
// - Reason: has a method `func` that is non dispatchable because of:
|
||||
// - missing a receiver
|
||||
trait T {
|
||||
fn func();
|
||||
const FLAG: i32;
|
||||
|
@ -7226,6 +7237,9 @@ impl T$0 for () {}
|
|||
```
|
||||
|
||||
```rust
|
||||
// Object Safety: No
|
||||
// - Reason: has a method `func` that is non dispatchable because of:
|
||||
// - missing a receiver
|
||||
trait T {
|
||||
fn func();
|
||||
const FLAG: i32;
|
||||
|
@ -8465,8 +8479,8 @@ impl Iterator for S {
|
|||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 7800..8042,
|
||||
focus_range: 7865..7871,
|
||||
full_range: 7801..8043,
|
||||
focus_range: 7866..7872,
|
||||
name: "Future",
|
||||
kind: Trait,
|
||||
container_name: "future",
|
||||
|
@ -8479,8 +8493,8 @@ impl Iterator for S {
|
|||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 8672..9171,
|
||||
focus_range: 8749..8757,
|
||||
full_range: 8673..9172,
|
||||
focus_range: 8750..8758,
|
||||
name: "Iterator",
|
||||
kind: Trait,
|
||||
container_name: "iterator",
|
||||
|
|
|
@ -288,7 +288,7 @@ mod tests {
|
|||
check_with_config(
|
||||
InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
|
||||
r#"
|
||||
//- minicore: coerce_unsized, fn, eq, index
|
||||
//- minicore: coerce_unsized, fn, eq, index, dispatch_from_dyn
|
||||
fn main() {
|
||||
let _: u32 = loop {};
|
||||
//^^^^^^^<never-to-any>
|
||||
|
@ -428,7 +428,7 @@ impl core::ops::IndexMut for Struct {}
|
|||
..DISABLED_CONFIG
|
||||
},
|
||||
r#"
|
||||
//- minicore: coerce_unsized, fn, eq, index
|
||||
//- minicore: coerce_unsized, fn, eq, index, dispatch_from_dyn
|
||||
fn main() {
|
||||
|
||||
Struct.consume();
|
||||
|
|
|
@ -242,6 +242,7 @@ define_symbols! {
|
|||
future_output,
|
||||
Future,
|
||||
ge,
|
||||
generic_associated_type_extended,
|
||||
get_context,
|
||||
global_allocator,
|
||||
global_asm,
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
//! from: sized
|
||||
//! future: pin
|
||||
//! coroutine: pin
|
||||
//! dispatch_from_dyn: unsize, pin
|
||||
//! hash:
|
||||
//! include:
|
||||
//! index: sized
|
||||
|
@ -822,6 +823,24 @@ pub mod ops {
|
|||
}
|
||||
pub use self::coroutine::{Coroutine, CoroutineState};
|
||||
// endregion:coroutine
|
||||
|
||||
// region:dispatch_from_dyn
|
||||
mod dispatch_from_dyn {
|
||||
use crate::marker::Unsize;
|
||||
|
||||
#[lang = "dispatch_from_dyn"]
|
||||
pub trait DispatchFromDyn<T> {}
|
||||
|
||||
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<&'a U> for &'a T {}
|
||||
|
||||
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<&'a mut U> for &'a mut T {}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<*const U> for *const T {}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<*mut U> for *mut T {}
|
||||
}
|
||||
pub use self::dispatch_from_dyn::DispatchFromDyn;
|
||||
// endregion:dispatch_from_dyn
|
||||
}
|
||||
|
||||
// region:eq
|
||||
|
@ -1183,6 +1202,12 @@ pub mod pin {
|
|||
}
|
||||
}
|
||||
// endregion:deref
|
||||
// region:dispatch_from_dyn
|
||||
impl<Ptr, U> crate::ops::DispatchFromDyn<Pin<U>> for Pin<Ptr> where
|
||||
Ptr: crate::ops::DispatchFromDyn<U>
|
||||
{
|
||||
}
|
||||
// endregion:dispatch_from_dyn
|
||||
}
|
||||
// endregion:pin
|
||||
|
||||
|
@ -1309,7 +1334,10 @@ pub mod iter {
|
|||
self
|
||||
}
|
||||
// region:iterators
|
||||
fn take(self, n: usize) -> crate::iter::Take<Self> {
|
||||
fn take(self, n: usize) -> crate::iter::Take<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
fn filter_map<B, F>(self, _f: F) -> crate::iter::FilterMap<Self, F>
|
||||
|
|
Loading…
Reference in a new issue