Rollup merge of #73452 - matthewjasper:auto-rec, r=nikomatsakis
Unify region variables when projecting associated types This is required to avoid cycles when evaluating auto trait predicates. Notably, this is required to be able add Chalk types to `CtxtInterners` for `cfg(parallel_compiler)`. r? @nikomatsakis
This commit is contained in:
commit
61f8c3ef27
@ -314,18 +314,19 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for Canonicalizer<'cx, 'tcx> {
|
||||
}
|
||||
|
||||
ty::ReVar(vid) => {
|
||||
let r = self
|
||||
let resolved_vid = self
|
||||
.infcx
|
||||
.unwrap()
|
||||
.inner
|
||||
.borrow_mut()
|
||||
.unwrap_region_constraints()
|
||||
.opportunistic_resolve_var(self.tcx, vid);
|
||||
.opportunistic_resolve_var(vid);
|
||||
debug!(
|
||||
"canonical: region var found with vid {:?}, \
|
||||
opportunistically resolved to {:?}",
|
||||
vid, r
|
||||
);
|
||||
let r = self.tcx.reuse_or_mk_region(r, ty::ReVar(resolved_vid));
|
||||
self.canonicalize_region_mode.canonicalize_free_region(self, r)
|
||||
}
|
||||
|
||||
|
@ -50,10 +50,10 @@ pub struct RegionConstraintStorage<'tcx> {
|
||||
/// R1 <= R2 and R2 <= R1 and (b) we unify the two regions in this
|
||||
/// table. You can then call `opportunistic_resolve_var` early
|
||||
/// which will map R1 and R2 to some common region (i.e., either
|
||||
/// R1 or R2). This is important when dropck and other such code
|
||||
/// is iterating to a fixed point, because otherwise we sometimes
|
||||
/// would wind up with a fresh stream of region variables that
|
||||
/// have been equated but appear distinct.
|
||||
/// R1 or R2). This is important when fulfillment, dropck and other such
|
||||
/// code is iterating to a fixed point, because otherwise we sometimes
|
||||
/// would wind up with a fresh stream of region variables that have been
|
||||
/// equated but appear distinct.
|
||||
pub(super) unification_table: ut::UnificationTableStorage<ty::RegionVid>,
|
||||
|
||||
/// a flag set to true when we perform any unifications; this is used
|
||||
@ -714,13 +714,8 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opportunistic_resolve_var(
|
||||
&mut self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
rid: RegionVid,
|
||||
) -> ty::Region<'tcx> {
|
||||
let vid = self.unification_table().probe_value(rid).min_vid;
|
||||
tcx.mk_region(ty::ReVar(vid))
|
||||
pub fn opportunistic_resolve_var(&mut self, rid: RegionVid) -> ty::RegionVid {
|
||||
self.unification_table().probe_value(rid).min_vid
|
||||
}
|
||||
|
||||
fn combine_map(&mut self, t: CombineMapType) -> &mut CombineMap<'tcx> {
|
||||
|
@ -46,51 +46,56 @@ impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticVarResolver<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The opportunistic type and region resolver is similar to the
|
||||
/// opportunistic type resolver, but also opportunistically resolves
|
||||
/// regions. It is useful for canonicalization.
|
||||
pub struct OpportunisticTypeAndRegionResolver<'a, 'tcx> {
|
||||
/// The opportunistic region resolver opportunistically resolves regions
|
||||
/// variables to the variable with the least variable id. It is used when
|
||||
/// normlizing projections to avoid hitting the recursion limit by creating
|
||||
/// many versions of a predicate for types that in the end have to unify.
|
||||
///
|
||||
/// If you want to resolve type and const variables as well, call
|
||||
/// [InferCtxt::resolve_vars_if_possible] first.
|
||||
pub struct OpportunisticRegionResolver<'a, 'tcx> {
|
||||
infcx: &'a InferCtxt<'a, 'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> OpportunisticTypeAndRegionResolver<'a, 'tcx> {
|
||||
impl<'a, 'tcx> OpportunisticRegionResolver<'a, 'tcx> {
|
||||
pub fn new(infcx: &'a InferCtxt<'a, 'tcx>) -> Self {
|
||||
OpportunisticTypeAndRegionResolver { infcx }
|
||||
OpportunisticRegionResolver { infcx }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticTypeAndRegionResolver<'a, 'tcx> {
|
||||
impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticRegionResolver<'a, 'tcx> {
|
||||
fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
|
||||
self.infcx.tcx
|
||||
}
|
||||
|
||||
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
|
||||
if !t.needs_infer() {
|
||||
if !t.has_infer_regions() {
|
||||
t // micro-optimize -- if there is nothing in this type that this fold affects...
|
||||
} else {
|
||||
let t0 = self.infcx.shallow_resolve(t);
|
||||
t0.super_fold_with(self)
|
||||
t.super_fold_with(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
|
||||
match *r {
|
||||
ty::ReVar(rid) => self
|
||||
.infcx
|
||||
.inner
|
||||
.borrow_mut()
|
||||
.unwrap_region_constraints()
|
||||
.opportunistic_resolve_var(self.tcx(), rid),
|
||||
ty::ReVar(rid) => {
|
||||
let resolved = self
|
||||
.infcx
|
||||
.inner
|
||||
.borrow_mut()
|
||||
.unwrap_region_constraints()
|
||||
.opportunistic_resolve_var(rid);
|
||||
self.tcx().reuse_or_mk_region(r, ty::ReVar(resolved))
|
||||
}
|
||||
_ => r,
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_const(&mut self, ct: &'tcx ty::Const<'tcx>) -> &'tcx ty::Const<'tcx> {
|
||||
if !ct.needs_infer() {
|
||||
if !ct.has_infer_regions() {
|
||||
ct // micro-optimize -- if there is nothing in this const that this fold affects...
|
||||
} else {
|
||||
let c0 = self.infcx.shallow_resolve(ct);
|
||||
c0.super_fold_with(self)
|
||||
ct.super_fold_with(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2110,6 +2110,13 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Same a `self.mk_region(kind)`, but avoids accessing the interners if
|
||||
/// `*r == kind`.
|
||||
#[inline]
|
||||
pub fn reuse_or_mk_region(self, r: Region<'tcx>, kind: RegionKind) -> Region<'tcx> {
|
||||
if *r == kind { r } else { self.mk_region(kind) }
|
||||
}
|
||||
|
||||
#[allow(rustc::usage_of_ty_tykind)]
|
||||
#[inline]
|
||||
pub fn mk_ty(&self, st: TyKind<'tcx>) -> Ty<'tcx> {
|
||||
|
@ -87,6 +87,9 @@ pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
|
||||
fn has_param_types_or_consts(&self) -> bool {
|
||||
self.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_CT_PARAM)
|
||||
}
|
||||
fn has_infer_regions(&self) -> bool {
|
||||
self.has_type_flags(TypeFlags::HAS_RE_INFER)
|
||||
}
|
||||
fn has_infer_types(&self) -> bool {
|
||||
self.has_type_flags(TypeFlags::HAS_TY_INFER)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_errors::ErrorReported;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::lang_items::{FnOnceTraitLangItem, GeneratorTraitLangItem};
|
||||
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
|
||||
use rustc_middle::ty::fold::{TypeFoldable, TypeFolder};
|
||||
use rustc_middle::ty::subst::Subst;
|
||||
use rustc_middle::ty::util::IntTypeExt;
|
||||
@ -1146,7 +1147,7 @@ fn confirm_candidate<'cx, 'tcx>(
|
||||
) -> Progress<'tcx> {
|
||||
debug!("confirm_candidate(candidate={:?}, obligation={:?})", candidate, obligation);
|
||||
|
||||
match candidate {
|
||||
let mut progress = match candidate {
|
||||
ProjectionTyCandidate::ParamEnv(poly_projection)
|
||||
| ProjectionTyCandidate::TraitDef(poly_projection) => {
|
||||
confirm_param_env_candidate(selcx, obligation, poly_projection)
|
||||
@ -1155,7 +1156,16 @@ fn confirm_candidate<'cx, 'tcx>(
|
||||
ProjectionTyCandidate::Select(impl_source) => {
|
||||
confirm_select_candidate(selcx, obligation, obligation_trait_ref, impl_source)
|
||||
}
|
||||
};
|
||||
// When checking for cycle during evaluation, we compare predicates with
|
||||
// "syntactic" equality. Since normalization generally introduces a type
|
||||
// with new region variables, we need to resolve them to existing variables
|
||||
// when possible for this to work. See `auto-trait-projection-recursion.rs`
|
||||
// for a case where this matters.
|
||||
if progress.ty.has_infer_regions() {
|
||||
progress.ty = OpportunisticRegionResolver::new(selcx.infcx()).fold_ty(progress.ty);
|
||||
}
|
||||
progress
|
||||
}
|
||||
|
||||
fn confirm_select_candidate<'cx, 'tcx>(
|
||||
|
35
src/test/rustdoc/synthetic_auto/overflow.rs
Normal file
35
src/test/rustdoc/synthetic_auto/overflow.rs
Normal file
@ -0,0 +1,35 @@
|
||||
// Tests that we don't fail with an overflow error for certain
|
||||
// strange types
|
||||
// See https://github.com/rust-lang/rust/pull/72936#issuecomment-643676915
|
||||
|
||||
pub trait Interner {
|
||||
type InternedType;
|
||||
}
|
||||
|
||||
struct RustInterner<'tcx> {
|
||||
foo: &'tcx ()
|
||||
}
|
||||
|
||||
impl<'tcx> Interner for RustInterner<'tcx> {
|
||||
type InternedType = Box<TyData<Self>>;
|
||||
}
|
||||
|
||||
enum TyData<I: Interner> {
|
||||
FnDef(I::InternedType)
|
||||
}
|
||||
|
||||
struct VariableKind<I: Interner>(I::InternedType);
|
||||
|
||||
// @has overflow/struct.BoundVarsCollector.html
|
||||
// @has - '//code' "impl<'tcx> Send for BoundVarsCollector<'tcx>"
|
||||
pub struct BoundVarsCollector<'tcx> {
|
||||
val: VariableKind<RustInterner<'tcx>>
|
||||
}
|
||||
|
||||
fn is_send<T: Send>() {}
|
||||
|
||||
struct MyInterner<'tcx> {
|
||||
val: &'tcx ()
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -1,8 +1,8 @@
|
||||
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
|
||||
--> $DIR/project-fn-ret-invariant.rs:48:8
|
||||
--> $DIR/project-fn-ret-invariant.rs:48:4
|
||||
|
|
||||
LL | bar(foo, x)
|
||||
| ^^^
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the function body at 44:8...
|
||||
--> $DIR/project-fn-ret-invariant.rs:44:8
|
||||
|
34
src/test/ui/auto-traits/auto-trait-projection-recursion.rs
Normal file
34
src/test/ui/auto-traits/auto-trait-projection-recursion.rs
Normal file
@ -0,0 +1,34 @@
|
||||
// Checking the `Send` bound in `main` requires:
|
||||
//
|
||||
// checking <C<'static> as Y>::P: Send
|
||||
// which normalizes to Box<X<C<'?1>>>: Send
|
||||
// which needs X<C<'?1>>: Send
|
||||
// which needs <C<'?1> as Y>::P: Send
|
||||
//
|
||||
// At this point we used to normalize the predicate to `Box<X<C<'?2>>>: Send`
|
||||
// and continue in a loop where we created new region variables to the
|
||||
// recursion limit. To avoid this we now "canonicalize" region variables to
|
||||
// lowest unified region vid. This means we instead have to prove
|
||||
// `Box<X<C<'?1>>>: Send`, which we can because auto traits are coinductive.
|
||||
|
||||
// check-pass
|
||||
|
||||
// Avoid a really long error message if this regresses.
|
||||
#![recursion_limit="20"]
|
||||
|
||||
trait Y {
|
||||
type P;
|
||||
}
|
||||
|
||||
impl<'a> Y for C<'a> {
|
||||
type P = Box<X<C<'a>>>;
|
||||
}
|
||||
|
||||
struct C<'a>(&'a ());
|
||||
struct X<T: Y>(T::P);
|
||||
|
||||
fn is_send<S: Send>() {}
|
||||
|
||||
fn main() {
|
||||
is_send::<X<C<'static>>>();
|
||||
}
|
30
src/test/ui/traits/traits-inductive-overflow-lifetime.rs
Normal file
30
src/test/ui/traits/traits-inductive-overflow-lifetime.rs
Normal file
@ -0,0 +1,30 @@
|
||||
// Test that we don't hit the recursion limit for short cycles involving lifetimes.
|
||||
|
||||
// Shouldn't hit this, we should realize that we're in a cycle sooner.
|
||||
#![recursion_limit="20"]
|
||||
|
||||
trait NotAuto {}
|
||||
trait Y {
|
||||
type P;
|
||||
}
|
||||
|
||||
impl<'a> Y for C<'a> {
|
||||
type P = Box<X<C<'a>>>;
|
||||
}
|
||||
|
||||
struct C<'a>(&'a ());
|
||||
struct X<T: Y>(T::P);
|
||||
|
||||
impl<T: NotAuto> NotAuto for Box<T> {}
|
||||
impl<T: Y> NotAuto for X<T> where T::P: NotAuto {}
|
||||
impl<'a> NotAuto for C<'a> {}
|
||||
|
||||
fn is_send<S: NotAuto>() {}
|
||||
//~^ NOTE: required
|
||||
|
||||
fn main() {
|
||||
// Should only be a few notes.
|
||||
is_send::<X<C<'static>>>();
|
||||
//~^ ERROR overflow evaluating
|
||||
//~| NOTE: required
|
||||
}
|
14
src/test/ui/traits/traits-inductive-overflow-lifetime.stderr
Normal file
14
src/test/ui/traits/traits-inductive-overflow-lifetime.stderr
Normal file
@ -0,0 +1,14 @@
|
||||
error[E0275]: overflow evaluating the requirement `std::boxed::Box<X<C<'_>>>: NotAuto`
|
||||
--> $DIR/traits-inductive-overflow-lifetime.rs:27:5
|
||||
|
|
||||
LL | fn is_send<S: NotAuto>() {}
|
||||
| ------- required by this bound in `is_send`
|
||||
...
|
||||
LL | is_send::<X<C<'static>>>();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: required because of the requirements on the impl of `NotAuto` for `X<C<'static>>`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0275`.
|
Loading…
x
Reference in New Issue
Block a user