From beb0f148577e92f95aaa327e9aecb0bbb51d5980 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Thu, 22 Jan 2026 21:03:53 +0100 Subject: [PATCH 1/3] perform WF check on closure args Done by removing the function that skipped the WF check on user types (used to skip it on closure args) and using the default one. add tests of issue + bless/change other tests --- .../src/type_check/canonical.rs | 50 ------------------- .../src/type_check/input_output.rs | 4 +- .../hrlt-implied-trait-bounds-roundtrip.rs | 3 +- ...hrlt-implied-trait-bounds-roundtrip.stderr | 11 ++++ .../escape-argument-callee.stderr | 2 +- .../propagate-approximated-ref.stderr | 2 + ...oximated-shorter-to-static-no-bound.stderr | 1 + ...mated-shorter-to-static-wrong-bound.stderr | 3 ++ ...ail-to-approximate-longer-no-bounds.stderr | 2 +- ...fail-to-approximate-longer-wrong-bounds.rs | 2 +- ...-to-approximate-longer-wrong-bounds.stderr | 29 +++++++++-- ...-argument-types-two-region-pointers.stderr | 2 +- tests/ui/wf/check-wf-of-closure-args.rs | 14 ++++++ tests/ui/wf/check-wf-of-closure-args.stderr | 20 ++++++++ 14 files changed, 84 insertions(+), 61 deletions(-) create mode 100644 tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.stderr create mode 100644 tests/ui/wf/check-wf-of-closure-args.rs create mode 100644 tests/ui/wf/check-wf-of-closure-args.stderr diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index 56c800dcf7c7e..4aeff9401b7fc 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -3,7 +3,6 @@ use std::fmt; use rustc_errors::ErrorGuaranteed; use rustc_infer::infer::canonical::Canonical; use rustc_infer::infer::outlives::env::RegionBoundPairs; -use rustc_middle::bug; use rustc_middle::mir::{Body, ConstraintCategory}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, Unnormalized, Upcast}; use rustc_span::Span; @@ -259,53 +258,4 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .and(type_op::ascribe_user_type::AscribeUserType { mir_ty, user_ty }), ); } - - /// *Incorrectly* skips the WF checks we normally do in `ascribe_user_type`. - /// - /// FIXME(#104478, #104477): This is a hack for backward-compatibility. - #[instrument(skip(self), level = "debug")] - pub(super) fn ascribe_user_type_skip_wf( - &mut self, - mir_ty: Ty<'tcx>, - user_ty: ty::UserType<'tcx>, - span: Span, - ) { - let ty::UserTypeKind::Ty(user_ty) = user_ty.kind else { bug!() }; - - // A fast path for a common case with closure input/output types. - if let ty::Infer(_) = user_ty.kind() { - self.eq_types(user_ty, mir_ty, Locations::All(span), ConstraintCategory::Boring) - .unwrap(); - return; - } - - // This is a hack. `body.local_decls` are not necessarily normalized in the old - // solver due to not deeply normalizing in writeback. So we must re-normalize here. - // - // I am not sure of a test case where this actually matters. There is a similar - // hack in `equate_inputs_and_outputs` which does have associated test cases. - let mir_ty = match self.infcx.next_trait_solver() { - true => mir_ty, - false => self.normalize(Unnormalized::new_wip(mir_ty), Locations::All(span)), - }; - - let cause = ObligationCause::dummy_with_span(span); - let param_env = self.infcx.param_env; - let _: Result<_, ErrorGuaranteed> = self.fully_perform_op( - Locations::All(span), - ConstraintCategory::Boring, - type_op::custom::CustomTypeOp::new( - |ocx| { - // The `AscribeUserType` query would normally emit a wf - // obligation for the unnormalized user_ty here. This is - // where the "incorrectly skips the WF checks we normally do" - // happens - let user_ty = ocx.normalize(&cause, param_env, Unnormalized::new_wip(user_ty)); - ocx.eq(&cause, param_env, user_ty, mir_ty)?; - Ok(()) - }, - "ascribe_user_type_skip_wf", - ), - ); - } } diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs index fe40173b905d6..43a2e14b9872a 100644 --- a/compiler/rustc_borrowck/src/type_check/input_output.rs +++ b/compiler/rustc_borrowck/src/type_check/input_output.rs @@ -118,7 +118,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .skip(1 + if is_coroutine_with_implicit_resume_ty { 1 } else { 0 }) .map(|local| &self.body.local_decls[local]), ) { - self.ascribe_user_type_skip_wf( + self.ascribe_user_type( arg_decl.ty, ty::UserType::new(ty::UserTypeKind::Ty(user_ty)), arg_decl.source_info.span, @@ -127,7 +127,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // If the user explicitly annotated the output type, enforce it. let output_decl = &self.body.local_decls[RETURN_PLACE]; - self.ascribe_user_type_skip_wf( + self.ascribe_user_type( output_decl.ty, ty::UserType::new(ty::UserTypeKind::Ty(user_provided_sig.output())), output_decl.source_info.span, diff --git a/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.rs b/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.rs index 0963053f57806..6bc644f256424 100644 --- a/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.rs +++ b/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.rs @@ -1,4 +1,3 @@ -//@ check-pass struct Foo<'a>(&'a ()) where (): Trait<'a>; @@ -21,7 +20,7 @@ where } fn main() { - let bar: for<'a, 'b> fn(Foo<'a>, &'b ()) = |_, _| {}; + let bar: for<'a, 'b> fn(Foo<'a>, &'b ()) = |_, _| {}; //~ ERROR: lifetime may not live long enough // If `could_use_implied_bounds` were to use implied bounds, // keeping 'a late-bound, then we could assign that function diff --git a/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.stderr b/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.stderr new file mode 100644 index 0000000000000..dd99590877bca --- /dev/null +++ b/tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.stderr @@ -0,0 +1,11 @@ +error: lifetime may not live long enough + --> $DIR/hrlt-implied-trait-bounds-roundtrip.rs:23:49 + | +LL | let bar: for<'a, 'b> fn(Foo<'a>, &'b ()) = |_, _| {}; + | ^ + | | + | has type `Foo<'1>` + | requires that `'1` must outlive `'static` + +error: aborting due to 1 previous error + diff --git a/tests/ui/nll/closure-requirements/escape-argument-callee.stderr b/tests/ui/nll/closure-requirements/escape-argument-callee.stderr index 2742162c82119..a5b68f1d22fc6 100644 --- a/tests/ui/nll/closure-requirements/escape-argument-callee.stderr +++ b/tests/ui/nll/closure-requirements/escape-argument-callee.stderr @@ -20,7 +20,7 @@ LL | let mut closure = expect_sig(|p, y| *p = y); | - - ^^^^^^ assignment requires that `'1` must outlive `'2` | | | | | has type `&'1 i32` - | has type `&'?1 mut &'2 i32` + | has type `&'2 mut &'?2 i32` note: no external requirements --> $DIR/escape-argument-callee.rs:20:1 diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr index f5527eeb2cdba..964ea3079c924 100644 --- a/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr +++ b/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr @@ -19,6 +19,8 @@ LL | establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y = note: late-bound region is '?4 = note: number of external vids: 5 = note: where '?1: '?2 + = note: where '?1: '?2 + = note: where '?1: '?2 note: no external requirements --> $DIR/propagate-approximated-ref.rs:42:1 diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr index 9e9eae985973a..d06166e4704db 100644 --- a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr +++ b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr @@ -18,6 +18,7 @@ LL | establish_relationships(&cell_a, &cell_b, |_outlives, x, y| { = note: late-bound region is '?3 = note: number of external vids: 4 = note: where '?1: '?0 + = note: where '?1: '?0 note: no external requirements --> $DIR/propagate-approximated-shorter-to-static-no-bound.rs:31:1 diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr index 303fcd4cdfcf3..5fbd37062702a 100644 --- a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr +++ b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr @@ -18,6 +18,9 @@ LL | establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y = note: late-bound region is '?3 = note: late-bound region is '?4 = note: number of external vids: 5 + = note: where '?1: '?2 + = note: where '?1: '?2 + = note: where '?1: '?0 = note: where '?1: '?0 note: no external requirements diff --git a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr index 6b04e346c6971..8b5641293b7a0 100644 --- a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr +++ b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr @@ -23,7 +23,7 @@ error: lifetime may not live long enough LL | establish_relationships(&cell_a, &cell_b, |_outlives, x, y| { | --------- - has type `&'?6 Cell<&'1 u32>` | | - | has type `&'?4 Cell<&'2 &'?1 u32>` + | has type `&'2 Cell<&'?5 &'?1 u32>` LL | // Only works if 'x: 'y: LL | demand_y(x, y, x.get()) | ^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2` diff --git a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.rs b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.rs index 2cb6ceb0c3b95..bda64151fba3d 100644 --- a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.rs +++ b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.rs @@ -36,7 +36,7 @@ fn demand_y<'x, 'y>(_cell_x: &Cell<&'x u32>, _cell_y: &Cell<&'y u32>, _y: &'y u3 #[rustc_regions] fn supply<'a, 'b>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>) { - establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| { + establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| { //~ ERROR: lifetime may not live long enough // Only works if 'x: 'y: demand_y(x, y, x.get()) //~^ ERROR diff --git a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr index ae2129c65f2c0..88eb8fe342d12 100644 --- a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr +++ b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr @@ -1,4 +1,4 @@ -note: no external requirements +note: external requirements --> $DIR/propagate-fail-to-approximate-longer-wrong-bounds.rs:39:47 | LL | establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| { @@ -17,12 +17,16 @@ LL | establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y = note: late-bound region is '?10 = note: late-bound region is '?3 = note: late-bound region is '?4 + = note: number of external vids: 5 + = note: where '?1: '?2 + = note: where '?1: '?2 + = note: where '?1: '?2 error: lifetime may not live long enough --> $DIR/propagate-fail-to-approximate-longer-wrong-bounds.rs:41:9 | LL | establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| { - | ---------- ---------- has type `&'?7 Cell<&'2 &'?2 u32>` + | ---------- ---------- has type `&'2 Cell<&'?8 &'?2 u32>` | | | has type `&'?5 Cell<&'1 &'?1 u32>` LL | // Only works if 'x: 'y: @@ -41,5 +45,24 @@ LL | fn supply<'a, 'b>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>) { | = note: defining type: supply -error: aborting due to 1 previous error +error: lifetime may not live long enough + --> $DIR/propagate-fail-to-approximate-longer-wrong-bounds.rs:39:5 + | +LL | fn supply<'a, 'b>(cell_a: Cell<&'a u32>, cell_b: Cell<&'b u32>) { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | / establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| { +LL | | // Only works if 'x: 'y: +LL | | demand_y(x, y, x.get()) +LL | | +LL | | }); + | |______^ argument requires that `'a` must outlive `'b` + | + = help: consider adding the following bound: `'a: 'b` + = note: requirement occurs because of the type `Cell<&'?11 u32>`, which makes the generic argument `&'?11 u32` invariant + = note: the struct `Cell` is invariant over the parameter `T` + = help: see for more information about variance + +error: aborting due to 2 previous errors diff --git a/tests/ui/unboxed-closures/unboxed-closures-infer-argument-types-two-region-pointers.stderr b/tests/ui/unboxed-closures/unboxed-closures-infer-argument-types-two-region-pointers.stderr index e2f48f37f0dad..c84efdcb8e3a6 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-infer-argument-types-two-region-pointers.stderr +++ b/tests/ui/unboxed-closures/unboxed-closures-infer-argument-types-two-region-pointers.stderr @@ -4,7 +4,7 @@ error: lifetime may not live long enough LL | doit(0, &|x, y| { | - - has type `&'1 i32` | | - | has type `&Cell<&'2 i32>` + | has type `&'2 Cell<&i32>` LL | x.set(y); | ^^^^^^^^ argument requires that `'1` must outlive `'2` | diff --git a/tests/ui/wf/check-wf-of-closure-args.rs b/tests/ui/wf/check-wf-of-closure-args.rs new file mode 100644 index 0000000000000..a4c4de7013c2f --- /dev/null +++ b/tests/ui/wf/check-wf-of-closure-args.rs @@ -0,0 +1,14 @@ +// Checks that we perform WF checks on closure args, regardless of wether they +// are used in the closure it self. +// related to issue #104478 + +struct MyTy(T); +trait Trait {} +impl Trait for &'static str {} +fn wf(_: T) {} + +fn main() { + let _: for<'x> fn(MyTy<&'x str>) = |_| {}; //~ ERROR: lifetime may not live long enough + + let _: for<'x> fn(MyTy<&'x str>) = |x| wf(x); //~ ERROR: lifetime may not live long enough +} diff --git a/tests/ui/wf/check-wf-of-closure-args.stderr b/tests/ui/wf/check-wf-of-closure-args.stderr new file mode 100644 index 0000000000000..a4881bc75d07b --- /dev/null +++ b/tests/ui/wf/check-wf-of-closure-args.stderr @@ -0,0 +1,20 @@ +error: lifetime may not live long enough + --> $DIR/check-wf-of-closure-args.rs:11:41 + | +LL | let _: for<'x> fn(MyTy<&'x str>) = |_| {}; + | ^ + | | + | has type `MyTy<&'1 str>` + | requires that `'1` must outlive `'static` + +error: lifetime may not live long enough + --> $DIR/check-wf-of-closure-args.rs:13:41 + | +LL | let _: for<'x> fn(MyTy<&'x str>) = |x| wf(x); + | ^ + | | + | has type `MyTy<&'1 str>` + | requires that `'1` must outlive `'static` + +error: aborting due to 2 previous errors + From e5233392a9f9eb5dbe58a8ce448c55b8c69264cd Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 13 Mar 2026 17:05:32 +0100 Subject: [PATCH 2/3] 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. + filter redundant requirements through unit propagation & bless test --- .../rustc_borrowck/src/region_infer/mod.rs | 141 +++++++++++++++--- .../unconstrained-closure-lifetime-generic.rs | 2 - ...onstrained-closure-lifetime-generic.stderr | 32 +--- tests/ui/consts/issue-102117.rs | 1 - tests/ui/consts/issue-102117.stderr | 17 +-- .../type-test-issue-154267-stronger.rs | 22 +++ .../type-test-issue-154267.rs | 26 ++++ 7 files changed, 174 insertions(+), 67 deletions(-) create mode 100644 tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs 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 5e56ae80ff5da..4eea6d625df64 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -585,7 +585,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; @@ -595,6 +595,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); + // 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 { debug!("check_type_test: {:?}", type_test); @@ -608,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_requirement + && self.try_promote_type_test( + infcx, + type_test, + conjunctive_propagated_outlives_requirements, + ) { continue; } @@ -633,6 +645,85 @@ impl<'tcx> RegionInferenceContext<'tcx> { errors_buffer.push(RegionErrorKind::TypeTestError { type_test: type_test.clone() }); } } + + if let Some(mut conjunctive_requirement) = conjunctive_propagated_outlives_requirement + && !conjunctive_requirement.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. + // + // 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 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 }) = + subject.subject + else { + unreachable!("ClosureOutliveSubject of a type test is always a Ty"); + }; + (ty, subject.outlived_free_region) + } + + let units: Vec<_> = conjunctive_requirement + .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_requirement.retain(|or_requirement| { + or_requirement.len() == 1 + || !or_requirement.iter().any(|r| { + 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) + }) + }) + }); + + assert!( + !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_requirement.into_iter().flatten()); + } } /// Invoked when we have some type-test (e.g., `T: 'X`) that we cannot @@ -664,7 +755,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; @@ -690,24 +781,41 @@ 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 mut found_outlived_universal_region = false; - for ur in self.scc_values.universal_regions_outlived_by(r_scc) { - found_outlived_universal_region = true; + 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" + ); + + for ur in minimal_universal_regions { debug!("universal_region_outlived_by ur={:?}", ur); let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); debug!(?non_local_ub); @@ -716,6 +824,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // 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)); @@ -727,14 +836,10 @@ 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 - // also be at least as large as some universal region, as the type test is otherwise - // trivial. - assert!(found_outlived_universal_region); true } 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/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`. 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() {} 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..6137f38886437 --- /dev/null +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267.rs @@ -0,0 +1,26 @@ +//@ 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, + T: 'c, + { + } +} + +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 5581cea5f8b098685cd48cbdca94da35be8ceea7 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 26 Jun 2026 15:44:53 +0200 Subject: [PATCH 3/3] address concern of closure type-test prop pr --- .../type-test-issue-154267-minimized.rs | 11 +++++++++++ .../type-test-issue-154267-stronger.rs | 10 ++++------ .../type-test-issue-154267-stronger.stderr | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 tests/ui/nll/closure-requirements/type-test-issue-154267-minimized.rs create mode 100644 tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.stderr diff --git a/tests/ui/nll/closure-requirements/type-test-issue-154267-minimized.rs b/tests/ui/nll/closure-requirements/type-test-issue-154267-minimized.rs new file mode 100644 index 0000000000000..44988db4998c9 --- /dev/null +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267-minimized.rs @@ -0,0 +1,11 @@ +//@ check-pass + +struct Arg<'a: 'b, 'b, 'c> { + field: *mut (&'a (), &'b (), &'c ()), +} +fn foo<'a, 'b, T: for<'c> FnOnce(Arg<'a, 'b, 'c>)>(_: T) {} + +fn error<'a, 'b>() { + foo::<'a, 'b>(|arg| {}); +} +fn main() {} 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 index a8956defb56e4..4533562150f16 100644 --- a/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.rs @@ -1,7 +1,5 @@ -//@ 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. +// Stronger version of `type-test-issue-154267` where `error` should fail because it does not have +// an explicit `'a: 'b` bound. struct Arg<'a: 'b, 'b: 'd, 'c: 'd, 'd, T> { field: *mut (&'a (), &'b (), &'c (), &'d (), T), @@ -14,9 +12,9 @@ impl<'a: 'b, 'b, 'c, 'd, T> Arg<'a, 'b, 'c, 'd, T> { { } } -fn takes_closure<'a, 'b, 'c, T>(_: impl for<'d> FnOnce(Arg<'a, 'b, 'c, 'd, T>)) {} +fn takes_closure<'a: 'b, '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()); + takes_closure::<'a, 'b, 'c, T>(|arg| arg.constrain()); //~ ERROR: lifetime may not live long enough } fn main() {} diff --git a/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.stderr b/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.stderr new file mode 100644 index 0000000000000..814e1f4d3755d --- /dev/null +++ b/tests/ui/nll/closure-requirements/type-test-issue-154267-stronger.stderr @@ -0,0 +1,14 @@ +error: lifetime may not live long enough + --> $DIR/type-test-issue-154267-stronger.rs:18:5 + | +LL | fn error<'a, 'b, 'c, T: 'a>() { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | takes_closure::<'a, 'b, 'c, T>(|arg| arg.constrain()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'b` + | + = help: consider adding the following bound: `'a: 'b` + +error: aborting due to 1 previous error +