Skip to content

Commit b893cff

Browse files
committed
make member constraints pick static if no upper bounds
The current member constraint algorithm has a failure mode when it encounters a variable `'0` and the only constraint is that `':static: '0`. In that case, there are no upper bounds, or at least no non-trivial upper bounds. As a result, we are not able to rule out any of the choices, so if you have a constraint like `'0 member ['a, 'b, 'static]`, where `'a` and `'b` are unrelated, then the algorithm gets stuck as there is no 'least choice' from that set. The tweak in this commit changes the algorithm so that *if* there are no upper bounds (and hence `'0` can get as large as desired without creating a region check error), it will just pick `'static`. This should only occur in cases where the data is flowing out from a `'static` value. This change is probably *not* right for impl Trait in let bindings, but those are challenging with member constraints anyway, and not currently supported. Furthermore, this change is not needed in a polonius-like formulation, which effectively permits "ad-hoc intersections" of lifetimes as the value for a region, and hence could give a value like `'a ^ 'b` as the resulting lifetime. Therefore I think there isn't forwards compat danger here. (famous last words?)
1 parent 207d955 commit b893cff

File tree

4 files changed

+99
-37
lines changed

4 files changed

+99
-37
lines changed

compiler/rustc_borrowck/src/region_infer/mod.rs

+43-36
Original file line numberDiff line numberDiff line change
@@ -717,11 +717,14 @@ impl<'tcx> RegionInferenceContext<'tcx> {
717717
// free region that must outlive the member region `R0` (`UB:
718718
// R0`). Therefore, we need only keep an option `O` if `UB: O`
719719
// for all UB.
720+
let fr_static = self.universal_regions.fr_static;
720721
let rev_scc_graph = self.reverse_scc_graph();
721722
let universal_region_relations = &self.universal_region_relations;
722-
for ub in rev_scc_graph.upper_bounds(scc) {
723+
let mut any_upper_bounds = false;
724+
for ub in rev_scc_graph.upper_bounds(scc).filter(|ub| *ub != fr_static) {
723725
debug!("apply_member_constraint: ub={:?}", ub);
724726
choice_regions.retain(|&o_r| universal_region_relations.outlives(ub, o_r));
727+
any_upper_bounds = true;
725728
}
726729
debug!("apply_member_constraint: after ub, choice_regions={:?}", choice_regions);
727730

@@ -730,46 +733,50 @@ impl<'tcx> RegionInferenceContext<'tcx> {
730733
return false;
731734
}
732735

733-
// Otherwise, we need to find the minimum remaining choice, if
734-
// any, and take that.
735-
debug!("apply_member_constraint: choice_regions remaining are {:#?}", choice_regions);
736-
let min = |r1: ty::RegionVid, r2: ty::RegionVid| -> Option<ty::RegionVid> {
737-
let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2);
738-
let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1);
739-
match (r1_outlives_r2, r2_outlives_r1) {
740-
(true, true) => Some(r1.min(r2)),
741-
(true, false) => Some(r2),
742-
(false, true) => Some(r1),
743-
(false, false) => None,
744-
}
745-
};
746-
let mut min_choice = choice_regions[0];
747-
for &other_option in &choice_regions[1..] {
748-
debug!(
749-
"apply_member_constraint: min_choice={:?} other_option={:?}",
750-
min_choice, other_option,
751-
);
752-
match min(min_choice, other_option) {
753-
Some(m) => min_choice = m,
754-
None => {
755-
debug!(
756-
"apply_member_constraint: {:?} and {:?} are incomparable; no min choice",
757-
min_choice, other_option,
758-
);
759-
return false;
736+
// If there WERE no upper bounds (apart from static), and static is one of the options,
737+
// then we can just pick that (c.f. #63033).
738+
let choice = if !any_upper_bounds && choice_regions.contains(&fr_static) {
739+
fr_static
740+
} else {
741+
// Otherwise, we need to find the minimum remaining choice, if
742+
// any, and take that.
743+
debug!("apply_member_constraint: choice_regions remaining are {:#?}", choice_regions);
744+
let min = |r1: ty::RegionVid, r2: ty::RegionVid| -> Option<ty::RegionVid> {
745+
let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2);
746+
let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1);
747+
match (r1_outlives_r2, r2_outlives_r1) {
748+
(true, true) => Some(r1.min(r2)),
749+
(true, false) => Some(r2),
750+
(false, true) => Some(r1),
751+
(false, false) => None,
752+
}
753+
};
754+
let mut min_choice = choice_regions[0];
755+
for &other_option in &choice_regions[1..] {
756+
debug!(
757+
"apply_member_constraint: min_choice={:?} other_option={:?}",
758+
min_choice, other_option,
759+
);
760+
match min(min_choice, other_option) {
761+
Some(m) => min_choice = m,
762+
None => {
763+
debug!(
764+
"apply_member_constraint: {:?} and {:?} are incomparable; no min choice",
765+
min_choice, other_option,
766+
);
767+
return false;
768+
}
760769
}
761770
}
762-
}
771+
min_choice
772+
};
763773

764-
let min_choice_scc = self.constraint_sccs.scc(min_choice);
765-
debug!(
766-
"apply_member_constraint: min_choice={:?} best_choice_scc={:?}",
767-
min_choice, min_choice_scc,
768-
);
769-
if self.scc_values.add_region(scc, min_choice_scc) {
774+
let choice_scc = self.constraint_sccs.scc(choice);
775+
debug!("apply_member_constraint: min_choice={:?} best_choice_scc={:?}", choice, choice_scc,);
776+
if self.scc_values.add_region(scc, choice_scc) {
770777
self.member_constraints_applied.push(AppliedMemberConstraint {
771778
member_region_scc: scc,
772-
min_choice,
779+
min_choice: choice,
773780
member_constraint_index,
774781
});
775782

compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
266266
///
267267
/// From that list, we look for a *minimal* option `'c_min`. If we
268268
/// find one, then we can enforce that `'r: 'c_min`.
269+
///
270+
/// Alternatively, if we find that there are *NO* upper bounds of `'r`
271+
/// apart from `'static`, and `'static` is one of choices, then
272+
/// we set `'r` to `'static` (c.f. #63033).
269273
fn enforce_member_constraint(
270274
&self,
271275
graph: &RegionGraph<'tcx>,
@@ -287,16 +291,31 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
287291
VarValue::ErrorValue => return false,
288292
VarValue::Value(r) => r,
289293
};
294+
debug!("enforce_member_constraint: lower_bound={:#?}", member_lower_bound);
295+
if member_constraint.choice_regions.contains(&member_lower_bound) {
296+
debug!("enforce_member_constraint: lower bound is already a valid choice");
297+
return false;
298+
}
290299

291300
// Find all the "upper bounds" -- that is, each region `b` such that
292301
// `r0 <= b` must hold.
293302
let (member_upper_bounds, ..) =
294303
self.collect_bounding_regions(graph, member_vid, OUTGOING, None);
304+
debug!("enforce_member_constraint: upper_bounds={:#?}", member_upper_bounds);
305+
306+
// If there are no upper bounds, and static is a choice (in practice, it always is),
307+
// then we should just pick static.
308+
if member_upper_bounds.is_empty()
309+
&& member_constraint.choice_regions.contains(&&ty::RegionKind::ReStatic)
310+
{
311+
debug!("enforce_member_constraint: selecting 'static since there are no upper bounds",);
312+
*var_values.value_mut(member_vid) = VarValue::Value(self.tcx().lifetimes.re_static);
313+
return true;
314+
}
295315

296316
// Get an iterator over the *available choice* -- that is,
297317
// each choice region `c` where `lb <= c` and `c <= ub` for all the
298318
// upper bounds `ub`.
299-
debug!("enforce_member_constraint: upper_bounds={:#?}", member_upper_bounds);
300319
let mut options = member_constraint.choice_regions.iter().filter(|option| {
301320
self.sub_concrete_regions(member_lower_bound, option)
302321
&& member_upper_bounds
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Regression test for #63033. The scenario here is:
2+
//
3+
// - The returned future captures the `Box<dyn T>`, which is shorthand for `Box<dyn T + 'static>`.
4+
// - The actual value that gets captured is `Box<dyn T + '?0>` where `'static: '?0`
5+
// - We generate a member constraint `'?0 member ['a, 'b, 'static]`
6+
// - None of those regions are a "least choice", so we got stuck
7+
//
8+
// After the fix, we now select `'static` in cases where there are no upper bounds (apart from
9+
// 'static).
10+
//
11+
// edition:2018
12+
// check-pass
13+
14+
#![allow(dead_code)]
15+
trait T {}
16+
struct S;
17+
impl S {
18+
async fn f<'a, 'b>(_a: &'a S, _b: &'b S, _c: Box<dyn T>) {}
19+
}
20+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Regression test for #63033. The scenario here is:
2+
//
3+
// - The returned future captures the &'static String`
4+
// - The actual value that gets captured is `&'?0 String` where `'static: '?0`
5+
// - We generate a member constraint `'?0 member ['a, 'b, 'static]`
6+
// - None of those regions are a "least choice", so we got stuck
7+
//
8+
// After the fix, we now select `'static` in cases where there are no upper bounds (apart from
9+
// 'static).
10+
//
11+
// edition:2018
12+
// check-pass
13+
14+
async fn test<'a, 'b>(test: &'a String, test2: &'b String, test3: &'static String) {}
15+
16+
fn main() {}

0 commit comments

Comments
 (0)