Skip to content

Commit ba2ef58

Browse files
committed
Unify region variables when projecting associated types
This is required to avoid cycles when evaluating auto trait predicates.
1 parent 435f97c commit ba2ef58

29 files changed

+133
-35
lines changed

src/librustc_infer/infer/canonical/canonicalizer.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -314,18 +314,25 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for Canonicalizer<'cx, 'tcx> {
314314
}
315315

316316
ty::ReVar(vid) => {
317-
let r = self
317+
let resolved_vid = self
318318
.infcx
319319
.unwrap()
320320
.inner
321321
.borrow_mut()
322322
.unwrap_region_constraints()
323-
.opportunistic_resolve_var(self.tcx, vid);
323+
.opportunistic_resolve_var(vid);
324324
debug!(
325325
"canonical: region var found with vid {:?}, \
326326
opportunistically resolved to {:?}",
327327
vid, r
328328
);
329+
// micro-optimize -- avoid an interner look-up if the vid
330+
// hasn't changed.
331+
let r = if vid == resolved_vid {
332+
r
333+
} else {
334+
self.tcx.mk_region(ty::ReVar(resolved_vid))
335+
};
329336
self.canonicalize_region_mode.canonicalize_free_region(self, r)
330337
}
331338

src/librustc_infer/infer/region_constraints/mod.rs

+6-11
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ pub struct RegionConstraintStorage<'tcx> {
5050
/// R1 <= R2 and R2 <= R1 and (b) we unify the two regions in this
5151
/// table. You can then call `opportunistic_resolve_var` early
5252
/// which will map R1 and R2 to some common region (i.e., either
53-
/// R1 or R2). This is important when dropck and other such code
54-
/// is iterating to a fixed point, because otherwise we sometimes
55-
/// would wind up with a fresh stream of region variables that
56-
/// have been equated but appear distinct.
53+
/// R1 or R2). This is important when fulfillment, dropck and other such
54+
/// code is iterating to a fixed point, because otherwise we sometimes
55+
/// would wind up with a fresh stream of region variables that have been
56+
/// equated but appear distinct.
5757
pub(super) unification_table: ut::UnificationTableStorage<ty::RegionVid>,
5858

5959
/// a flag set to true when we perform any unifications; this is used
@@ -714,13 +714,8 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> {
714714
}
715715
}
716716

717-
pub fn opportunistic_resolve_var(
718-
&mut self,
719-
tcx: TyCtxt<'tcx>,
720-
rid: RegionVid,
721-
) -> ty::Region<'tcx> {
722-
let vid = self.unification_table().probe_value(rid).min_vid;
723-
tcx.mk_region(ty::ReVar(vid))
717+
pub fn opportunistic_resolve_var(&mut self, rid: RegionVid) -> ty::RegionVid {
718+
self.unification_table().probe_value(rid).min_vid
724719
}
725720

726721
fn combine_map(&mut self, t: CombineMapType) -> &mut CombineMap<'tcx> {

src/librustc_infer/infer/resolve.rs

+24-19
Original file line numberDiff line numberDiff line change
@@ -46,51 +46,56 @@ impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticVarResolver<'a, 'tcx> {
4646
}
4747
}
4848

49-
/// The opportunistic type and region resolver is similar to the
50-
/// opportunistic type resolver, but also opportunistically resolves
51-
/// regions. It is useful for canonicalization.
52-
pub struct OpportunisticTypeAndRegionResolver<'a, 'tcx> {
49+
/// The opportunistic region resolver opportunistically resolves regions
50+
/// variables to the variable with the least variable id. It is used when
51+
/// normlizing projections to avoid hitting the recursion limit by creating
52+
/// many versions of a predicate for types that in the end have to unify.
53+
///
54+
/// If you want to resolve type and const variables as well, call
55+
/// [InferCtxt::resolve_vars_if_possible] first.
56+
pub struct OpportunisticRegionResolver<'a, 'tcx> {
5357
infcx: &'a InferCtxt<'a, 'tcx>,
5458
}
5559

56-
impl<'a, 'tcx> OpportunisticTypeAndRegionResolver<'a, 'tcx> {
60+
impl<'a, 'tcx> OpportunisticRegionResolver<'a, 'tcx> {
5761
pub fn new(infcx: &'a InferCtxt<'a, 'tcx>) -> Self {
58-
OpportunisticTypeAndRegionResolver { infcx }
62+
OpportunisticRegionResolver { infcx }
5963
}
6064
}
6165

62-
impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticTypeAndRegionResolver<'a, 'tcx> {
66+
impl<'a, 'tcx> TypeFolder<'tcx> for OpportunisticRegionResolver<'a, 'tcx> {
6367
fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
6468
self.infcx.tcx
6569
}
6670

6771
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
68-
if !t.needs_infer() {
72+
if !t.has_infer_regions() {
6973
t // micro-optimize -- if there is nothing in this type that this fold affects...
7074
} else {
71-
let t0 = self.infcx.shallow_resolve(t);
72-
t0.super_fold_with(self)
75+
t.super_fold_with(self)
7376
}
7477
}
7578

7679
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
7780
match *r {
78-
ty::ReVar(rid) => self
79-
.infcx
80-
.inner
81-
.borrow_mut()
82-
.unwrap_region_constraints()
83-
.opportunistic_resolve_var(self.tcx(), rid),
81+
ty::ReVar(rid) => {
82+
let resolved = self
83+
.infcx
84+
.inner
85+
.borrow_mut()
86+
.unwrap_region_constraints()
87+
.opportunistic_resolve_var(rid);
88+
if resolved == rid { r } else { self.tcx().mk_region(ty::ReVar(resolved)) }
89+
}
8490
_ => r,
8591
}
8692
}
8793

8894
fn fold_const(&mut self, ct: &'tcx ty::Const<'tcx>) -> &'tcx ty::Const<'tcx> {
89-
if !ct.needs_infer() {
95+
if !ct.has_infer_regions() {
9096
ct // micro-optimize -- if there is nothing in this const that this fold affects...
9197
} else {
92-
let c0 = self.infcx.shallow_resolve(ct);
93-
c0.super_fold_with(self)
98+
ct.super_fold_with(self)
9499
}
95100
}
96101
}

src/librustc_middle/ty/fold.rs

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
8787
fn has_param_types_or_consts(&self) -> bool {
8888
self.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_CT_PARAM)
8989
}
90+
fn has_infer_regions(&self) -> bool {
91+
self.has_type_flags(TypeFlags::HAS_RE_INFER)
92+
}
9093
fn has_infer_types(&self) -> bool {
9194
self.has_type_flags(TypeFlags::HAS_TY_INFER)
9295
}

src/librustc_trait_selection/traits/project.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
2424
use rustc_errors::ErrorReported;
2525
use rustc_hir::def_id::DefId;
2626
use rustc_hir::lang_items::{FnOnceTraitLangItem, GeneratorTraitLangItem};
27+
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
2728
use rustc_middle::ty::fold::{TypeFoldable, TypeFolder};
2829
use rustc_middle::ty::subst::Subst;
2930
use rustc_middle::ty::util::IntTypeExt;
@@ -1146,7 +1147,7 @@ fn confirm_candidate<'cx, 'tcx>(
11461147
) -> Progress<'tcx> {
11471148
debug!("confirm_candidate(candidate={:?}, obligation={:?})", candidate, obligation);
11481149

