Skip to content
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

Merged
merged 3 commits into from
Dec 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 139 additions & 17 deletions src/librustc_typeck/check/dropck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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
Copy link
Member

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.)

Copy link
Member

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!)

Copy link
Contributor

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.

.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);
}
Expand All @@ -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();
Copy link
Member

@pnkfelix pnkfelix Dec 11, 2019

Choose a reason for hiding this comment

The 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);
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
Expand All @@ -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())
}
}
20 changes: 20 additions & 0 deletions src/test/ui/dropck/dropck_fn_type.rs
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() {}
68 changes: 68 additions & 0 deletions src/test/ui/dropck/dropck_traits.rs
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 };
}