From 15e6565646549a05ef5be0d7a376a96d64ac6463 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 13 Mar 2026 17:05:32 +0100 Subject: [PATCH] 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() {}