Skip to content

Commit 5cfa7d1

Browse files
committed
Handle equal regions in opaque type inference
1 parent 728224d commit 5cfa7d1

File tree

4 files changed

+127
-18
lines changed

4 files changed

+127
-18
lines changed

src/librustc_mir/borrow_check/region_infer/opaque_types.rs

+68-18
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,50 @@
1-
use rustc::hir::def_id::DefId;
21
use rustc::infer::InferCtxt;
32
use rustc::ty;
43
use rustc_data_structures::fx::FxHashMap;
4+
use rustc_hir::def_id::DefId;
55
use rustc_span::Span;
66

77
use super::RegionInferenceContext;
88

99
impl<'tcx> RegionInferenceContext<'tcx> {
1010
/// Resolve any opaque types that were encountered while borrow checking
1111
/// this item. This is then used to get the type in the `type_of` query.
12+
///
13+
/// For example consider `fn f<'a>(x: &'a i32) -> impl Sized + 'a { x }`.
14+
/// This is lowered to give HIR something like
15+
///
16+
/// type _Return<'_a> = impl Sized + '_a;
17+
/// fn f<'a>(x: &'a i32) -> _Return<'a> { x }
18+
///
19+
/// When checking the return type record the type from the return and the
20+
/// type used in the return value. In this case they might be `_Return<'1>`
21+
/// and `&'2 i32` respectively.
22+
///
23+
/// Once we to this method, we have completed region inference and want to
24+
/// call `infer_opaque_definition_from_instantiation` to get the inferred
25+
/// type of `_Return<'_a>`. `infer_opaque_definition_from_instantiation`
26+
/// compares lifetimes directly, so we need to map the inference variables
27+
/// back to concrete lifetimes: `'static`, `ReEarlyBound` or `ReFree`.
28+
///
29+
/// First we map all the lifetimes in the concrete type to an equal
30+
/// universal region that occurs in the concrete type's substs, in this case
31+
/// this would result in `&'1 i32`. We only consider regions in the substs
32+
/// in case there is an equal region that does not. For example, this should
33+
/// be allowed:
34+
/// `fn f<'a: 'b, 'b: 'a>(x: *mut &'b i32) -> impl Sized + 'a { x }`
35+
///
36+
/// Then we map the regions in both the type and the subst to their
37+
/// `external_name` giving `concrete_type = &'a i32, substs = ['a]`. This
38+
/// will then allow `infer_opaque_definition_from_instantiation` to
39+
/// determine that `_Return<'_a> = &'_a i32`.
40+
///
41+
/// There's a slight complication around closures. Given
42+
/// `fn f<'a: 'a>() { || {} }` the closure's type is something like
43+
/// `f::<'a>::{{closure}}`. The region parameter from f is essentially
44+
/// ignored by type checking so ends up being inferred to an empty region.
45+
/// Calling `universal_upper_bound` for such a region gives `fr_fn_body`,
46+
/// which has no `external_name` in which case we use `'empty` as the
47+
/// region to pass to `infer_opaque_definition_from_instantiation`.
1248
pub(in crate::borrow_check) fn infer_opaque_types(
1349
&self,
1450
infcx: &InferCtxt<'_, 'tcx>,
@@ -23,32 +59,46 @@ impl<'tcx> RegionInferenceContext<'tcx> {
2359
concrete_type, substs
2460
);
2561

26-
// Map back to "concrete" regions so that errors in
27-
// `infer_opaque_definition_from_instantiation` can show
28-
// sensible region names.
29-
let universal_concrete_type =
30-
infcx.tcx.fold_regions(&concrete_type, &mut false, |region, _| match region {
31-
&ty::ReVar(vid) => {
32-
let universal_bound = self.universal_upper_bound(vid);
33-
self.definitions[universal_bound]
34-
.external_name
35-
.filter(|_| self.eval_equal(universal_bound, vid))
36-
.unwrap_or(infcx.tcx.lifetimes.re_empty)
37-
}
38-
concrete => concrete,
39-
});
62+
let mut subst_regions = vec![self.universal_regions.fr_static];
4063
let universal_substs =
41-
infcx.tcx.fold_regions(&substs, &mut false, |region, _| match region {
64+
infcx.tcx.fold_regions(&substs, &mut false, |region, _| match *region {
4265
ty::ReVar(vid) => {
43-
self.definitions[*vid].external_name.unwrap_or_else(|| {
66+
subst_regions.push(vid);
67+
self.definitions[vid].external_name.unwrap_or_else(|| {
4468
infcx.tcx.sess.delay_span_bug(
4569
span,
4670
"opaque type with non-universal region substs",
4771
);
4872
infcx.tcx.lifetimes.re_static
4973
})
5074
}
51-
concrete => concrete,
75+
_ => {
76+
infcx.tcx.sess.delay_span_bug(
77+
span,
78+
&format!("unexpected concrete region in borrowck: {:?}", region),
79+
);
80+
region
81+
}
82+
});
83+
84+
subst_regions.sort();
85+
subst_regions.dedup();
86+
87+
let universal_concrete_type =
88+
infcx.tcx.fold_regions(&concrete_type, &mut false, |region, _| match *region {
89+
ty::ReVar(vid) => subst_regions
90+
.iter()
91+
.find(|ur_vid| self.eval_equal(vid, **ur_vid))
92+
.and_then(|ur_vid| self.definitions[*ur_vid].external_name)
93+
.unwrap_or(infcx.tcx.lifetimes.re_root_empty),
94+
ty::ReLateBound(..) => region,
95+
_ => {
96+
infcx.tcx.sess.delay_span_bug(
97+
span,
98+
&format!("unexpected concrete region in borrowck: {:?}", region),
99+
);
100+
region
101+
}
52102
});
53103

54104
debug!(

src/librustc_mir/borrow_check/type_check/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
12751275
);
12761276

12771277
if !concrete_is_opaque {
1278+
// Equate concrete_ty (an inference variable) with
1279+
// the renumbered type from typeck.
12781280
obligations.add(
12791281
infcx
12801282
.at(&ObligationCause::dummy(), param_env)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Test that we consider equal regions when checking for hidden regions in
2+
// opaque types
3+
4+
// check-pass
5+
6+
// `'a == 'static` so `&'a i32` is fine as the return type
7+
fn equal_regions_static<'a: 'static>(x: &'a i32) -> impl Sized {
8+
//~^ WARNING unnecessary lifetime parameter `'a`
9+
x
10+
}
11+
12+
// `'a == 'b` so `&'b i32` is fine as the return type
13+
fn equal_regions<'a: 'b, 'b: 'a>(x: &'b i32) -> impl Sized + 'a {
14+
let y: &'a i32 = x;
15+
let z: &'b i32 = y;
16+
x
17+
}
18+
19+
// `'a == 'b` so `&'a i32` is fine as the return type
20+
fn equal_regions_rev<'a: 'b, 'b: 'a>(x: &'a i32) -> impl Sized + 'b {
21+
let y: &'a i32 = x;
22+
let z: &'b i32 = y;
23+
x
24+
}
25+
26+
// `'a == 'b` so `*mut &'b i32` is fine as the return type
27+
fn equal_regions_inv<'a: 'b, 'b: 'a>(x: *mut &'b i32) -> impl Sized + 'a {
28+
let y: *mut &'a i32 = x;
29+
let z: *mut &'b i32 = y;
30+
x
31+
}
32+
33+
// `'a == 'b` so `*mut &'a i32` is fine as the return type
34+
fn equal_regions_inv_rev<'a: 'b, 'b: 'a>(x: *mut &'a i32) -> impl Sized + 'b {
35+
let y: *mut &'a i32 = x;
36+
let z: *mut &'b i32 = y;
37+
x
38+
}
39+
40+
// Should be able to infer `fn(&'static ())` as the return type.
41+
fn contravariant_lub<'a, 'b: 'a, 'c: 'a, 'd: 'b + 'c>(
42+
x: fn(&'b ()),
43+
y: fn(&'c ()),
44+
c: bool,
45+
) -> impl Sized + 'a {
46+
if c { x } else { y }
47+
}
48+
49+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
warning: unnecessary lifetime parameter `'a`
2+
--> $DIR/equal-hidden-lifetimes.rs:7:25
3+
|
4+
LL | fn equal_regions_static<'a: 'static>(x: &'a i32) -> impl Sized {
5+
| ^^^^^^^^^^^
6+
|
7+
= help: you can use the `'static` lifetime directly, in place of `'a`
8+

0 commit comments

Comments
 (0)