From d1fc1a0e071d935ede2404dd77445dae706aa49a Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 13 Mar 2026 17:05:32 +0100 Subject: [PATCH 1/5] Implement fix in `try_promote_type_test_subject` + add a test. Fix in question: We only propagate `T: 'ub` requirements when 'ub actually outlives the lower bounds of the type test. If none of the non-local upper bounds outlive it, then we propagate all of them. --- .../rustc_borrowck/src/region_infer/mod.rs | 28 ++++++++++++++++++- .../type-test-issue-154267.rs | 25 +++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/ui/nll/closure-requirements/type-test-issue-154267.rs diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 5cdda777723b3..06b9dec7d0412 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -713,9 +713,35 @@ impl<'tcx> RegionInferenceContext<'tcx> { for ur in self.scc_values.universal_regions_outlived_by(r_scc) { found_outlived_universal_region = true; debug!("universal_region_outlived_by ur={:?}", ur); - let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); + let mut non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); debug!(?non_local_ub); + // We don't need to propagate every `T: 'ub` for soundness: + // Say we have the following outlives constraints given (`'b: 'a` == `a -> b`): + // + // a + // \ + // -> c + // / + // b + // + // And we are doing the type test `T: 'a`. + // + // The `lower_bound_universal_regions` of `'a` are `['a, 'c]`. The `upper_bounds` of `'a` + // is `['a]`, so we propagate `T: 'a`. + // The `upper_bounds` of `'c` are `['a, 'b]`, propagating `T: 'a` is correct; + // `T: 'b` is redundant because it provides no value to proving `T: 'a`. + // + // So we filter the set of upper_bounds to regions already outliving `lower_bound`, + // but if this subset is empty, we fall back to the original one. + let subset: Vec<_> = non_local_ub + .iter() + .copied() + .filter(|ub| self.eval_outlives(*ub, lower_bound)) + .collect(); + non_local_ub = if subset.is_empty() { non_local_ub } else { subset }; + debug!(?non_local_ub, "upper_bounds after filtering"); + // This is slightly too conservative. To show T: '1, given `'2: '1` // and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to // avoid potential non-determinism we approximate this by requiring diff --git a/tests/ui/nll/closure-requirements/type-test-issue-154267.rs b/tests/ui/nll/closure-requirements/type-test-issue-154267.rs new file mode 100644 index 0000000000000..836802ecd481e --- /dev/null +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267.rs @@ -0,0 +1,25 @@ +//@ check-pass +// This test checks that the compiler doesn't propagate `T: 'b` during the `T: 'a` type test. +// If it did, it would fail to compile, even though the program is sound. + +struct Arg<'a: 'c, 'b: 'c, 'c, T> { + field: *mut (&'a (), &'b (), &'c (), T), +} + +impl<'a, 'b, 'c, T> Arg<'a, 'b, 'c, T> { + fn constrain(self) + where + T: 'a, + { + } +} + +fn takes_closure<'a, 'b, T>(_: impl for<'c> FnOnce(Arg<'a, 'b, 'c, T>)) {} + +// We have `'a: 'c` and `'b: 'c`, requiring `T: 'a` in `constrain` should not need +// `T: 'b` here. +fn error<'a, 'b, T: 'a>() { + takes_closure::<'a, 'b, T>(|arg| arg.constrain()); +} + +fn main() {} From d27d60271ab22a5ab6fe4763a7393acac38faf72 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Thu, 23 Apr 2026 22:15:38 +0200 Subject: [PATCH 2/5] filter redundant requirements through unit propagation --- .../rustc_borrowck/src/region_infer/mod.rs | 142 +++++++++++++----- .../unconstrained-closure-lifetime-generic.rs | 2 - ...onstrained-closure-lifetime-generic.stderr | 32 +--- .../type-test-issue-154267.rs | 1 + 4 files changed, 107 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 06b9dec7d0412..0668fe32cc070 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -589,7 +589,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn check_type_tests( &self, infcx: &InferCtxt<'tcx>, - mut propagated_outlives_requirements: Option<&mut Vec>>, + propagated_outlives_requirements: Option<&mut Vec>>, errors_buffer: &mut RegionErrors<'tcx>, ) { let tcx = infcx.tcx; @@ -599,6 +599,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); + let mut conjunctive_propagated_outlives_requirements = + propagated_outlives_requirements.is_some().then_some(vec![]); + for type_test in &self.type_tests { debug!("check_type_test: {:?}", type_test); @@ -612,8 +615,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { continue; } - if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements - && self.try_promote_type_test(infcx, type_test, propagated_outlives_requirements) + if let Some(conjunctive_propagated_outlives_requirements) = + &mut conjunctive_propagated_outlives_requirements + && self.try_promote_type_test( + infcx, + type_test, + conjunctive_propagated_outlives_requirements, + ) { continue; } @@ -637,6 +645,69 @@ impl<'tcx> RegionInferenceContext<'tcx> { errors_buffer.push(RegionErrorKind::TypeTestError { type_test: type_test.clone() }); } } + + if let Some(mut conjunctive_requirements) = conjunctive_propagated_outlives_requirements + && !conjunctive_requirements.is_empty() + { + // We can simplify this list of list of requirements. + // + // Say we did some number of type tests and it results in following requirements: + // + // R1: (T: 'a OR T: 'b) + // R2: (T: 'a) + // + // * See `try_promote_type_test` below on why we obtain OR requirements implicitly. + // + // Full requirement is then: R1 AND R2. *BUT*, we can remove R1 entirely, because we already + // require `T: 'a`, which implies `T:'a OR T: 'b`, making R1 redundant. + // + // The requirements can be seen as a boolean conjunctive normal form expression: + // Treat a requirement `T: 'region` as a boolean value, then this problem is (almost) + // equivalent to "Unit Propagation". However, this problem we are trying to solve is much + // simpler: Unit Propagation considers any form of subexpression, even containing negation + // of values, making it a multi-pass algorithm. The only subexpressions we encounter are of + // the form (R1 OR ... OR RN), thus if even on R is required on their own (a unit), this + // whole subexpression can be removed. + // + // So we can filter redundant OR requirements with the following algorithm: + // Collect every Unit requirement. Then for every other requirement, if one of the units is + // contained within them we can remove the entire requirement from the list. + + fn requirement_key<'a>(subject: ClosureOutlivesRequirement<'a>) -> (Ty<'a>, RegionVid) { + let ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy { inner: ty }) = + subject.subject + else { + unreachable!("ClosureOutliveSubject of a type test is always a Ty"); + }; + (ty, subject.outlived_free_region) + } + + let units: Vec<_> = conjunctive_requirements + .iter() + .filter_map(|r| { + let [r] = r.as_slice() else { return None }; + Some(requirement_key(*r)) + }) + .collect(); + + // Remove the `or_requirement`s that contain any of the unit requirements. + conjunctive_requirements.retain(|or_requirement| { + or_requirement.len() == 1 + || !or_requirement.iter().any(|r| { + let key = requirement_key(*r); + units.contains(&key) + }) + }); + + assert!( + !conjunctive_requirements.is_empty(), + "It should not be possible to remove every requirement." + ); + // Propagate all requirements as is. + propagated_outlives_requirements + .expect("conjunctive_requirements is `Some`, so this should be as well") + .extend(conjunctive_requirements.into_iter().flatten()); + } } /// Invoked when we have some type-test (e.g., `T: 'X`) that we cannot @@ -668,7 +739,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { &self, infcx: &InferCtxt<'tcx>, type_test: &TypeTest<'tcx>, - propagated_outlives_requirements: &mut Vec>, + propagated_outlives_requirements: &mut Vec>>, ) -> bool { let tcx = infcx.tcx; let TypeTest { generic_kind, lower_bound, span: blame_span, verify_bound: _ } = *type_test; @@ -694,58 +765,52 @@ impl<'tcx> RegionInferenceContext<'tcx> { if let Some(p) = self.scc_values.placeholders_contained_in(r_scc).next() { debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p); let static_r = self.universal_regions().fr_static; - propagated_outlives_requirements.push(ClosureOutlivesRequirement { + propagated_outlives_requirements.push(vec![ClosureOutlivesRequirement { subject, outlived_free_region: static_r, blame_span, category: ConstraintCategory::Boring, - }); + }]); // we can return here -- the code below might push add'l constraints // but they would all be weaker than this one. return true; } - // For each region outlived by lower_bound find a non-local, - // universal region (it may be the same region) and add it to - // `ClosureOutlivesRequirement`. + let universal_regions: Vec<_> = + self.scc_values.universal_regions_outlived_by(r_scc).collect(); + debug!(?universal_regions); + + // Filter to only the "minimal" universal regions: + // Drop any region `a` that strictly outlives another region `b`. + let minimal_universal_regions: Vec<_> = universal_regions + .iter() + .copied() + .filter(|&a| { + !universal_regions.iter().copied().any(|b| { + !self.universal_region_relations.outlives(a, b) + && self.universal_region_relations.outlives(b, a) + }) + }) + .collect(); + + assert!( + !minimal_universal_regions.is_empty(), + "There should always be at least 1 minimal region" + ); + let mut found_outlived_universal_region = false; - for ur in self.scc_values.universal_regions_outlived_by(r_scc) { + for ur in minimal_universal_regions { found_outlived_universal_region = true; debug!("universal_region_outlived_by ur={:?}", ur); - let mut non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); + let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); debug!(?non_local_ub); - // We don't need to propagate every `T: 'ub` for soundness: - // Say we have the following outlives constraints given (`'b: 'a` == `a -> b`): - // - // a - // \ - // -> c - // / - // b - // - // And we are doing the type test `T: 'a`. - // - // The `lower_bound_universal_regions` of `'a` are `['a, 'c]`. The `upper_bounds` of `'a` - // is `['a]`, so we propagate `T: 'a`. - // The `upper_bounds` of `'c` are `['a, 'b]`, propagating `T: 'a` is correct; - // `T: 'b` is redundant because it provides no value to proving `T: 'a`. - // - // So we filter the set of upper_bounds to regions already outliving `lower_bound`, - // but if this subset is empty, we fall back to the original one. - let subset: Vec<_> = non_local_ub - .iter() - .copied() - .filter(|ub| self.eval_outlives(*ub, lower_bound)) - .collect(); - non_local_ub = if subset.is_empty() { non_local_ub } else { subset }; - debug!(?non_local_ub, "upper_bounds after filtering"); - // This is slightly too conservative. To show T: '1, given `'2: '1` // and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to // avoid potential non-determinism we approximate this by requiring // T: '1 and T: '2. + let mut or_requirements = Vec::with_capacity(non_local_ub.len()); for upper_bound in non_local_ub { debug_assert!(self.universal_regions().is_universal_region(upper_bound)); debug_assert!(!self.universal_regions().is_local_free_region(upper_bound)); @@ -757,8 +822,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { category: ConstraintCategory::Boring, }; debug!(?requirement, "adding closure requirement"); - propagated_outlives_requirements.push(requirement); + or_requirements.push(requirement); } + propagated_outlives_requirements.push(or_requirements); } // If we succeed to promote the subject, i.e. it only contains non-local regions, // and fail to prove the type test inside of the closure, the `lower_bound` has to diff --git a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs index 4fdf5470feac6..dcba2081cfb33 100644 --- a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs +++ b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs @@ -13,8 +13,6 @@ impl Foo { //~| ERROR the parameter type `impl for<'a> Fn(&'a usize) -> Box` may not live long enough //~| ERROR the parameter type `impl for<'a> Fn(&'a usize) -> Box` may not live long enough //~| ERROR the parameter type `I` may not live long enough - //~| ERROR the parameter type `I` may not live long enough - //~| ERROR the parameter type `I` may not live long enough //~| ERROR `f` does not live long enough } } diff --git a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr index df86ce79f09c7..9befce0e94dc3 100644 --- a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr +++ b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr @@ -69,34 +69,6 @@ help: consider adding an explicit lifetime bound LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { | +++++++++ -error[E0310]: the parameter type `I` may not live long enough - --> $DIR/unconstrained-closure-lifetime-generic.rs:10:35 - | -LL | self.bar = Box::new(|baz| Box::new(f(baz))); - | ^^^^^^^^^^^^^^^^ - | | - | the parameter type `I` must be valid for the static lifetime... - | ...so that the type `I` will meet its required lifetime bounds - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit lifetime bound - | -LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | +++++++++ - -error[E0311]: the parameter type `I` may not live long enough - --> $DIR/unconstrained-closure-lifetime-generic.rs:10:35 - | -LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | --------- the parameter type `I` must be valid for the anonymous lifetime defined here... -LL | self.bar = Box::new(|baz| Box::new(f(baz))); - | ^^^^^^^^^^^^^^^^ ...so that the type `I` will meet its required lifetime bounds - | -help: consider adding an explicit lifetime bound - | -LL | pub fn ack<'a, I: 'a>(&'a mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | +++ ++++ ++ - error[E0597]: `f` does not live long enough --> $DIR/unconstrained-closure-lifetime-generic.rs:10:44 | @@ -113,7 +85,7 @@ LL | } | = note: due to object lifetime defaults, `Box Fn(&'a usize) -> Box<(dyn Any + 'a)>>` actually means `Box<(dyn for<'a> Fn(&'a usize) -> Box<(dyn Any + 'a)> + 'static)>` -error: aborting due to 8 previous errors +error: aborting due to 6 previous errors -Some errors have detailed explanations: E0310, E0311, E0597. +Some errors have detailed explanations: E0310, E0597. For more information about an error, try `rustc --explain E0310`. diff --git a/tests/ui/nll/closure-requirements/type-test-issue-154267.rs b/tests/ui/nll/closure-requirements/type-test-issue-154267.rs index 836802ecd481e..6137f38886437 100644 --- a/tests/ui/nll/closure-requirements/type-test-issue-154267.rs +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267.rs @@ -10,6 +10,7 @@ impl<'a, 'b, 'c, T> Arg<'a, 'b, 'c, T> { fn constrain(self) where T: 'a, + T: 'c, { } } From b06d93647032cfed4a707f8549ce011f724523f2 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 24 Apr 2026 12:32:23 +0200 Subject: [PATCH 3/5] bless test --- tests/ui/consts/issue-102117.rs | 1 - tests/ui/consts/issue-102117.stderr | 17 +---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/tests/ui/consts/issue-102117.rs b/tests/ui/consts/issue-102117.rs index b7955283a8d81..141dbfb4e935e 100644 --- a/tests/ui/consts/issue-102117.rs +++ b/tests/ui/consts/issue-102117.rs @@ -16,7 +16,6 @@ impl VTable { layout: Layout::new::(), type_id: TypeId::of::(), //~^ ERROR the parameter type `T` may not live long enough - //~| ERROR the parameter type `T` may not live long enough drop_in_place: unsafe { transmute::(drop_in_place::) }, diff --git a/tests/ui/consts/issue-102117.stderr b/tests/ui/consts/issue-102117.stderr index 4440680bd7f19..56bc72621fc16 100644 --- a/tests/ui/consts/issue-102117.stderr +++ b/tests/ui/consts/issue-102117.stderr @@ -12,21 +12,6 @@ help: consider adding an explicit lifetime bound LL | pub fn new() -> &'static Self { | +++++++++ -error[E0310]: the parameter type `T` may not live long enough - --> $DIR/issue-102117.rs:17:26 - | -LL | type_id: TypeId::of::(), - | ^^^^^^^^^^^^^^^ - | | - | the parameter type `T` must be valid for the static lifetime... - | ...so that the type `T` will meet its required lifetime bounds - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit lifetime bound - | -LL | pub fn new() -> &'static Self { - | +++++++++ - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0310`. From 28df9b0206224498fa2fa73dd9b5c6ca06bdb241 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Thu, 30 Apr 2026 12:13:17 +0200 Subject: [PATCH 4/5] address review, without the stronger redundancy check --- .../rustc_borrowck/src/region_infer/mod.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 0668fe32cc070..15191e0aee0b7 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -599,7 +599,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); - let mut conjunctive_propagated_outlives_requirements = + // Each type test introduces one or more OR-constraints (e.g. T: 'a OR T: 'b), + // where at least one option in each constraint must be satisfied. All such + // constraints must be satisfied simultaneously: i.e., they form a conjunction (AND). + // We'll use this conjunctive requirement later on. + let mut conjunctive_propagated_outlives_requirement = propagated_outlives_requirements.is_some().then_some(vec![]); for type_test in &self.type_tests { @@ -616,7 +620,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } if let Some(conjunctive_propagated_outlives_requirements) = - &mut conjunctive_propagated_outlives_requirements + &mut conjunctive_propagated_outlives_requirement && self.try_promote_type_test( infcx, type_test, @@ -646,8 +650,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - if let Some(mut conjunctive_requirements) = conjunctive_propagated_outlives_requirements - && !conjunctive_requirements.is_empty() + if let Some(mut conjunctive_requirement) = conjunctive_propagated_outlives_requirement + && !conjunctive_requirement.is_empty() { // We can simplify this list of list of requirements. // @@ -682,7 +686,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { (ty, subject.outlived_free_region) } - let units: Vec<_> = conjunctive_requirements + let units: Vec<_> = conjunctive_requirement .iter() .filter_map(|r| { let [r] = r.as_slice() else { return None }; @@ -691,7 +695,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { .collect(); // Remove the `or_requirement`s that contain any of the unit requirements. - conjunctive_requirements.retain(|or_requirement| { + conjunctive_requirement.retain(|or_requirement| { or_requirement.len() == 1 || !or_requirement.iter().any(|r| { let key = requirement_key(*r); @@ -700,13 +704,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { }); assert!( - !conjunctive_requirements.is_empty(), + !conjunctive_requirement.is_empty(), "It should not be possible to remove every requirement." ); // Propagate all requirements as is. propagated_outlives_requirements .expect("conjunctive_requirements is `Some`, so this should be as well") - .extend(conjunctive_requirements.into_iter().flatten()); + .extend(conjunctive_requirement.into_iter().flatten()); } } @@ -799,9 +803,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { "There should always be at least 1 minimal region" ); - let mut found_outlived_universal_region = false; for ur in minimal_universal_regions { - found_outlived_universal_region = true; debug!("universal_region_outlived_by ur={:?}", ur); let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); debug!(?non_local_ub); @@ -826,11 +828,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { } propagated_outlives_requirements.push(or_requirements); } - // If we succeed to promote the subject, i.e. it only contains non-local regions, - // and fail to prove the type test inside of the closure, the `lower_bound` has to - // also be at least as large as some universal region, as the type test is otherwise - // trivial. - assert!(found_outlived_universal_region); true } From 99a4359b6e882ef86114dced8184f36caaf92fe7 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Thu, 30 Apr 2026 12:42:06 +0200 Subject: [PATCH 5/5] stronger redundancy check --- .../rustc_borrowck/src/region_infer/mod.rs | 24 +++++++++++++++---- .../type-test-issue-154267-stronger.rs | 22 +++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 15191e0aee0b7..bb02d41e75a83 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -673,9 +673,20 @@ impl<'tcx> RegionInferenceContext<'tcx> { // the form (R1 OR ... OR RN), thus if even on R is required on their own (a unit), this // whole subexpression can be removed. // + // Because of the outlives relations, we can actually have a stronger redundancy check, + // say we have following requirements that create a conjunctive requirement: + // R1: T: 'a + // R2: T: 'b OR T: 'c + // R: R1 AND R2 + // + // And we have we an assumption in our environment that `'a: 'b`, we can thus remove R2 + // as well. `T: 'b` is implied by `T: 'a` because of the assumption `'a: 'b`: + // T -> 'a -> 'b + // // So we can filter redundant OR requirements with the following algorithm: - // Collect every Unit requirement. Then for every other requirement, if one of the units is - // contained within them we can remove the entire requirement from the list. + // Collect every Unit requirement. Then for every OR requirement, loop over its + // individual requirements and if the region is outlived by the region of one of the + // units, remove the entire OR requirement. fn requirement_key<'a>(subject: ClosureOutlivesRequirement<'a>) -> (Ty<'a>, RegionVid) { let ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy { inner: ty }) = @@ -698,8 +709,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { conjunctive_requirement.retain(|or_requirement| { or_requirement.len() == 1 || !or_requirement.iter().any(|r| { - let key = requirement_key(*r); - units.contains(&key) + let (ty, region) = requirement_key(*r); + units.iter().any(|&(unit_subj, unit_region)| { + // Same type, and the unit region outlives the disjunct region, + // meaning T: unit_region implies T: region. + unit_subj == ty + && self.universal_region_relations.outlives(unit_region, region) + }) }) }); diff --git a/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs b/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs new file mode 100644 index 0000000000000..a8956defb56e4 --- /dev/null +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs @@ -0,0 +1,22 @@ +//@ check-pass +// +// Check that the compiler correctly detects that we don't need to propagate `T: 'b` and `T: 'c` +// when doing the type tests of `constrain`. This is a stronger version of issue-154267. + +struct Arg<'a: 'b, 'b: 'd, 'c: 'd, 'd, T> { + field: *mut (&'a (), &'b (), &'c (), &'d (), T), +} +impl<'a: 'b, 'b, 'c, 'd, T> Arg<'a, 'b, 'c, 'd, T> { + fn constrain(self) + where + T: 'a, + T: 'd, + { + } +} +fn takes_closure<'a, 'b, 'c, T>(_: impl for<'d> FnOnce(Arg<'a, 'b, 'c, 'd, T>)) {} + +fn error<'a, 'b, 'c, T: 'a>() { + takes_closure::<'a, 'b, 'c, T>(|arg| arg.constrain()); +} +fn main() {}