Skip to content

Commit 79e8c31

Browse files
committed
Propagate region constraints more precisely from closures
1 parent ea613f3 commit 79e8c31

File tree

3 files changed

+142
-66
lines changed

3 files changed

+142
-66
lines changed

src/librustc_mir/borrow_check/nll/region_infer/mod.rs

+37-29
Original file line numberDiff line numberDiff line change
@@ -763,20 +763,26 @@ impl<'tcx> RegionInferenceContext<'tcx> {
763763

764764
debug!("try_promote_type_test: ur={:?}", ur);
765765

766-
let non_local_ub = self.universal_region_relations.non_local_upper_bound(ur);
766+
let non_local_ub = self.universal_region_relations.non_local_upper_bounds(&ur);
767767
debug!("try_promote_type_test: non_local_ub={:?}", non_local_ub);
768768

769-
assert!(self.universal_regions.is_universal_region(non_local_ub));
770-
assert!(!self.universal_regions.is_local_free_region(non_local_ub));
771-
772-
let requirement = ClosureOutlivesRequirement {
773-
subject,
774-
outlived_free_region: non_local_ub,
775-
blame_span: locations.span(mir),
776-
category: ConstraintCategory::Boring,
777-
};
778-
debug!("try_promote_type_test: pushing {:#?}", requirement);
779-
propagated_outlives_requirements.push(requirement);
769+
// This is slightly too conservative. To show T: '1, given `'2: '1`
770+
// and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to
771+
// avoid potential non-determinism we approximate this by requiring
772+
// T: '1 and T: '2.
773+
for &upper_bound in non_local_ub {
774+
debug_assert!(self.universal_regions.is_universal_region(upper_bound));
775+
debug_assert!(!self.universal_regions.is_local_free_region(upper_bound));
776+
777+
let requirement = ClosureOutlivesRequirement {
778+
subject,
779+
outlived_free_region: upper_bound,
780+
blame_span: locations.span(mir),
781+
category: ConstraintCategory::Boring,
782+
};
783+
debug!("try_promote_type_test: pushing {:#?}", requirement);
784+
propagated_outlives_requirements.push(requirement);
785+
}
780786
}
781787
true
782788
}
@@ -1217,35 +1223,37 @@ impl<'tcx> RegionInferenceContext<'tcx> {
12171223
longer_fr, shorter_fr,
12181224
);
12191225

1220-
12211226
if let Some(propagated_outlives_requirements) = propagated_outlives_requirements {
1222-
// Shrink `fr` until we find a non-local region (if we do).
1223-
// We'll call that `fr-` -- it's ever so slightly smaller than `fr`.
1224-
if let Some(fr_minus) = self.universal_region_relations
1227+
// Shrink `longer_fr` until we find a non-local region (if we do).
1228+
// We'll call it `fr-` -- it's ever so slightly smaller than
1229+
// `longer_fr`.
1230+
1231+
if let Some(fr_minus) = self
1232+
.universal_region_relations
12251233
.non_local_lower_bound(longer_fr)
12261234
{
12271235
debug!("check_universal_region: fr_minus={:?}", fr_minus);
12281236

12291237
let blame_span_category = self.find_outlives_blame_span(mir, longer_fr, shorter_fr);
12301238

1231-
// Grow `shorter_fr` until we find a non-local
1232-
// region. (We always will.) We'll call that
1233-
// `shorter_fr+` -- it's ever so slightly larger than
1234-
// `fr`.
1239+
// Grow `shorter_fr` until we find some non-local regions. (We
1240+
// always will.) We'll call them `shorter_fr+` -- they're ever
1241+
// so slightly larger than `shorter_fr`.
12351242
let shorter_fr_plus = self.universal_region_relations
1236-
.non_local_upper_bound(shorter_fr);
1243+
.non_local_upper_bounds(&shorter_fr);
12371244
debug!(
12381245
"check_universal_region: shorter_fr_plus={:?}",
12391246
shorter_fr_plus
12401247
);
1241-
1242-
// Push the constraint `fr-: shorter_fr+`
1243-
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
1244-
subject: ClosureOutlivesSubject::Region(fr_minus),
1245-
outlived_free_region: shorter_fr_plus,
1246-
blame_span: blame_span_category.1,
1247-
category: blame_span_category.0,
1248-
});
1248+
for &&fr in &shorter_fr_plus {
1249+
// Push the constraint `fr-: shorter_fr+`
1250+
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
1251+
subject: ClosureOutlivesSubject::Region(fr_minus),
1252+
outlived_free_region: fr,
1253+
blame_span: blame_span_category.1,
1254+
category: blame_span_category.0,
1255+
});
1256+
}
12491257
return None;
12501258
}
12511259
}

src/librustc_mir/borrow_check/nll/type_check/free_region_relations.rs

+65-37
Original file line numberDiff line numberDiff line change
@@ -105,44 +105,89 @@ impl UniversalRegionRelations<'tcx> {
105105

106106
/// Finds an "upper bound" for `fr` that is not local. In other
107107
/// words, returns the smallest (*) known region `fr1` that (a)
108-
/// outlives `fr` and (b) is not local. This cannot fail, because
109-
/// we will always find `'static` at worst.
108+
/// outlives `fr` and (b) is not local.
110109
///
111-
/// (*) If there are multiple competing choices, we pick the "postdominating"
112-
/// one. See `TransitiveRelation::postdom_upper_bound` for details.
113-
crate fn non_local_upper_bound(&self, fr: RegionVid) -> RegionVid {
110+
/// (*) If there are multiple competing choices, we return all of them.
111+
crate fn non_local_upper_bounds(&'a self, fr: &'a RegionVid) -> Vec<&'a RegionVid> {
114112
debug!("non_local_upper_bound(fr={:?})", fr);
115-
self.non_local_bound(&self.inverse_outlives, fr)
113+
let res = self.non_local_bounds(&self.inverse_outlives, fr);
114+
assert!(!res.is_empty(), "can't find an upper bound!?");
115+
res
116+
}
117+
118+
/// Returns the "postdominating" bound of the set of
119+
/// `non_local_upper_bounds` for the given region.
120+
crate fn non_local_upper_bound(&self, fr: RegionVid) -> RegionVid {
121+
let upper_bounds = self.non_local_upper_bounds(&fr);
122+
123+
// In case we find more than one, reduce to one for
124+
// convenience. This is to prevent us from generating more
125+
// complex constraints, but it will cause spurious errors.
126+
let post_dom = self
127+
.inverse_outlives
128+
.mutual_immediate_postdominator(upper_bounds);
129+
130+
debug!("non_local_bound: post_dom={:?}", post_dom);
131+
132+
post_dom
133+
.and_then(|&post_dom| {
134+
// If the mutual immediate postdom is not local, then
135+
// there is no non-local result we can return.
136+
if !self.universal_regions.is_local_free_region(post_dom) {
137+
Some(post_dom)
138+
} else {
139+
None
140+
}
141+
})
116142
.unwrap_or(self.universal_regions.fr_static)
117143
}
118144

145+
119146
/// Finds a "lower bound" for `fr` that is not local. In other
120147
/// words, returns the largest (*) known region `fr1` that (a) is
121-
/// outlived by `fr` and (b) is not local. This cannot fail,
122-
/// because we will always find `'static` at worst.
148+
/// outlived by `fr` and (b) is not local.
123149
///
124150
/// (*) If there are multiple competing choices, we pick the "postdominating"
125151
/// one. See `TransitiveRelation::postdom_upper_bound` for details.
126152
crate fn non_local_lower_bound(&self, fr: RegionVid) -> Option<RegionVid> {
127153
debug!("non_local_lower_bound(fr={:?})", fr);
128-
self.non_local_bound(&self.outlives, fr)
154+
let lower_bounds = self.non_local_bounds(&self.outlives, &fr);
155+
156+
// In case we find more than one, reduce to one for
157+
// convenience. This is to prevent us from generating more
158+
// complex constraints, but it will cause spurious errors.
159+
let post_dom = self
160+
.outlives
161+
.mutual_immediate_postdominator(lower_bounds);
162+
163+
debug!("non_local_bound: post_dom={:?}", post_dom);
164+
165+
post_dom
166+
.and_then(|&post_dom| {
167+
// If the mutual immediate postdom is not local, then
168+
// there is no non-local result we can return.
169+
if !self.universal_regions.is_local_free_region(post_dom) {
170+
Some(post_dom)
171+
} else {
172+
None
173+
}
174+
})
129175
}
130176