1149-
match candidate {
1150+
let mut progress = match candidate {
11501151
ProjectionTyCandidate::ParamEnv(poly_projection)
11511152
| ProjectionTyCandidate::TraitDef(poly_projection) => {
11521153
confirm_param_env_candidate(selcx, obligation, poly_projection)
@@ -1155,7 +1156,16 @@ fn confirm_candidate<'cx, 'tcx>(
11551156
ProjectionTyCandidate::Select(impl_source) => {
11561157
confirm_select_candidate(selcx, obligation, obligation_trait_ref, impl_source)
11571158
}
1159+
};
1160+
// When checking for cycle during evaluation, we compare predicates with
1161+
// "syntactic" equality. Since normalization generally introduces a type
1162+
// with new region variables, we need to resolve them to existing variables
1163+
// when possible for this to work. See `auto-trait-projection-recursion.rs`
1164+
// for a case where this matters.
1165+
if progress.ty.has_infer_regions() {
1166+
progress.ty = OpportunisticRegionResolver::new(selcx.infcx()).fold_ty(progress.ty);
11581167
}
1168+
progress
11591169
}
11601170

11611171
fn confirm_select_candidate<'cx, 'tcx>(

src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
2-
--> $DIR/project-fn-ret-invariant.rs:48:8
2+
--> $DIR/project-fn-ret-invariant.rs:48:4
33
|
44
LL | bar(foo, x)
5-
| ^^^
5+
| ^^^^^^^^^^^
66
|
77
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the function body at 44:8...
88
--> $DIR/project-fn-ret-invariant.rs:44:8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Checking the `Send` bound in `main` requires:
2+
//
3+
// checking <C<'static> as Y>::P: Send
4+
// which normalizes to Box<X<C<'?1>>>: Send
5+
// which needs X<C<'?1>>: Send
6+
// which needs <C<'?1> as Y>::P: Send
7+
//
8+
// At this point we used to normalize the predicate to `Box<X<C<'?2>>>: Send`
9+
// and continue in a loop where we created new region variables to the
10+
// recursion limit. To avoid this we now "canonicalize" region variables to
11+
// lowest unified region vid. This means we instead have to prove
12+
// `Box<X<C<'?1>>>: Send`, which we can because auto traits are coinductive.
13+
14+
// check-pass
15+
16+
// Avoid a really long error message if this regresses.
17+
#![recursion_limit="20"]
18+
19+
trait Y {
20+
type P;
21+
}
22+
23+
impl<'a> Y for C<'a> {
24+
type P = Box<X<C<'a>>>;
25+
}
26+
27+
struct C<'a>(&'a ());
28+
struct X<T: Y>(T::P);
29+
30+
fn is_send<S: Send>() {}
31+
32+
fn main() {
33+
is_send::<X<C<'static>>>();
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Test that we don't hit the recursion limit for short cycles involving lifetimes.
2+
3+
// Shouldn't hit this, we should realize that we're in a cycle sooner.
4+
#![recursion_limit="20"]
5+
6+
trait NotAuto {}
7+
trait Y {
8+
type P;
9+
}
10+
11+
impl<'a> Y for C<'a> {
12+
type P = Box<X<C<'a>>>;
13+
}
14+
15+
struct C<'a>(&'a ());
16+
struct X<T: Y>(T::P);
17+
18+
impl<T: NotAuto> NotAuto for Box<T> {}
19+
impl<T: Y> NotAuto for X<T> where T::P: NotAuto {}
20+
impl<'a> NotAuto for C<'a> {}
21+
22+
fn is_send<S: NotAuto>() {}
23+
//~^ NOTE: required
24+
25+
fn main() {
26+
// Should only be a few notes.
27+
is_send::<X<C<'static>>>();
28+
//~^ ERROR overflow evaluating
29+
//~| NOTE: required
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0275]: overflow evaluating the requirement `std::boxed::Box<X<C<'_>>>: NotAuto`
2+
--> $DIR/traits-inductive-overflow-lifetime.rs:27:5
3+
|
4+
LL | fn is_send<S: NotAuto>() {}
5+
| ------- required by this bound in `is_send`
6+
...
7+
LL | is_send::<X<C<'static>>>();
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^
9+
|
10+
= note: required because of the requirements on the impl of `NotAuto` for `X<C<'static>>`
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0275`.

0 commit comments

Comments
 (0)