Skip to content

Commit b7b3d2c

Browse files
committed
generalize the outlives obligation code
The code now accepts `Binder<OutlivesPredicate>` instead of just `OutlivesPredicate` and thus exercises the new, generalized `IfEqBound` codepaths. Note though that we never *produce* Binder<OutlivesPredicate>, so we are only testing a subset of those codepaths that excludes actual higher-ranked outlives bounds.
1 parent 10f0f66 commit b7b3d2c

File tree

9 files changed

+109
-174
lines changed

9 files changed

+109
-174
lines changed

compiler/rustc_infer/src/infer/outlives/obligations.rs

+26-10
Original file line numberDiff line numberDiff line change
@@ -359,13 +359,21 @@ where
359359
// #55756) in cases where you have e.g., `<T as Foo<'a>>::Item:
360360
// 'a` in the environment but `trait Foo<'b> { type Item: 'b
361361
// }` in the trait definition.
362-
approx_env_bounds.retain(|bound| match *bound.0.kind() {
363-
ty::Projection(projection_ty) => self
364-
.verify_bound
365-
.projection_declared_bounds_from_trait(projection_ty)
366-
.all(|r| r != bound.1),
367-
368-
_ => panic!("expected only projection types from env, not {:?}", bound.0),
362+
approx_env_bounds.retain(|bound_outlives| {
363+
// OK to skip binder because we only manipulate and compare against other
364+
// values from the same inder. e.g. if we have (e.g.) `for<'a> <T as Trait<'a>>::Item: 'a`
365+
// in `bound`, the `'a` will be a `^1` (bound, debruijn index == innermost) region.
366+
// If the declaration is `trait Trait<'b> { type Item: 'b; }`, then `projection_declared_bounds_from_trait`
367+
// will be invoked with `['b => ^1]` and so we will get `^1` returned.
368+
let bound = bound_outlives.skip_binder();
369+
match *bound.0.kind() {
370+
ty::Projection(projection_ty) => self
371+
.verify_bound
372+
.projection_declared_bounds_from_trait(projection_ty)
373+
.all(|r| r != bound.1),
374+
375+
_ => panic!("expected only projection types from env, not {:?}", bound.0),
376+
}
369377
});
370378

371379
// If declared bounds list is empty, the only applicable rule is
@@ -416,8 +424,16 @@ where
416424
if !trait_bounds.is_empty()
417425
&& trait_bounds[1..]
418426
.iter()
419-
.chain(approx_env_bounds.iter().map(|b| &b.1))
420-
.all(|b| *b == trait_bounds[0])
427+
.map(|r| Some(*r))
428+
.chain(
429+
// NB: The environment may contain `for<'a> T: 'a` style bounds.
430+
// In that case, we don't know if they are equal to the trait bound
431+
// or not (since we don't *know* whether the environment bound even applies),
432+
// so just map to `None` here if there are bound vars, ensuring that
433+
// the call to `all` will fail below.
434+
approx_env_bounds.iter().map(|b| b.map_bound(|b| b.1).no_bound_vars()),
435+
)
436+
.all(|b| b == Some(trait_bounds[0]))
421437
{
422438
let unique_bound = trait_bounds[0];
423439
debug!("projection_must_outlive: unique trait bound = {:?}", unique_bound);
@@ -433,7 +449,7 @@ where
433449
// even though a satisfactory solution exists.
434450
let generic = GenericKind::Projection(projection_ty);
435451
let verify_bound = self.verify_bound.generic_bound(generic);
436-
debug!("projection_must_outlive: pushing verify_bound={:?}", verify_bound,);
452+
debug!("projection_must_outlive: pushing {:?}", verify_bound);
437453
self.delegate.push_verify(origin, generic, region, verify_bound);
438454
}
439455
}

compiler/rustc_infer/src/infer/outlives/test_type_match.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use crate::infer::region_constraints::VerifyIfEq;
3434
/// like are used. This is a particular challenge since this function is invoked
3535
/// very late in inference and hence cannot make use of the normal inference
3636
/// machinery.
37+
#[tracing::instrument(level = "Debug", skip(tcx, param_env))]
3738
pub fn extract_verify_if_eq_bound<'tcx>(
3839
tcx: TyCtxt<'tcx>,
3940
param_env: ty::ParamEnv<'tcx>,
@@ -61,6 +62,25 @@ pub fn extract_verify_if_eq_bound<'tcx>(
6162
}
6263
}
6364

