From 67defd72e6a39a3a339ebe925ffb6f5a35f5a04e Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 3 Dec 2024 13:42:43 +0100 Subject: [PATCH 1/4] update instrumentation --- compiler/rustc_borrowck/src/region_infer/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 99af5500ac6..f4f87fa9f65 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -981,8 +981,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { return false; }; - debug!("subject = {:?}", subject); - let r_scc = self.constraint_sccs.scc(*lower_bound); debug!( @@ -1063,7 +1061,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// variables in the type `T` with an equal universal region from the /// closure signature. /// This is not always possible, so this is a fallible process. - #[instrument(level = "debug", skip(self, infcx))] + #[instrument(level = "debug", skip(self, infcx), ret)] fn try_promote_type_test_subject( &self, infcx: &InferCtxt<'tcx>, From 8a47b442c431efcaba7e257c509b9b8d2337e37a Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 3 Dec 2024 13:49:12 +0100 Subject: [PATCH 2/4] closure requirements: don't replace bivariant opaque args It is unnecessary, these get constrained when checking that the opaque type is well-formed. It also results in the opaque type no longer being well formed. If you've got `fn foo<'a>() -> impl Sized + 'a` the opaque is `type Opaque<'a, 'aDummy> where 'a: 'aDummy, 'aDummy: 'a` where `'aDummy` is bivariant. If we call `foo::<'b>()` inside of a closure and its return type ends up in a type test, we start out with the WF `Opaque<'b, 'b>`, and then replace the bivariant `'b` with `'static`. `Opaque<'b, 'static>` is no longer well-formed. Given how these type tests are used, I don't think this caused any practical issues. --- .../rustc_borrowck/src/region_infer/mod.rs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index f4f87fa9f65..691709e53ee 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1068,37 +1068,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { ty: Ty<'tcx>, ) -> Option> { let tcx = infcx.tcx; - - // Opaque types' args may include useless lifetimes. - // We will replace them with ReStatic. - struct OpaqueFolder<'tcx> { - tcx: TyCtxt<'tcx>, - } - impl<'tcx> ty::TypeFolder> for OpaqueFolder<'tcx> { - fn cx(&self) -> TyCtxt<'tcx> { - self.tcx - } - fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - use ty::TypeSuperFoldable as _; - let tcx = self.tcx; - let &ty::Alias(ty::Opaque, ty::AliasTy { args, def_id, .. }) = t.kind() else { - return t.super_fold_with(self); - }; - let args = std::iter::zip(args, tcx.variances_of(def_id)).map(|(arg, v)| { - match (arg.unpack(), v) { - (ty::GenericArgKind::Lifetime(_), ty::Bivariant) => { - tcx.lifetimes.re_static.into() - } - _ => arg.fold_with(self), - } - }); - Ty::new_opaque(tcx, def_id, tcx.mk_args_from_iter(args)) - } - } - - let ty = ty.fold_with(&mut OpaqueFolder { tcx }); let mut failed = false; - let ty = fold_regions(tcx, ty, |r, _depth| { let r_vid = self.to_region_vid(r); let r_scc = self.constraint_sccs.scc(r_vid); From 65d0b5dc2eb6074579f9a17a6349073066209538 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 3 Dec 2024 14:06:46 +0100 Subject: [PATCH 3/4] small code cleanup --- compiler/rustc_borrowck/src/region_infer/mod.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 691709e53ee..0eecf98a6ed 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -973,23 +973,20 @@ impl<'tcx> RegionInferenceContext<'tcx> { propagated_outlives_requirements: &mut Vec>, ) -> bool { let tcx = infcx.tcx; - - let TypeTest { generic_kind, lower_bound, span: _, verify_bound: _ } = type_test; + let TypeTest { generic_kind, lower_bound, span: blame_span, ref verify_bound } = *type_test; let generic_ty = generic_kind.to_ty(tcx); let Some(subject) = self.try_promote_type_test_subject(infcx, generic_ty) else { return false; }; - let r_scc = self.constraint_sccs.scc(*lower_bound); - + let r_scc = self.constraint_sccs.scc(lower_bound); debug!( "lower_bound = {:?} r_scc={:?} universe={:?}", lower_bound, r_scc, self.constraint_sccs.annotation(r_scc).min_universe() ); - // If the type test requires that `T: 'a` where `'a` is a // placeholder from another universe, that effectively requires // `T: 'static`, so we have to propagate that requirement. @@ -1002,7 +999,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { propagated_outlives_requirements.push(ClosureOutlivesRequirement { subject, outlived_free_region: static_r, - blame_span: type_test.span, + blame_span, category: ConstraintCategory::Boring, }); @@ -1029,12 +1026,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { // where `ur` is a local bound -- we are sometimes in a // position to prove things that our caller cannot. See // #53570 for an example. - if self.eval_verify_bound(infcx, generic_ty, ur, &type_test.verify_bound) { + if self.eval_verify_bound(infcx, generic_ty, ur, &verify_bound) { continue; } let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur); - debug!("try_promote_type_test: non_local_ub={:?}", non_local_ub); + debug!(?non_local_ub); // 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 @@ -1047,10 +1044,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { let requirement = ClosureOutlivesRequirement { subject, outlived_free_region: upper_bound, - blame_span: type_test.span, + blame_span, category: ConstraintCategory::Boring, }; - debug!("try_promote_type_test: pushing {:#?}", requirement); + debug!(?requirement, "adding closure requirement"); propagated_outlives_requirements.push(requirement); } } From 1c96ddde11ad3ef054228322ed144742cecb8643 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 3 Dec 2024 17:26:43 +0100 Subject: [PATCH 4/4] closure-requirements: add regression tests --- .../thread_scope_correct_implied_bound.rs | 23 +++++++++++++++++++ .../thread_scope_incorrect_implied_bound.rs | 21 +++++++++++++++++ ...hread_scope_incorrect_implied_bound.stderr | 17 ++++++++++++++ .../ty-outlives/projection-implied-bounds.rs | 2 -- .../projection-implied-bounds.stderr | 2 +- 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/ui/nll/closure-requirements/thread_scope_correct_implied_bound.rs create mode 100644 tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.rs create mode 100644 tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.stderr diff --git a/tests/ui/nll/closure-requirements/thread_scope_correct_implied_bound.rs b/tests/ui/nll/closure-requirements/thread_scope_correct_implied_bound.rs new file mode 100644 index 00000000000..e2b3b051ea8 --- /dev/null +++ b/tests/ui/nll/closure-requirements/thread_scope_correct_implied_bound.rs @@ -0,0 +1,23 @@ +// This example broke while refactoring the way closure +// requirements are handled. The setup here matches +// `thread::scope`. + +//@ check-pass + +struct Outlives<'hr, 'scope: 'hr>(*mut (&'scope (), &'hr ())); +impl<'hr, 'scope> Outlives<'hr, 'scope> { + fn outlives_hr(self) {} +} + +fn takes_closure_implied_bound<'scope>(f: impl for<'hr> FnOnce(Outlives<'hr, 'scope>)) {} + +fn requires_external_outlives_hr() { + // implied bounds: + // - `T: 'scope` as `'scope` is local to this function + // - `'scope: 'hr` as it's an implied bound of `Outlives` + // + // need to prove `T: 'hr` :> + takes_closure_implied_bound(|proof| proof.outlives_hr::()); +} + +fn main() {} diff --git a/tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.rs b/tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.rs new file mode 100644 index 00000000000..cfc8980410a --- /dev/null +++ b/tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.rs @@ -0,0 +1,21 @@ +// This example incorrectly compiled while refactoring the way +// closure requirements are handled. + +struct Outlives<'hr: 'scope, 'scope>(*mut (&'scope (), &'hr ())); +impl<'hr, 'scope> Outlives<'hr, 'scope> { + fn outlives_hr(self) {} +} + +fn takes_closure_implied_bound<'scope>(f: impl for<'hr> FnOnce(Outlives<'hr, 'scope>)) {} + +fn requires_external_outlives_hr() { + // implied bounds: + // - `T: 'scope` as `'scope` is local to this function + // - `'hr: 'scope` as it's an implied bound of `Outlives` + // + // need to prove `T: 'hr` :< + takes_closure_implied_bound(|proof| proof.outlives_hr::()); + //~^ ERROR the parameter type `T` may not live long enough +} + +fn main() {} diff --git a/tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.stderr b/tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.stderr new file mode 100644 index 00000000000..e22673c249f --- /dev/null +++ b/tests/ui/nll/closure-requirements/thread_scope_incorrect_implied_bound.stderr @@ -0,0 +1,17 @@ +error[E0310]: the parameter type `T` may not live long enough + --> $DIR/thread_scope_incorrect_implied_bound.rs:17:47 + | +LL | takes_closure_implied_bound(|proof| proof.outlives_hr::()); + | ^^^^^^^^^^^ + | | + | the parameter type `T` must be valid for the static lifetime... + | ...so that the type `T` will meet its required lifetime bounds + | +help: consider adding an explicit lifetime bound + | +LL | fn requires_external_outlives_hr() { + | +++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0310`. diff --git a/tests/ui/nll/ty-outlives/projection-implied-bounds.rs b/tests/ui/nll/ty-outlives/projection-implied-bounds.rs index 7d983adfe88..c6572d60bb0 100644 --- a/tests/ui/nll/ty-outlives/projection-implied-bounds.rs +++ b/tests/ui/nll/ty-outlives/projection-implied-bounds.rs @@ -1,8 +1,6 @@ // Test that we can deduce when projections like `T::Item` outlive the // function body. Test that this does not imply that `T: 'a` holds. -//@ compile-flags:-Zverbose-internals - use std::cell::Cell; fn twice(mut value: T, mut f: F) diff --git a/tests/ui/nll/ty-outlives/projection-implied-bounds.stderr b/tests/ui/nll/ty-outlives/projection-implied-bounds.stderr index 2aab03ee7b7..5d5b890151d 100644 --- a/tests/ui/nll/ty-outlives/projection-implied-bounds.stderr +++ b/tests/ui/nll/ty-outlives/projection-implied-bounds.stderr @@ -1,5 +1,5 @@ error[E0310]: the parameter type `T` may not live long enough - --> $DIR/projection-implied-bounds.rs:30:36 + --> $DIR/projection-implied-bounds.rs:28:36 | LL | twice(value, |value_ref, item| invoke2(value_ref, item)); | ^^^^^^^^^^^^^^^^^^^^^^^^