131-
/// Helper for `non_local_upper_bound` and
132-
/// `non_local_lower_bound`. Repeatedly invokes `postdom_parent`
133-
/// until we find something that is not local. Returns `None` if we
134-
/// never do so.
135-
fn non_local_bound(
177+
/// Helper for `non_local_upper_bounds` and `non_local_lower_bounds`.
178+
/// Repeatedly invokes `postdom_parent` until we find something that is not
179+
/// local. Returns `None` if we never do so.
180+
fn non_local_bounds<'a>(
136181
&self,
137-
relation: &TransitiveRelation<RegionVid>,
138-
fr0: RegionVid,
139-
) -> Option<RegionVid> {
182+
relation: &'a TransitiveRelation<RegionVid>,
183+
fr0: &'a RegionVid,
184+
) -> Vec<&'a RegionVid> {
140185
// This method assumes that `fr0` is one of the universally
141186
// quantified region variables.
142-
assert!(self.universal_regions.is_universal_region(fr0));
187+
assert!(self.universal_regions.is_universal_region(*fr0));
143188

144189
let mut external_parents = vec![];
145-
let mut queue = vec![&fr0];
190+
let mut queue = vec![fr0];
146191

147192
// Keep expanding `fr` into its parents until we reach
148193
// non-local regions.
@@ -157,24 +202,7 @@ impl UniversalRegionRelations<'tcx> {
157202

158203
debug!("non_local_bound: external_parents={:?}", external_parents);
159204

160-
// In case we find more than one, reduce to one for
161-
// convenience. This is to prevent us from generating more
162-
// complex constraints, but it will cause spurious errors.
163-
let post_dom = relation
164-
.mutual_immediate_postdominator(external_parents)
165-
.cloned();
166-
167-
debug!("non_local_bound: post_dom={:?}", post_dom);
168-
169-
post_dom.and_then(|post_dom| {
170-
// If the mutual immediate postdom is not local, then
171-
// there is no non-local result we can return.
172-
if !self.universal_regions.is_local_free_region(post_dom) {
173-
Some(post_dom)
174-
} else {
175-
None
176-
}
177-
})
205+
external_parents
178206
}
179207

180208
/// Returns `true` if fr1 is known to outlive fr2.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// revisions: migrate nll
2+
//[migrate]compile-flags: -Z borrowck=migrate
3+
#![cfg_attr(nll, feature(nll))]
4+
5+
// compile-pass
6+
7+
// Test that we propagate region relations from closures precisely when there is
8+
// more than one non-local lower bound.
9+
10+
// In this case the closure has signature
11+
// |x: &'4 mut (&'5 (&'1 str, &'2 str), &'3 str)| -> ..
12+
// We end up with a `'3: '5` constraint that we can propagate as
13+
// `'3: '1`, `'3: '2`, but previously we approximated it as `'3: 'static`.
14+
15+
// As an optimization, we primarily propagate bounds for the "representative"
16+
// of each SCC. As such we have these two similar cases where hopefully one
17+
// of them will test the case we want (case2, when this test was added).
18+
mod case1 {
19+
fn f(s: &str) {
20+
g(s, |x| h(x));
21+
}
22+
23+
fn g<T, F>(_: T, _: F)
24+
where F: Fn(&mut (&(T, T), T)) {}
25+
26+
fn h<T>(_: &mut (&(T, T), T)) {}
27+
}
28+
29+
mod case2 {
30+
fn f(s: &str) {
31+
g(s, |x| h(x));
32+
}
33+
34+
fn g<T, F>(_: T, _: F)
35+
where F: Fn(&mut (T, &(T, T))) {}
36+
37+
fn h<T>(_: &mut (T, &(T, T))) {}
38+
}
39+
40+
fn main() {}

0 commit comments

Comments
 (0)