65+
/// True if a (potentially higher-ranked) outlives
66+
#[tracing::instrument(level = "Debug", skip(tcx, param_env))]
67+
pub(super) fn can_match_erased_ty<'tcx>(
68+
tcx: TyCtxt<'tcx>,
69+
param_env: ty::ParamEnv<'tcx>,
70+
outlives_predicate: ty::Binder<'tcx, ty::TypeOutlivesPredicate<'tcx>>,
71+
erased_ty: Ty<'tcx>,
72+
) -> bool {
73+
assert!(!outlives_predicate.has_escaping_bound_vars());
74+
let erased_outlives_predicate = tcx.erase_regions(outlives_predicate);
75+
let outlives_ty = erased_outlives_predicate.skip_binder().0;
76+
if outlives_ty == erased_ty {
77+
// pointless micro-optimization
78+
true
79+
} else {
80+
Match::new(tcx, param_env).relate(outlives_ty, erased_ty).is_ok()
81+
}
82+
}
83+
6484
struct Match<'tcx> {
6585
tcx: TyCtxt<'tcx>,
6686
param_env: ty::ParamEnv<'tcx>,
@@ -82,6 +102,7 @@ impl<'tcx> Match<'tcx> {
82102

83103
/// Binds the pattern variable `br` to `value`; returns an `Err` if the pattern
84104
/// is already bound to a different value.
105+
#[tracing::instrument(level = "Debug", skip(self))]
85106
fn bind(
86107
&mut self,
87108
br: ty::BoundRegion,
@@ -133,15 +154,17 @@ impl<'tcx> TypeRelation<'tcx> for Match<'tcx> {
133154
pattern: ty::Region<'tcx>,
134155
value: ty::Region<'tcx>,
135156
) -> RelateResult<'tcx, ty::Region<'tcx>> {
157+
debug!("self.pattern_depth = {:?}", self.pattern_depth);
136158
if let ty::RegionKind::ReLateBound(depth, br) = pattern.kind() && depth == self.pattern_depth {
137-
self.bind(br, pattern)
159+
self.bind(br, value)
138160
} else if pattern == value {
139161
Ok(pattern)
140162
} else {
141163
self.no_match()
142164
}
143165
}
144166

167+
#[instrument(skip(self), level = "debug")]
145168
fn tys(&mut self, pattern: Ty<'tcx>, value: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
146169
if pattern == value {
147170
return Ok(pattern);
@@ -150,6 +173,7 @@ impl<'tcx> TypeRelation<'tcx> for Match<'tcx> {
150173
}
151174
}
152175

176+
#[instrument(skip(self), level = "debug")]
153177
fn consts(
154178
&mut self,
155179
pattern: ty::Const<'tcx>,

compiler/rustc_infer/src/infer/outlives/verify.rs

+43-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::infer::outlives::env::RegionBoundPairs;
2+
use crate::infer::region_constraints::VerifyIfEq;
23
use crate::infer::{GenericKind, VerifyBound};
34
use rustc_data_structures::captures::Captures;
45
use rustc_data_structures::sso::SsoHashSet;
@@ -82,25 +83,39 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
8283
debug!("param_bound(param_ty={:?})", param_ty);
8384

8485
// Start with anything like `T: 'a` we can scrape from the
85-
// environment
86-
let param_bounds =
87-
self.declared_generic_bounds_from_env(param_ty).into_iter().map(|outlives| outlives.1);
86+
// environment. If the environment contains something like
87+
// `for<'a> T: 'a`, then we know that `T` outlives everything.
88+
let declared_bounds_from_env = self.declared_generic_bounds_from_env(param_ty);
89+
let mut param_bounds = vec![];
90+
for declared_bound in declared_bounds_from_env {
91+
let bound_region = declared_bound.map_bound(|outlives| outlives.1);
92+
if let Some(region) = bound_region.no_bound_vars() {
93+
// This is `T: 'a` for some free region `'a`.
94+
param_bounds.push(VerifyBound::OutlivedBy(region));
95+
} else {
96+
// This is `for<'a> T: 'a`. This means that `T` outlives everything! All done here.
97+
return VerifyBound::AllBounds(vec![]);
98+
}
99+
}
88100

89101
// Add in the default bound of fn body that applies to all in
90102
// scope type parameters:
91-
let param_bounds = param_bounds.chain(self.implicit_region_bound);
92-
93-
let any_bounds: Vec<_> = param_bounds.map(|r| VerifyBound::OutlivedBy(r)).collect();
103+
if let Some(r) = self.implicit_region_bound {
104+
param_bounds.push(VerifyBound::OutlivedBy(r));
105+
}
94106

95-
if any_bounds.is_empty() {
107+
if param_bounds.is_empty() {
96108
// We know that all types `T` outlive `'empty`, so if we
97109
// can find no other bound, then check that the region
98110
// being tested is `'empty`.
99111
VerifyBound::IsEmpty
112+
} else if param_bounds.len() == 1 {
113+
// Micro-opt: no need to store the vector if it's just len 1
114+
param_bounds.pop().unwrap()
100115
} else {
101116
// If we can find any other bound `R` such that `T: R`, then
102117
// we don't need to check for `'empty`, because `R: 'empty`.
103-
VerifyBound::AnyBound(any_bounds)
118+
VerifyBound::AnyBound(param_bounds)
104119
}
105120
}
106121

@@ -120,7 +135,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
120135
pub fn projection_approx_declared_bounds_from_env(
121136
&self,
122137
projection_ty: ty::ProjectionTy<'tcx>,
123-
) -> Vec<ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>> {
138+
) -> Vec<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
124139
let projection_ty = GenericKind::Projection(projection_ty).to_ty(self.tcx);
125140
let erased_projection_ty = self.tcx.erase_regions(projection_ty);
126141
self.declared_generic_bounds_from_env_for_erased_ty(erased_projection_ty)
@@ -150,14 +165,15 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
150165
let env_bounds = self
151166
.projection_approx_declared_bounds_from_env(projection_ty)
152167
.into_iter()
153-
.map(|ty::OutlivesPredicate(ty, r)| {
154-
if ty == projection_ty_as_ty {
168+
.map(|binder| {
169+
if let Some(ty::OutlivesPredicate(ty, r)) = binder.no_bound_vars() && ty == projection_ty_as_ty {
155170
// Micro-optimize if this is an exact match (this
156171
// occurs often when there are no region variables
157172
// involved).
158173
VerifyBound::OutlivedBy(r)
159174
} else {
160-
VerifyBound::IfEq(ty, r)
175+
let verify_if_eq_b = binder.map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound });
176+
VerifyBound::IfEqBound(verify_if_eq_b)
161177
}
162178
});
163179

@@ -210,7 +226,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
210226
fn declared_generic_bounds_from_env(
211227
&self,
212228
param_ty: ty::ParamTy,
213-
) -> Vec<ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>> {
229+
) -> Vec<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
214230
let generic_ty = param_ty.to_ty(self.tcx);
215231
self.declared_generic_bounds_from_env_for_erased_ty(generic_ty)
216232
}
@@ -229,7 +245,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
229245
fn declared_generic_bounds_from_env_for_erased_ty(
230246
&self,
231247
erased_ty: Ty<'tcx>,
232-
) -> Vec<ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>> {
248+
) -> Vec<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
233249
let tcx = self.tcx;
234250

235251
// To start, collect bounds from user environment. Note that
@@ -259,7 +275,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
259275
);
260276
let p_ty = p.to_ty(tcx);
261277
let erased_p_ty = self.tcx.erase_regions(p_ty);
262-
(erased_p_ty == erased_ty).then_some(ty::OutlivesPredicate(p.to_ty(tcx), r))
278+
(erased_p_ty == erased_ty)
279+
.then_some(ty::Binder::dummy(ty::OutlivesPredicate(p.to_ty(tcx), r)))
263280
});
264281

