From 8c66aab9ca78bc1a619f137f0c8aa38f29d2bb95 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 31 Oct 2025 15:36:41 +0100 Subject: [PATCH 1/2] Do not propogate unnecessary closure constraints + tests. --- .../rustc_borrowck/src/region_infer/mod.rs | 47 +++++++++++++++---- .../closure-prop-issue-104477-case1.rs | 11 +++++ tests/ui/regions/closure-prop-issue-148289.rs | 22 +++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 tests/ui/regions/closure-prop-issue-104477-case1.rs create mode 100644 tests/ui/regions/closure-prop-issue-148289.rs diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index e98c60e633805..3728ecc4400be 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1290,17 +1290,44 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Grow `shorter_fr` until we find some non-local regions. (We // always will.) We'll call them `shorter_fr+` -- they're ever // so slightly larger than `shorter_fr`. - let shorter_fr_plus = + let shorter_fr_plusses = self.universal_region_relations.non_local_upper_bounds(shorter_fr); - debug!("try_propagate_universal_region_error: shorter_fr_plus={:?}", shorter_fr_plus); - for fr in shorter_fr_plus { - // Push the constraint `fr-: shorter_fr+` - propagated_outlives_requirements.push(ClosureOutlivesRequirement { - subject: ClosureOutlivesSubject::Region(fr_minus), - outlived_free_region: fr, - blame_span: blame_constraint.cause.span, - category: blame_constraint.category, - }); + debug!( + "try_propagate_universal_region_error: shorter_fr_plus={:?}", + shorter_fr_plusses + ); + + let fr_static = self.universal_regions().fr_static; + let single_region = shorter_fr_plusses.len() == 1; + + for shorter_fr_plus in shorter_fr_plusses { + // Don't propagate every `fr-: shorter_fr+`. + // A smaller "optimal subset" exists, since full propagation is overly conservative + // and can reject valid code. Consider this small example (`'b: 'a` == `a -> b`) + // were we try to propagate region error `'d: 'a`: + // a --> b --> d + // \ + // \-> c + // Here `shorter_fr_plusses` == `['b, 'c]`. + // Propagating `'d: 'b` is correct and should happen; `'d: 'c` is redundant and can reject valid code. + // We can come closer to this "optimal subset" by checking if the `shorter_fr+` should be outlived by `fr-`. + // NOTE: [] is *not* a valid subset, so we check for that as well. + if single_region + || shorter_fr_plus == fr_static // `fr-: 'static` should be propagated + || self.eval_outlives(fr_minus, shorter_fr_plus) + { + debug!( + "try_propagate_universal_region_error: propagating {:?}: {:?}", + fr_minus, shorter_fr_plus, + ); + // If that's the case, push the constraint `fr-: shorter_fr+` + propagated_outlives_requirements.push(ClosureOutlivesRequirement { + subject: ClosureOutlivesSubject::Region(fr_minus), + outlived_free_region: shorter_fr_plus, + blame_span: blame_constraint.cause.span, + category: blame_constraint.category, + }); + } } return RegionRelationCheckResult::Propagated; } diff --git a/tests/ui/regions/closure-prop-issue-104477-case1.rs b/tests/ui/regions/closure-prop-issue-104477-case1.rs new file mode 100644 index 0000000000000..c51dea4335599 --- /dev/null +++ b/tests/ui/regions/closure-prop-issue-104477-case1.rs @@ -0,0 +1,11 @@ +//@ check-pass + + +struct MyTy<'x, 'a, 'b>(std::cell::Cell<(&'x &'a u8, &'x &'b u8)>); +fn wf(_: T) {} +fn test<'a, 'b>() { + |_: &'a u8, x: MyTy<'_, 'a, 'b>| wf(x); + |x: MyTy<'_, 'a, 'b>, _: &'a u8| wf(x); +} + +fn main(){} diff --git a/tests/ui/regions/closure-prop-issue-148289.rs b/tests/ui/regions/closure-prop-issue-148289.rs new file mode 100644 index 0000000000000..b6acf5655c99d --- /dev/null +++ b/tests/ui/regions/closure-prop-issue-148289.rs @@ -0,0 +1,22 @@ +//@ check-pass + +#[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() {} From a36dc084177e6d554a8496d395ee319442ab3e79 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Mon, 3 Nov 2025 17:03:47 +0100 Subject: [PATCH 2/2] correct filtering of regions + update comment --- .../rustc_borrowck/src/region_infer/mod.rs | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 3728ecc4400be..61a6d6d5b79d9 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1290,44 +1290,37 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Grow `shorter_fr` until we find some non-local regions. (We // always will.) We'll call them `shorter_fr+` -- they're ever // so slightly larger than `shorter_fr`. - let shorter_fr_plusses = + let shorter_fr_plus = self.universal_region_relations.non_local_upper_bounds(shorter_fr); - debug!( - "try_propagate_universal_region_error: shorter_fr_plus={:?}", - shorter_fr_plusses - ); - let fr_static = self.universal_regions().fr_static; - let single_region = shorter_fr_plusses.len() == 1; - - for shorter_fr_plus in shorter_fr_plusses { - // Don't propagate every `fr-: shorter_fr+`. - // A smaller "optimal subset" exists, since full propagation is overly conservative - // and can reject valid code. Consider this small example (`'b: 'a` == `a -> b`) - // were we try to propagate region error `'d: 'a`: - // a --> b --> d - // \ - // \-> c - // Here `shorter_fr_plusses` == `['b, 'c]`. - // Propagating `'d: 'b` is correct and should happen; `'d: 'c` is redundant and can reject valid code. - // We can come closer to this "optimal subset" by checking if the `shorter_fr+` should be outlived by `fr-`. - // NOTE: [] is *not* a valid subset, so we check for that as well. - if single_region - || shorter_fr_plus == fr_static // `fr-: 'static` should be propagated - || self.eval_outlives(fr_minus, shorter_fr_plus) - { - debug!( - "try_propagate_universal_region_error: propagating {:?}: {:?}", - fr_minus, shorter_fr_plus, - ); - // If that's the case, push the constraint `fr-: shorter_fr+` - propagated_outlives_requirements.push(ClosureOutlivesRequirement { - subject: ClosureOutlivesSubject::Region(fr_minus), - outlived_free_region: shorter_fr_plus, - blame_span: blame_constraint.cause.span, - category: blame_constraint.category, - }); - } + // If any of the `shorter_fr+` regions are already outlived by `fr-`, we propagate only those. + // Otherwise, we might incorrectly reject valid code. + // + // 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 `shorter_fr+` to regions already outlived by `fr-`, but if the filter yields an empty set, + // we fall back to the original one. + let subset: Vec<_> = shorter_fr_plus + .iter() + .filter(|&&fr_plus| self.eval_outlives(fr_minus, fr_plus)) + .copied() + .collect(); + let shorter_fr_plus = if subset.is_empty() { shorter_fr_plus } else { subset }; + debug!("try_propagate_universal_region_error: shorter_fr_plus={:?}", shorter_fr_plus); + for fr in shorter_fr_plus { + // Push the constraint `fr-: shorter_fr+` + propagated_outlives_requirements.push(ClosureOutlivesRequirement { + subject: ClosureOutlivesSubject::Region(fr_minus), + outlived_free_region: fr, + blame_span: blame_constraint.cause.span, + category: blame_constraint.category, + }); } return RegionRelationCheckResult::Propagated; }