Skip to content

Commit 837c1af

Browse files
committed
rework min_choice algorithm of member constraints
See the inline comments for the description of the new algorithm.
1 parent 159ba8a commit 837c1af

File tree

8 files changed

+288
-11
lines changed

8 files changed

+288
-11
lines changed

compiler/rustc_borrowck/src/region_infer/mod.rs

+24-11
Original file line numberDiff line numberDiff line change
@@ -739,20 +739,33 @@ impl<'tcx> RegionInferenceContext<'tcx> {
739739
}
740740
debug!(?choice_regions, "after ub");
741741

742-
// If we ruled everything out, we're done.
743-
if choice_regions.is_empty() {
744-
return false;
745-
}
746-
747-
// Otherwise, we need to find the minimum remaining choice, if
748-
// any, and take that.
749-
debug!("choice_regions remaining are {:#?}", choice_regions);
750-
let Some(&min_choice) = choice_regions.iter().find(|&r1| {
742+
// At this point we can pick any member of `choice_regions`, but to avoid potential
743+
// non-determinism we will pick the *unique minimum* choice.
744+
//
745+
// Because universal regions are only partially ordered (i.e, not every two regions are
746+
// comparable), we will ignore any region that doesn't compare to all others when picking
747+
// the minimum choice.
748+
// For example, consider `choice_regions = ['static, 'a, 'b, 'c, 'd, 'e]`, where
749+
// `'static: 'a, 'static: 'b, 'a: 'c, 'b: 'c, 'c: 'd, 'c: 'e`.
750+
// `['d, 'e]` are ignored because they do not compare - the same goes for `['a, 'b]`.
751+
let totally_ordered_subset = choice_regions.iter().copied().filter(|&r1| {
751752
choice_regions.iter().all(|&r2| {
752-
self.universal_region_relations.outlives(r2, *r1)
753+
self.universal_region_relations.outlives(r1, r2)
754+
|| self.universal_region_relations.outlives(r2, r1)
753755
})
756+
});
757+
// Now we're left with `['static, 'c]`. Pick `'c` as the minimum!
758+
let Some(min_choice) = totally_ordered_subset.reduce(|r1, r2| {
759+
let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2);
760+
let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1);
761+
match (r1_outlives_r2, r2_outlives_r1) {
762+
(true, true) => r1.min(r2),
763+
(true, false) => r2,
764+
(false, true) => r1,
765+
(false, false) => bug!("incomparable regions in total order"),
766+
}
754767
}) else {
755-
debug!("no choice region outlived by all others");
768+
debug!("no unique minimum choice");
756769
return false;
757770
};
758771

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Regression test for #63033.
2+
3+
// check-pass
4+
// edition: 2018
5+
6+
async fn test1(_: &'static u8, _: &'_ u8, _: &'_ u8) {}
7+
8+
async fn test2<'s>(_: &'s u8, _: &'_ &'s u8, _: &'_ &'s u8) {}
9+
10+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// ... continued from ./min-choice.rs
2+
3+
// check-fail
4+
5+
trait Cap<'a> {}
6+
impl<T> Cap<'_> for T {}
7+
8+
fn type_test<'a, T: 'a>() -> &'a u8 { &0 }
9+
10+
// Make sure we don't pick `'b`.
11+
fn test_b<'a, 'b, 'c, T>() -> impl Cap<'a> + Cap<'b> + Cap<'c>
12+
where
13+
'a: 'b,
14+
'a: 'c,
15+
T: 'b,
16+
{
17+
type_test::<'_, T>() // This should pass if we pick 'b.
18+
//~^ ERROR the parameter type `T` may not live long enough
19+
}
20+
21+
// Make sure we don't pick `'c`.
22+
fn test_c<'a, 'b, 'c, T>() -> impl Cap<'a> + Cap<'b> + Cap<'c>
23+
where
24+
'a: 'b,
25+
'a: 'c,
26+
T: 'c,
27+
{
28+
type_test::<'_, T>() // This should pass if we pick 'c.
29+
//~^ ERROR the parameter type `T` may not live long enough
30+
}
31+
32+
// We need to pick min_choice from `['b, 'c]`, but it's ambiguous which one to pick because
33+
// they're incomparable.
34+
fn test_ambiguous<'a, 'b, 'c>(s: &'a u8) -> impl Cap<'b> + Cap<'c>
35+
where
36+
'a: 'b,
37+
'a: 'c,
38+
{
39+
s
40+
//~^ ERROR captures lifetime that does not appear in bounds
41+
}
42+
43+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error[E0309]: the parameter type `T` may not live long enough
2+
--> $DIR/min-choice-reject-ambiguous.rs:17:5
3+
|
4+
LL | type_test::<'_, T>() // This should pass if we pick 'b.
5+
| ^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
6+
|
7+
help: consider adding an explicit lifetime bound...
8+
|
9+
LL | T: 'b + 'a,
10+
| ++++
11+
12+
error[E0309]: the parameter type `T` may not live long enough
13+
--> $DIR/min-choice-reject-ambiguous.rs:28:5
14+
|
15+
LL | type_test::<'_, T>() // This should pass if we pick 'c.
16+
| ^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
17+
|
18+
help: consider adding an explicit lifetime bound...
19+
|
20+
LL | T: 'c + 'a,
21+
| ++++
22+
23+
error[E0700]: hidden type for `impl Cap<'b> + Cap<'c>` captures lifetime that does not appear in bounds
24+
--> $DIR/min-choice-reject-ambiguous.rs:39:5
25+
|
26+
LL | fn test_ambiguous<'a, 'b, 'c>(s: &'a u8) -> impl Cap<'b> + Cap<'c>
27+
| -- hidden type `&'a u8` captures the lifetime `'a` as defined here
28+
...
29+
LL | s
30+
| ^
31+
|
32+
help: to declare that `impl Cap<'b> + Cap<'c>` captures `'a`, you can add an explicit `'a` lifetime bound
33+
|
34+
LL | fn test_ambiguous<'a, 'b, 'c>(s: &'a u8) -> impl Cap<'b> + Cap<'c> + 'a
35+
| ++++
36+
37+
error: aborting due to 3 previous errors
38+
39+
Some errors have detailed explanations: E0309, E0700.
40+
For more information about an error, try `rustc --explain E0309`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Assuming that the hidden type in these tests is `&'_#15r u8`,
2+
// we have a member constraint: `'_#15r member ['static, 'a, 'b, 'c]`.
3+
//
4+
// Make sure we pick up the minimum non-ambiguous region among them.
5+
// We will have to exclude `['b, 'c]` because they're incomparable,
6+
// and then we should pick `'a` because we know `'static: 'a`.
7+
8+
// check-pass
9+
10+
trait Cap<'a> {}
11+
impl<T> Cap<'_> for T {}
12+
13+
fn type_test<'a, T: 'a>() -> &'a u8 { &0 }
14+
15+
// Basic test: make sure we don't bail out because 'b and 'c are incomparable.
16+
fn basic<'a, 'b, 'c>() -> impl Cap<'a> + Cap<'b> + Cap<'c>
17+
where
18+
'a: 'b,
19+
'a: 'c,
20+
{
21+
&0
22+
}
23+
24+
// Make sure we don't pick `'static`.
25+
fn test_static<'a, 'b, 'c, T>() -> impl Cap<'a> + Cap<'b> + Cap<'c>
26+
where
27+
'a: 'b,
28+
'a: 'c,
29+
T: 'a,
30+
{
31+
type_test::<'_, T>() // This will fail if we pick 'static
32+
}
33+
34+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Nested impl-traits can impose different member constraints on the same region variable.
2+
3+
// check-fail
4+
5+
trait Cap<'a> {}
6+
impl<T> Cap<'_> for T {}
7+
8+
// Assuming the hidden type is `[&'_#15r u8; 1]`, we have two distinct member constraints:
9+
// - '_#15r member ['static, 'a, 'b] // from outer impl-trait
10+
// - '_#15r member ['static, 'a, 'b] // from inner impl-trait
11+
// To satisfy both we can choose 'a or 'b, so it's a failure due to ambiguity.
12+
fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>
13+
where
14+
's: 'a,
15+
's: 'b,
16+
{
17+
[a]
18+
//~^ E0700
19+
//~| E0700
20+
}
21+
22+
// Same as the above but with late-bound regions.
23+
fn fail_late_bound<'s, 'a, 'b>(
24+
a: &'s u8,
25+
_: &'a &'s u8,
26+
_: &'b &'s u8,
27+
) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>> {
28+
[a]
29+
//~^ E0700
30+
//~| E0700
31+
}
32+
33+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
error[E0700]: hidden type for `impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>` captures lifetime that does not appear in bounds
2+
--> $DIR/nested-impl-trait-fail.rs:17:5
3+
|
4+
LL | fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>
5+
| -- hidden type `[&'s u8; 1]` captures the lifetime `'s` as defined here
6+
...
7+
LL | [a]
8+
| ^^^
9+
|
10+
help: to declare that `impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>` captures `'s`, you can add an explicit `'s` lifetime bound
11+
|
12+
LL | fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>> + 's
13+
| ++++
14+
help: to declare that `impl Cap<'a> + Cap<'b>` captures `'s`, you can add an explicit `'s` lifetime bound
15+
|
16+
LL | fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b> + 's>
17+
| ++++
18+
19+
error[E0700]: hidden type for `impl Cap<'a> + Cap<'b>` captures lifetime that does not appear in bounds
20+
--> $DIR/nested-impl-trait-fail.rs:17:5
21+
|
22+
LL | fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>
23+
| -- hidden type `&'s u8` captures the lifetime `'s` as defined here
24+
...
25+
LL | [a]
26+
| ^^^
27+
|
28+
help: to declare that `impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>` captures `'s`, you can add an explicit `'s` lifetime bound
29+
|
30+
LL | fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>> + 's
31+
| ++++
32+
help: to declare that `impl Cap<'a> + Cap<'b>` captures `'s`, you can add an explicit `'s` lifetime bound
33+
|
34+
LL | fn fail_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b> + 's>
35+
| ++++
36+
37+
error[E0700]: hidden type for `impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>` captures lifetime that does not appear in bounds
38+
--> $DIR/nested-impl-trait-fail.rs:28:5
39+
|
40+
LL | fn fail_late_bound<'s, 'a, 'b>(
41+
| -- hidden type `[&'s u8; 1]` captures the lifetime `'s` as defined here
42+
...
43+
LL | [a]
44+
| ^^^
45+
|
46+
help: to declare that `impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>` captures `'s`, you can add an explicit `'s` lifetime bound
47+
|
48+
LL | ) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>> + 's {
49+
| ++++
50+
help: to declare that `impl Cap<'a> + Cap<'b>` captures `'s`, you can add an explicit `'s` lifetime bound
51+
|
52+
LL | ) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b> + 's> {
53+
| ++++
54+
55+
error[E0700]: hidden type for `impl Cap<'a> + Cap<'b>` captures lifetime that does not appear in bounds
56+
--> $DIR/nested-impl-trait-fail.rs:28:5
57+
|
58+
LL | fn fail_late_bound<'s, 'a, 'b>(
59+
| -- hidden type `&'s u8` captures the lifetime `'s` as defined here
60+
...
61+
LL | [a]
62+
| ^^^
63+
|
64+
help: to declare that `impl IntoIterator<Item = impl Cap<'a> + Cap<'b>>` captures `'s`, you can add an explicit `'s` lifetime bound
65+
|
66+
LL | ) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b>> + 's {
67+
| ++++
68+
help: to declare that `impl Cap<'a> + Cap<'b>` captures `'s`, you can add an explicit `'s` lifetime bound
69+
|
70+
LL | ) -> impl IntoIterator<Item = impl Cap<'a> + Cap<'b> + 's> {
71+
| ++++
72+
73+
error: aborting due to 4 previous errors
74+
75+
For more information about this error, try `rustc --explain E0700`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Nested impl-traits can impose different member constraints on the same region variable.
2+
3+
// check-pass
4+
5+
trait Cap<'a> {}
6+
impl<T> Cap<'_> for T {}
7+
8+
// Assuming the hidden type is `[&'_#15r u8; 1]`, we have two distinct member constraints:
9+
// - '_#15r member ['static, 'a, 'b] // from outer impl-trait
10+
// - '_#15r member ['static, 'a] // from inner impl-trait
11+
// To satisfy both we can only choose 'a.
12+
fn pass_early_bound<'s, 'a, 'b>(a: &'s u8) -> impl IntoIterator<Item = impl Cap<'a>> + Cap<'b>
13+
where
14+
's: 'a,
15+
's: 'b,
16+
{
17+
[a]
18+
}
19+
20+
// Same as the above but with late-bound regions.
21+
fn pass_late_bound<'s, 'a, 'b>(
22+
a: &'s u8,
23+
_: &'a &'s u8,
24+
_: &'b &'s u8,
25+
) -> impl IntoIterator<Item = impl Cap<'a>> + Cap<'b> {
26+
[a]
27+
}
28+
29+
fn main() {}

0 commit comments

Comments
 (0)