-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix too restrictive checks on Drop impls #67059
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,15 @@ use crate::check::regionck::RegionCtxt; | |
|
||
use crate::hir; | ||
use crate::hir::def_id::DefId; | ||
use crate::util::common::ErrorReported; | ||
use rustc::infer::outlives::env::OutlivesEnvironment; | ||
use rustc::infer::{InferOk, SuppressRegionErrors}; | ||
use rustc::middle::region; | ||
use rustc::traits::{ObligationCause, TraitEngine, TraitEngineExt}; | ||
use rustc::ty::error::TypeError; | ||
use rustc::ty::relate::{Relate, RelateResult, TypeRelation}; | ||
use rustc::ty::subst::{Subst, SubstsRef}; | ||
use rustc::ty::{self, Ty, TyCtxt}; | ||
use crate::util::common::ErrorReported; | ||
use rustc::ty::{self, Predicate, Ty, TyCtxt}; | ||
|
||
use syntax_pos::Span; | ||
|
||
|
@@ -54,8 +56,10 @@ pub fn check_drop_impl(tcx: TyCtxt<'_>, drop_impl_did: DefId) -> Result<(), Erro | |
// already checked by coherence, but compilation may | ||
// not have been terminated. | ||
let span = tcx.def_span(drop_impl_did); | ||
tcx.sess.delay_span_bug(span, | ||
&format!("should have been rejected by coherence check: {}", dtor_self_type)); | ||
tcx.sess.delay_span_bug( | ||
span, | ||
&format!("should have been rejected by coherence check: {}", dtor_self_type), | ||
); | ||
Err(ErrorReported) | ||
} | ||
} | ||
|
@@ -83,10 +87,7 @@ fn ensure_drop_params_and_item_params_correspond<'tcx>( | |
let fresh_impl_self_ty = drop_impl_ty.subst(tcx, fresh_impl_substs); | ||
|
||
let cause = &ObligationCause::misc(drop_impl_span, drop_impl_hir_id); | ||
match infcx | ||
.at(cause, impl_param_env) | ||
.eq(named_type, fresh_impl_self_ty) | ||
{ | ||
match infcx.at(cause, impl_param_env).eq(named_type, fresh_impl_self_ty) { | ||
Ok(InferOk { obligations, .. }) => { | ||
fulfillment_cx.register_predicate_obligations(infcx, obligations); | ||
} | ||
|
@@ -97,12 +98,13 @@ fn ensure_drop_params_and_item_params_correspond<'tcx>( | |
drop_impl_span, | ||
E0366, | ||
"Implementations of Drop cannot be specialized" | ||
).span_note( | ||
) | ||
.span_note( | ||
item_span, | ||
"Use same sequence of generic type and region \ | ||
parameters that is on the struct/enum definition", | ||
) | ||
.emit(); | ||
.emit(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. follow-up to other note about formatting changes: while I stand by my claim that I prefer formatting changes to be segregated into their own commits, I will say that if the formatting "fixes" were solely to formatting "bugs" that are this egregious, I myself would probably leave them in the same commit too. |
||
return Err(ErrorReported); | ||
} | ||
} | ||
|
@@ -192,6 +194,8 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( | |
let assumptions_in_impl_context = generic_assumptions.instantiate(tcx, &self_to_impl_substs); | ||
let assumptions_in_impl_context = assumptions_in_impl_context.predicates; | ||
|
||
let self_param_env = tcx.param_env(self_type_did); | ||
|
||
// An earlier version of this code attempted to do this checking | ||
// via the traits::fulfill machinery. However, it ran into trouble | ||
// since the fulfill machinery merely turns outlives-predicates | ||
|
@@ -205,27 +209,49 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( | |
// to take on a structure that is roughly an alpha-renaming of | ||
// the generic parameters of the item definition.) | ||
|
||
// This path now just checks *all* predicates via the direct | ||
// lookup, rather than using fulfill machinery. | ||
// This path now just checks *all* predicates via an instantiation of | ||
// the `SimpleEqRelation`, which simply forwards to the `relate` machinery | ||
// after taking care of anonymizing late bound regions. | ||
// | ||
// However, it may be more efficient in the future to batch | ||
// the analysis together via the fulfill , rather than the | ||
// repeated `contains` calls. | ||
// the analysis together via the fulfill (see comment above regarding | ||
// the usage of the fulfill machinery), rather than the | ||
// repeated `.iter().any(..)` calls. | ||
|
||
if !assumptions_in_impl_context.contains(&predicate) { | ||
// This closure is a more robust way to check `Predicate` equality | ||
// than simple `==` checks (which were the previous implementation). | ||
// It relies on `ty::relate` for `TraitPredicate` and `ProjectionPredicate` | ||
// (which implement the Relate trait), while delegating on simple equality | ||
// for the other `Predicate`. | ||
// This implementation solves (Issue #59497) and (Issue #58311). | ||
// It is unclear to me at the moment whether the approach based on `relate` | ||
// could be extended easily also to the other `Predicate`. | ||
let predicate_matches_closure = |p: &'_ Predicate<'tcx>| { | ||
let mut relator: SimpleEqRelation<'tcx> = SimpleEqRelation::new(tcx, self_param_env); | ||
match (predicate, p) { | ||
(Predicate::Trait(a), Predicate::Trait(b)) => relator.relate(a, b).is_ok(), | ||
(Predicate::Projection(a), Predicate::Projection(b)) => { | ||
relator.relate(a, b).is_ok() | ||
} | ||
_ => predicate == p, | ||
} | ||
}; | ||
|
||
if !assumptions_in_impl_context.iter().any(predicate_matches_closure) { | ||
let item_span = tcx.hir().span(self_type_hir_id); | ||
struct_span_err!( | ||
tcx.sess, | ||
drop_impl_span, | ||
E0367, | ||
"The requirement `{}` is added only by the Drop impl.", | ||
predicate | ||
).span_note( | ||
) | ||
.span_note( | ||
item_span, | ||
"The same requirement must be part of \ | ||
the struct/enum definition", | ||
) | ||
.emit(); | ||
.emit(); | ||
result = Err(ErrorReported); | ||
} | ||
} | ||
|
@@ -251,3 +277,99 @@ crate fn check_drop_obligations<'a, 'tcx>( | |
|
||
Ok(()) | ||
} | ||
|
||
// This is an implementation of the TypeRelation trait with the | ||
// aim of simply comparing for equality (without side-effects). | ||
// It is not intended to be used anywhere else other than here. | ||
crate struct SimpleEqRelation<'tcx> { | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
} | ||
|
||
impl<'tcx> SimpleEqRelation<'tcx> { | ||
fn new(tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> SimpleEqRelation<'tcx> { | ||
SimpleEqRelation { tcx, param_env } | ||
} | ||
} | ||
|
||
impl TypeRelation<'tcx> for SimpleEqRelation<'tcx> { | ||
fn tcx(&self) -> TyCtxt<'tcx> { | ||
self.tcx | ||
} | ||
|
||
fn param_env(&self) -> ty::ParamEnv<'tcx> { | ||
self.param_env | ||
} | ||
|
||
fn tag(&self) -> &'static str { | ||
"dropck::SimpleEqRelation" | ||
} | ||
|
||
fn a_is_expected(&self) -> bool { | ||
true | ||
} | ||
|
||
fn relate_with_variance<T: Relate<'tcx>>( | ||
&mut self, | ||
_: ty::Variance, | ||
a: &T, | ||
b: &T, | ||
) -> RelateResult<'tcx, T> { | ||
// Here we ignore variance because we require drop impl's types | ||
// to be *exactly* the same as to the ones in the struct definition. | ||
self.relate(a, b) | ||
} | ||
|
||
fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { | ||
debug!("SimpleEqRelation::tys(a={:?}, b={:?})", a, b); | ||
ty::relate::super_relate_tys(self, a, b) | ||
} | ||
|
||
fn regions( | ||
&mut self, | ||
a: ty::Region<'tcx>, | ||
b: ty::Region<'tcx>, | ||
) -> RelateResult<'tcx, ty::Region<'tcx>> { | ||
debug!("SimpleEqRelation::regions(a={:?}, b={:?})", a, b); | ||
|
||
// We can just equate the regions because LBRs have been | ||
// already anonymized. | ||
if a == b { | ||
Ok(a) | ||
} else { | ||
// I'm not sure is this `TypeError` is the right one, but | ||
// it should not matter as it won't be checked (the dropck | ||
// will emit its own, more informative and higher-level errors | ||
// in case anything goes wrong). | ||
Err(TypeError::RegionsPlaceholderMismatch) | ||
} | ||
} | ||
|
||
fn consts( | ||
&mut self, | ||
a: &'tcx ty::Const<'tcx>, | ||
b: &'tcx ty::Const<'tcx>, | ||
) -> RelateResult<'tcx, &'tcx ty::Const<'tcx>> { | ||
debug!("SimpleEqRelation::consts(a={:?}, b={:?})", a, b); | ||
ty::relate::super_relate_consts(self, a, b) | ||
} | ||
|
||
fn binders<T>( | ||
&mut self, | ||
a: &ty::Binder<T>, | ||
b: &ty::Binder<T>, | ||
) -> RelateResult<'tcx, ty::Binder<T>> | ||
where | ||
T: Relate<'tcx>, | ||
{ | ||
debug!("SimpleEqRelation::binders({:?}: {:?}", a, b); | ||
|
||
// Anonymizing the LBRs is necessary to solve (Issue #59497). | ||
// After we do so, it should be totally fine to skip the binders. | ||
let anon_a = self.tcx.anonymize_late_bound_regions(a); | ||
let anon_b = self.tcx.anonymize_late_bound_regions(b); | ||
self.relate(anon_a.skip_binder(), anon_b.skip_binder())?; | ||
|
||
Ok(a.clone()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// run-pass | ||
//! Regression test for #58311, regarding the usage of Fn types in drop impls | ||
|
||
// All of this Drop impls should compile. | ||
|
||
#[allow(dead_code)] | ||
struct S<F: Fn() -> [u8; 1]>(F); | ||
|
||
impl<F: Fn() -> [u8; 1]> Drop for S<F> { | ||
fn drop(&mut self) {} | ||
} | ||
|
||
#[allow(dead_code)] | ||
struct P<A, F: FnOnce() -> [A; 10]>(F); | ||
|
||
impl<A, F: FnOnce() -> [A; 10]> Drop for P<A, F> { | ||
fn drop(&mut self) {} | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// run-pass | ||
//! Regression test for #34426, regarding HRTB in drop impls | ||
|
||
// All of this Drop impls should compile. | ||
|
||
pub trait Lifetime<'a> {} | ||
impl<'a> Lifetime<'a> for i32 {} | ||
|
||
#[allow(dead_code)] | ||
struct Foo<L> | ||
where | ||
for<'a> L: Lifetime<'a>, | ||
{ | ||
l: L, | ||
} | ||
|
||
impl<L> Drop for Foo<L> | ||
where | ||
for<'a> L: Lifetime<'a>, | ||
{ | ||
fn drop(&mut self) {} | ||
} | ||
|
||
#[allow(dead_code)] | ||
struct Foo2<L> | ||
where | ||
for<'a> L: Lifetime<'a>, | ||
{ | ||
l: L, | ||
} | ||
|
||
impl<T: for<'a> Lifetime<'a>> Drop for Foo2<T> | ||
where | ||
for<'x> T: Lifetime<'x>, | ||
{ | ||
fn drop(&mut self) {} | ||
} | ||
|
||
pub trait Lifetime2<'a, 'b> {} | ||
impl<'a, 'b> Lifetime2<'a, 'b> for i32 {} | ||
|
||
#[allow(dead_code)] | ||
struct Bar<L> | ||
where | ||
for<'a, 'b> L: Lifetime2<'a, 'b>, | ||
{ | ||
l: L, | ||
} | ||
|
||
impl<L> Drop for Bar<L> | ||
where | ||
for<'a, 'b> L: Lifetime2<'a, 'b>, | ||
{ | ||
fn drop(&mut self) {} | ||
} | ||
|
||
#[allow(dead_code)] | ||
struct FnHolder<T: for<'a> Fn(&'a T, dyn for<'b> Lifetime2<'a, 'b>) -> u8>(T); | ||
|
||
impl<T: for<'a> Fn(&'a T, dyn for<'b> Lifetime2<'a, 'b>) -> u8> Drop for FnHolder<T> { | ||
fn drop(&mut self) {} | ||
} | ||
|
||
fn main() { | ||
let _foo = Foo { l: 0 }; | ||
|
||
let _bar = Bar { l: 0 }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please try to avoid making pure-formatting changes in the same commit that has effectual semantic changes.
(If the reformatting is being injected by e.g. running
rustfmt
, then try to do those runs in a separate commit, please.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(the reason I'm asking for it to be in a separate commit is that it eases review: It allows the reviewer to just quickly skim the commits that are formatting fixes, while diving into the commits that have the real meat!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, ticking the "hide whitespace changes" checkbox usually helps a bit with this when reviewing.