265282
param_bounds
@@ -348,11 +365,17 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
348365
&self,
349366
erased_ty: Ty<'tcx>,
350367
predicates: impl Iterator<Item = ty::Predicate<'tcx>>,
351-
) -> impl Iterator<Item = ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>> {
368+
) -> impl Iterator<Item = ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>>
369+
{
352370
let tcx = self.tcx;
353-
predicates
354-
.filter_map(|p| p.to_opt_type_outlives())
355-
.filter_map(|p| p.no_bound_vars())
356-
.filter(move |p| tcx.erase_regions(p.0) == erased_ty)
371+
let param_env = self.param_env;
372+
predicates.filter_map(|p| p.to_opt_type_outlives()).filter(move |outlives_predicate| {
373+
super::test_type_match::can_match_erased_ty(
374+
tcx,
375+
param_env,
376+
*outlives_predicate,
377+
erased_ty,
378+
)
379+
})
357380
}
358381
}

src/test/ui/borrowck/issue-71546.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
// Regression test for #71546.
2+
//
3+
// Made to pass as part of fixing #98095.
4+
//
5+
// check-pass
26

37
pub fn serialize_as_csv<V>(value: &V) -> Result<String, &str>
48
where
59
V: 'static,
610
for<'a> &'a V: IntoIterator,
711
for<'a> <&'a V as IntoIterator>::Item: ToString + 'static,
812
{
9-
let csv_str: String = value
10-
//~^ ERROR higher-ranked lifetime error
11-
//~| ERROR higher-ranked lifetime error
12-
//~| ERROR higher-ranked lifetime error
13-
.into_iter()
14-
.map(|elem| elem.to_string())
15-
//~^ ERROR higher-ranked lifetime error
16-
.collect::<String>();
17-
//~^ ERROR higher-ranked lifetime error
13+
let csv_str: String = value.into_iter().map(|elem| elem.to_string()).collect::<String>();
1814
Ok(csv_str)
1915
}
2016

src/test/ui/borrowck/issue-71546.stderr

-59
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// Regression test of #86483.
2+
//
3+
// Made to pass as part of fixing #98095.
4+
//
5+
// check-pass
26

37
#![feature(generic_associated_types)]
48

5-
pub trait IceIce<T> //~ ERROR: the parameter type `T` may not live long enough
9+
pub trait IceIce<T>
610
where
711
for<'a> T: 'a,
812
{
913
type Ice<'v>: IntoIterator<Item = &'v T>;
10-
//~^ ERROR: the parameter type `T` may not live long enough
11-
//~| ERROR: the parameter type `T` may not live long enough
1214
}
1315

1416
fn main() {}

0 commit comments

Comments
 (0)