Auto merge of #148329 - LorrensP-2158466:closure-prop, r=lcnr
Better closure requirement propagation. Fixes rust-lang/rust#148289 Fixes rust-lang/rust#104477 Should unblock rust-lang/rust#148187 When propagating closure requirements we: - no longer try to find a post dominiting region for the list of non-local lower bounds of `longer_fr`. - we filter the list of `longer_fr-: shorter_fr+` to only these constraints between external regions which are required regardless. If no such constraint exists, we fall back to propagating all of them. See the [FCP](https://github.com/rust-lang/rust/pull/148329#issuecomment-3581019253) for more information. r? `@lcnr`
This commit is contained in:
commit
cb79c42008
8 changed files with 205 additions and 82 deletions
|
|
@ -1275,29 +1275,81 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
||||||
shorter_fr: RegionVid,
|
shorter_fr: RegionVid,
|
||||||
propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
|
propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
|
||||||
) -> RegionRelationCheckResult {
|
) -> RegionRelationCheckResult {
|
||||||
if let Some(propagated_outlives_requirements) = propagated_outlives_requirements
|
if let Some(propagated_outlives_requirements) = propagated_outlives_requirements {
|
||||||
// Shrink `longer_fr` until we find a non-local region (if we do).
|
// Shrink `longer_fr` until we find some non-local regions.
|
||||||
// We'll call it `fr-` -- it's ever so slightly smaller than
|
// We'll call them `longer_fr-` -- they are ever so slightly smaller than
|
||||||
// `longer_fr`.
|
// `longer_fr`.
|
||||||
&& let Some(fr_minus) = self.universal_region_relations.non_local_lower_bound(longer_fr)
|
let longer_fr_minus = self.universal_region_relations.non_local_lower_bounds(longer_fr);
|
||||||
{
|
|
||||||
debug!("try_propagate_universal_region_error: fr_minus={:?}", fr_minus);
|
debug!("try_propagate_universal_region_error: fr_minus={:?}", longer_fr_minus);
|
||||||
|
|
||||||
|
// If we don't find a any non-local regions, we should error out as there is nothing
|
||||||
|
// to propagate.
|
||||||
|
if longer_fr_minus.is_empty() {
|
||||||
|
return RegionRelationCheckResult::Error;
|
||||||
|
}
|
||||||
|
|
||||||
let blame_constraint = self
|
let blame_constraint = self
|
||||||
.best_blame_constraint(longer_fr, NllRegionVariableOrigin::FreeRegion, shorter_fr)
|
.best_blame_constraint(longer_fr, NllRegionVariableOrigin::FreeRegion, shorter_fr)
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
// Grow `shorter_fr` until we find some non-local regions. (We
|
// Grow `shorter_fr` until we find some non-local regions.
|
||||||
// always will.) We'll call them `shorter_fr+` -- they're ever
|
// We will always find at least one: `'static`. We'll call
|
||||||
// so slightly larger than `shorter_fr`.
|
// them `shorter_fr+` -- they're ever so slightly larger
|
||||||
|
// than `shorter_fr`.
|
||||||
let shorter_fr_plus =
|
let shorter_fr_plus =
|
||||||
self.universal_region_relations.non_local_upper_bounds(shorter_fr);
|
self.universal_region_relations.non_local_upper_bounds(shorter_fr);
|
||||||
debug!("try_propagate_universal_region_error: shorter_fr_plus={:?}", shorter_fr_plus);
|
debug!("try_propagate_universal_region_error: shorter_fr_plus={:?}", shorter_fr_plus);
|
||||||
for fr in shorter_fr_plus {
|
|
||||||
// Push the constraint `fr-: shorter_fr+`
|
// We then create constraints `longer_fr-: shorter_fr+` that may or may not
|
||||||
|
// be propagated (see below).
|
||||||
|
let mut constraints = vec![];
|
||||||
|
for fr_minus in longer_fr_minus {
|
||||||
|
for shorter_fr_plus in &shorter_fr_plus {
|
||||||
|
constraints.push((fr_minus, *shorter_fr_plus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need to propagate at least one of the constraints for
|
||||||
|
// soundness. However, we want to avoid arbitrary choices here
|
||||||
|
// and currently don't support returning OR constraints.
|
||||||
|
//
|
||||||
|
// If any of the `shorter_fr+` regions are already outlived by `longer_fr-`,
|
||||||
|
// we propagate only those.
|
||||||
|
//
|
||||||
|
// Consider this example (`'b: 'a` == `a -> b`), where we try to propagate `'d: 'a`:
|
||||||
|
// a --> b --> d
|
||||||
|
// \
|
||||||
|
// \-> c
|
||||||
|
// Here, `shorter_fr+` of `'a` == `['b, 'c]`.
|
||||||
|
// Propagating `'d: 'b` is correct and should occur; `'d: 'c` is redundant because of
|
||||||
|
// `'d: 'b` and could reject valid code.
|
||||||
|
//
|
||||||
|
// So we filter the constraints to regions already outlived by `longer_fr-`, but if
|
||||||
|
// the filter yields an empty set, we fall back to the original one.
|
||||||
|
let subset: Vec<_> = constraints
|
||||||
|
.iter()
|
||||||
|
.filter(|&&(fr_minus, shorter_fr_plus)| {
|
||||||
|
self.eval_outlives(fr_minus, shorter_fr_plus)
|
||||||
|
})
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
let propagated_constraints = if subset.is_empty() { constraints } else { subset };
|
||||||
|
debug!(
|
||||||
|
"try_propagate_universal_region_error: constraints={:?}",
|
||||||
|
propagated_constraints
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!propagated_constraints.is_empty(),
|
||||||
|
"Expected at least one constraint to propagate here"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (fr_minus, fr_plus) in propagated_constraints {
|
||||||
|
// Push the constraint `long_fr-: shorter_fr+`
|
||||||
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
|
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
|
||||||
subject: ClosureOutlivesSubject::Region(fr_minus),
|
subject: ClosureOutlivesSubject::Region(fr_minus),
|
||||||
outlived_free_region: fr,
|
outlived_free_region: fr_plus,
|
||||||
blame_span: blame_constraint.cause.span,
|
blame_span: blame_constraint.cause.span,
|
||||||
category: blame_constraint.category,
|
category: blame_constraint.category,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -94,28 +94,10 @@ impl UniversalRegionRelations<'_> {
|
||||||
/// words, returns the largest (*) known region `fr1` that (a) is
|
/// words, returns the largest (*) known region `fr1` that (a) is
|
||||||
/// outlived by `fr` and (b) is not local.
|
/// outlived by `fr` and (b) is not local.
|
||||||
///
|
///
|
||||||
/// (*) If there are multiple competing choices, we pick the "postdominating"
|
/// (*) If there are multiple competing choices, we return all of them.
|
||||||
/// one. See `TransitiveRelation::postdom_upper_bound` for details.
|
pub(crate) fn non_local_lower_bounds(&self, fr: RegionVid) -> Vec<RegionVid> {
|
||||||
pub(crate) fn non_local_lower_bound(&self, fr: RegionVid) -> Option<RegionVid> {
|
|
||||||
debug!("non_local_lower_bound(fr={:?})", fr);
|
debug!("non_local_lower_bound(fr={:?})", fr);
|
||||||
let lower_bounds = self.non_local_bounds(&self.outlives, fr);
|
self.non_local_bounds(&self.outlives, fr)
|
||||||
|
|
||||||
// In case we find more than one, reduce to one for
|
|
||||||
// convenience. This is to prevent us from generating more
|
|
||||||
// complex constraints, but it will cause spurious errors.
|
|
||||||
let post_dom = self.outlives.mutual_immediate_postdominator(lower_bounds);
|
|
||||||
|
|
||||||
debug!("non_local_bound: post_dom={:?}", post_dom);
|
|
||||||
|
|
||||||
post_dom.and_then(|post_dom| {
|
|
||||||
// If the mutual immediate postdom is not local, then
|
|
||||||
// there is no non-local result we can return.
|
|
||||||
if !self.universal_regions.is_local_free_region(post_dom) {
|
|
||||||
Some(post_dom)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for `non_local_upper_bounds` and `non_local_lower_bounds`.
|
/// Helper for `non_local_upper_bounds` and `non_local_lower_bounds`.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// Test where we fail to approximate due to demanding a postdom
|
// Test that we can propagate multiple region errors for closure constraints
|
||||||
// relationship between our upper bounds.
|
// where the longer region has multiple non-local lower bounds without any postdominating one.
|
||||||
|
|
||||||
//@ compile-flags:-Zverbose-internals
|
//@ compile-flags:-Zverbose-internals
|
||||||
|
|
||||||
#![feature(rustc_attrs)]
|
#![feature(rustc_attrs)]
|
||||||
|
|
@ -13,9 +12,8 @@ use std::cell::Cell;
|
||||||
// 'x: 'b
|
// 'x: 'b
|
||||||
// 'c: 'y
|
// 'c: 'y
|
||||||
//
|
//
|
||||||
// we have to prove that `'x: 'y`. We currently can only approximate
|
// we have to prove that `'x: 'y`. We find non-local lower bounds of 'x to be 'a and 'b and
|
||||||
// via a postdominator -- hence we fail to choose between `'a` and
|
// non-local upper bound of 'y to be 'c. So we propagate `'b: 'c` and `'a: 'c`.
|
||||||
// `'b` here and report the error in the closure.
|
|
||||||
fn establish_relationships<'a, 'b, 'c, F>(
|
fn establish_relationships<'a, 'b, 'c, F>(
|
||||||
_cell_a: Cell<&'a u32>,
|
_cell_a: Cell<&'a u32>,
|
||||||
_cell_b: Cell<&'b u32>,
|
_cell_b: Cell<&'b u32>,
|
||||||
|
|
@ -36,6 +34,8 @@ fn demand_y<'x, 'y>(_cell_x: Cell<&'x u32>, _cell_y: Cell<&'y u32>, _y: &'y u32)
|
||||||
|
|
||||||
#[rustc_regions]
|
#[rustc_regions]
|
||||||
fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell<&'c u32>) {
|
fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell<&'c u32>) {
|
||||||
|
//~vv ERROR lifetime may not live long enough
|
||||||
|
//~v ERROR lifetime may not live long enough
|
||||||
establish_relationships(
|
establish_relationships(
|
||||||
cell_a,
|
cell_a,
|
||||||
cell_b,
|
cell_b,
|
||||||
|
|
@ -43,7 +43,7 @@ fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell
|
||||||
|_outlives1, _outlives2, _outlives3, x, y| {
|
|_outlives1, _outlives2, _outlives3, x, y| {
|
||||||
// Only works if 'x: 'y:
|
// Only works if 'x: 'y:
|
||||||
let p = x.get();
|
let p = x.get();
|
||||||
demand_y(x, y, p) //~ ERROR
|
demand_y(x, y, p)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
note: external requirements
|
||||||
|
--> $DIR/propagate-approximated-both-lower-bounds.rs:43:9
|
||||||
|
|
|
||||||
|
LL | |_outlives1, _outlives2, _outlives3, x, y| {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: defining type: supply::{closure#0} with closure args [
|
||||||
|
i16,
|
||||||
|
for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 &'^0 u32>, std::cell::Cell<&'?2 &'^0 u32>, std::cell::Cell<&'^1 &'?3 u32>, std::cell::Cell<&'^0 u32>, std::cell::Cell<&'^1 u32>)),
|
||||||
|
(),
|
||||||
|
]
|
||||||
|
= note: late-bound region is '?7
|
||||||
|
= note: late-bound region is '?8
|
||||||
|
= note: late-bound region is '?4
|
||||||
|
= note: late-bound region is '?5
|
||||||
|
= note: late-bound region is '?6
|
||||||
|
= note: number of external vids: 7
|
||||||
|
= note: where '?2: '?3
|
||||||
|
= note: where '?1: '?3
|
||||||
|
|
||||||
|
note: no external requirements
|
||||||
|
--> $DIR/propagate-approximated-both-lower-bounds.rs:36:1
|
||||||
|
|
|
||||||
|
LL | fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell<&'c u32>) {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: defining type: supply
|
||||||
|
|
||||||
|
error: lifetime may not live long enough
|
||||||
|
--> $DIR/propagate-approximated-both-lower-bounds.rs:39:5
|
||||||
|
|
|
||||||
|
LL | fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell<&'c u32>) {
|
||||||
|
| -- -- lifetime `'c` defined here
|
||||||
|
| |
|
||||||
|
| lifetime `'a` defined here
|
||||||
|
...
|
||||||
|
LL | / establish_relationships(
|
||||||
|
LL | | cell_a,
|
||||||
|
LL | | cell_b,
|
||||||
|
LL | | cell_c,
|
||||||
|
... |
|
||||||
|
LL | | },
|
||||||
|
LL | | );
|
||||||
|
| |_____^ argument requires that `'a` must outlive `'c`
|
||||||
|
|
|
||||||
|
= help: consider adding the following bound: `'a: 'c`
|
||||||
|
= note: requirement occurs because of the type `Cell<&'?10 u32>`, which makes the generic argument `&'?10 u32` invariant
|
||||||
|
= note: the struct `Cell<T>` is invariant over the parameter `T`
|
||||||
|
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
|
||||||
|
|
||||||
|
error: lifetime may not live long enough
|
||||||
|
--> $DIR/propagate-approximated-both-lower-bounds.rs:39:5
|
||||||
|
|
|
||||||
|
LL | fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell<&'c u32>) {
|
||||||
|
| -- -- lifetime `'c` defined here
|
||||||
|
| |
|
||||||
|
| lifetime `'b` defined here
|
||||||
|
...
|
||||||
|
LL | / establish_relationships(
|
||||||
|
LL | | cell_a,
|
||||||
|
LL | | cell_b,
|
||||||
|
LL | | cell_c,
|
||||||
|
... |
|
||||||
|
LL | | },
|
||||||
|
LL | | );
|
||||||
|
| |_____^ argument requires that `'b` must outlive `'c`
|
||||||
|
|
|
||||||
|
= help: consider adding the following bound: `'b: 'c`
|
||||||
|
= note: requirement occurs because of the type `Cell<&'?10 u32>`, which makes the generic argument `&'?10 u32` invariant
|
||||||
|
= note: the struct `Cell<T>` is invariant over the parameter `T`
|
||||||
|
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
|
||||||
|
|
||||||
|
help: the following changes may resolve your lifetime errors
|
||||||
|
|
|
||||||
|
= help: add bound `'a: 'c`
|
||||||
|
= help: add bound `'b: 'c`
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
note: no external requirements
|
|
||||||
--> $DIR/propagate-approximated-fail-no-postdom.rs:43:9
|
|
||||||
|
|
|
||||||
LL | |_outlives1, _outlives2, _outlives3, x, y| {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: defining type: supply::{closure#0} with closure args [
|
|
||||||
i16,
|
|
||||||
for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 &'^0 u32>, std::cell::Cell<&'?2 &'^0 u32>, std::cell::Cell<&'^1 &'?3 u32>, std::cell::Cell<&'^0 u32>, std::cell::Cell<&'^1 u32>)),
|
|
||||||
(),
|
|
||||||
]
|
|
||||||
= note: late-bound region is '?7
|
|
||||||
= note: late-bound region is '?8
|
|
||||||
= note: late-bound region is '?4
|
|
||||||
= note: late-bound region is '?5
|
|
||||||
= note: late-bound region is '?6
|
|
||||||
|
|
||||||
error: lifetime may not live long enough
|
|
||||||
--> $DIR/propagate-approximated-fail-no-postdom.rs:46:13
|
|
||||||
|
|
|
||||||
LL | |_outlives1, _outlives2, _outlives3, x, y| {
|
|
||||||
| ---------- ---------- has type `Cell<&'2 &'?3 u32>`
|
|
||||||
| |
|
|
||||||
| has type `Cell<&'?1 &'1 u32>`
|
|
||||||
...
|
|
||||||
LL | demand_y(x, y, p)
|
|
||||||
| ^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`
|
|
||||||
|
|
|
||||||
= note: requirement occurs because of the type `Cell<&'?34 u32>`, which makes the generic argument `&'?34 u32` invariant
|
|
||||||
= note: the struct `Cell<T>` is invariant over the parameter `T`
|
|
||||||
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
|
|
||||||
|
|
||||||
note: no external requirements
|
|
||||||
--> $DIR/propagate-approximated-fail-no-postdom.rs:38:1
|
|
||||||
|
|
|
||||||
LL | fn supply<'a, 'b, 'c>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>, cell_c: Cell<&'c u32>) {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: defining type: supply
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
|
||||||
|
|
||||||
14
tests/ui/regions/closure-prop-issue-104477-case1.rs
Normal file
14
tests/ui/regions/closure-prop-issue-104477-case1.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
//@ check-pass
|
||||||
|
// This checks that the compiler does not require that 'a: 'b. '_ has 'a and 'b as non-local
|
||||||
|
// upper bounds, but the compiler should not propagate 'a: 'b OR 'b: 'a when checking
|
||||||
|
// the closures. If it did, this would fail to compile, eventhough it's a valid program.
|
||||||
|
// PR #148329 explains this in detail.
|
||||||
|
|
||||||
|
struct MyTy<'x, 'a, 'b>(std::cell::Cell<(&'x &'a u8, &'x &'b u8)>);
|
||||||
|
fn wf<T>(_: T) {}
|
||||||
|
fn test<'a, 'b>() {
|
||||||
|
|_: &'a u8, x: MyTy<'_, 'a, 'b>| wf(x);
|
||||||
|
|x: MyTy<'_, 'a, 'b>, _: &'a u8| wf(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(){}
|
||||||
12
tests/ui/regions/closure-prop-issue-104477-case2.rs
Normal file
12
tests/ui/regions/closure-prop-issue-104477-case2.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
//@ check-pass
|
||||||
|
// This test checks that the compiler propagates outlives requirements for both
|
||||||
|
// non-local lower bounds ['a, 'b] of '_, instead of conservatively finding a post-dominiting one
|
||||||
|
// from those 2.
|
||||||
|
|
||||||
|
struct MyTy<'a, 'b, 'x>(std::cell::Cell<(&'a &'x str, &'b &'x str)>);
|
||||||
|
fn wf<T>(_: T) {}
|
||||||
|
fn test<'a, 'b, 'x>() {
|
||||||
|
|x: MyTy<'a, 'b, '_>| wf(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
26
tests/ui/regions/closure-prop-issue-148289.rs
Normal file
26
tests/ui/regions/closure-prop-issue-148289.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
//@ check-pass
|
||||||
|
// This test checks that the compiler does not propagate 'd: 'c when propagating region errors
|
||||||
|
// for the closure argument. If it did, this would fail to compile, eventhough it's a valid program.
|
||||||
|
// It should only propagate 'd: 'b.
|
||||||
|
// PR #148329 explains this in detail.
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Inv<'a>(*mut &'a ());
|
||||||
|
impl<'a> Inv<'a> {
|
||||||
|
fn outlived_by<'b: 'a>(self, _: Inv<'b>) {}
|
||||||
|
}
|
||||||
|
struct OutlivedBy<'a, 'b: 'a>(Inv<'a>, Inv<'b>);
|
||||||
|
|
||||||
|
fn closure_arg<'b, 'c, 'd>(
|
||||||
|
_: impl for<'a> FnOnce(Inv<'a>, OutlivedBy<'a, 'b>, OutlivedBy<'a, 'c>, Inv<'d>),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn foo<'b, 'c, 'd: 'b>() {
|
||||||
|
closure_arg::<'b, 'c, 'd>(|a, b, c, d| {
|
||||||
|
a.outlived_by(b.1);
|
||||||
|
a.outlived_by(c.1);
|
||||||
|
b.1.outlived_by(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue