From 184d5ef1077869c58479d0c3fcab404b99090ed3 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 21 Sep 2023 04:11:08 +0000 Subject: [PATCH 1/5] Consider alias bounds when considering lliveness for alias types in NLL --- .../src/type_check/liveness/trace.rs | 95 +++++++++++++++---- .../ui/borrowck/alias-liveness/rpit-static.rs | 14 +++ .../borrowck/alias-liveness/rpitit-static.rs | 12 +++ 3 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 tests/ui/borrowck/alias-liveness/rpit-static.rs create mode 100644 tests/ui/borrowck/alias-liveness/rpitit-static.rs diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 21da05c32dd86..432c7a1207193 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -5,10 +5,13 @@ use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; -use rustc_middle::ty::{RegionVid, Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; +use rustc_middle::ty::{ + self, RegionVid, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; use rustc_span::DUMMY_SP; use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; +use std::ops::ControlFlow; use std::rc::Rc; use rustc_mir_dataflow::impls::MaybeInitializedPlaces; @@ -601,34 +604,88 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { values::location_set_str(elements, live_at.iter()), ); - let tcx = typeck.tcx(); - let borrowck_context = &mut typeck.borrowck_context; - // When using `-Zpolonius=next`, we want to record the loans that flow into this value's // regions as being live at the given `live_at` points: this will be used to compute the // location where a loan goes out of scope. - let num_loans = borrowck_context.borrow_set.len(); - let mut value_loans = HybridBitSet::new_empty(num_loans); - - tcx.for_each_free_region(&value, |live_region| { - let live_region_vid = borrowck_context.universal_regions.to_region_vid(live_region); + let num_loans = typeck.borrowck_context.borrow_set.len(); + let value_loans = &mut HybridBitSet::new_empty(num_loans); + + struct MakeAllRegionsLive<'a, 'b, 'tcx> { + typeck: &'b mut TypeChecker<'a, 'tcx>, + live_at: &'b IntervalSet, + value_loans: &'b mut HybridBitSet, + inflowing_loans: &'b SparseBitMatrix, + } + impl<'tcx> MakeAllRegionsLive<'_, '_, 'tcx> { + fn make_alias_live(&mut self, t: Ty<'tcx>) -> ControlFlow { + let ty::Alias(_kind, alias_ty) = t.kind() else { + bug!(); + }; + let tcx = self.typeck.infcx.tcx; + let mut outlives_bounds = tcx + .item_bounds(alias_ty.def_id) + .iter_instantiated(tcx, alias_ty.args) + .filter_map(|clause| { + if let Some(outlives) = clause.as_type_outlives_clause() + && outlives.skip_binder().0 == t + { + Some(outlives.skip_binder().1) + } else { + None + } + }); + if let Some(r) = outlives_bounds.next() + && !r.is_late_bound() + && outlives_bounds.all(|other_r| { + other_r == r + }) + { + r.visit_with(self) + } else { + t.super_visit_with(self) + } + } + } + impl<'tcx> TypeVisitor> for MakeAllRegionsLive<'_, '_, 'tcx> { + type BreakTy = !; - borrowck_context - .constraints - .liveness_constraints - .add_elements(live_region_vid, live_at); + fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow { + if r.is_late_bound() { + return ControlFlow::Continue(()); + } + let live_region_vid = + self.typeck.borrowck_context.universal_regions.to_region_vid(r); + + self.typeck + .borrowck_context + .constraints + .liveness_constraints + .add_elements(live_region_vid, self.live_at); + + // There can only be inflowing loans for this region when we are using + // `-Zpolonius=next`. + if let Some(inflowing) = self.inflowing_loans.row(live_region_vid) { + self.value_loans.union(inflowing); + } + ControlFlow::Continue(()) + } - // There can only be inflowing loans for this region when we are using - // `-Zpolonius=next`. - if let Some(inflowing) = inflowing_loans.row(live_region_vid) { - value_loans.union(inflowing); + fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { + if !t.has_free_regions() { + ControlFlow::Continue(()) + } else if let ty::Alias(..) = t.kind() { + self.make_alias_live(t) + } else { + t.super_visit_with(self) + } } - }); + } + value.visit_with(&mut MakeAllRegionsLive { typeck, live_at, value_loans, inflowing_loans }); // Record the loans reaching the value. if !value_loans.is_empty() { for point in live_at.iter() { - borrowck_context.live_loans.union_row(point, &value_loans); + typeck.borrowck_context.live_loans.union_row(point, value_loans); } } } diff --git a/tests/ui/borrowck/alias-liveness/rpit-static.rs b/tests/ui/borrowck/alias-liveness/rpit-static.rs new file mode 100644 index 0000000000000..8637d5fb720ea --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-static.rs @@ -0,0 +1,14 @@ +// check-pass + +trait Captures<'a> {} +impl Captures<'_> for T {} + +fn foo(x: &i32) -> impl Sized + Captures<'_> + 'static {} + +fn main() { + let y; + { + let x = 1; + y = foo(&x); + } +} diff --git a/tests/ui/borrowck/alias-liveness/rpitit-static.rs b/tests/ui/borrowck/alias-liveness/rpitit-static.rs new file mode 100644 index 0000000000000..ed7b496a42ba9 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-static.rs @@ -0,0 +1,12 @@ +// check-pass + +trait Foo { + fn rpitit(&mut self) -> impl Sized + 'static; +} + +fn test(mut t: T) { + let a = t.rpitit(); + let b = t.rpitit(); +} + +fn main() {} From e425d85518b4e3c2dc6edfccab638db26380198a Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 21 Sep 2023 18:02:22 +0000 Subject: [PATCH 2/5] Consider param-env candidates, too --- .../src/type_check/liveness/trace.rs | 22 ++++++++++++++++++- .../ui/borrowck/alias-liveness/gat-static.rs | 20 +++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/ui/borrowck/alias-liveness/gat-static.rs diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 432c7a1207193..d0078f71cd81c 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -3,6 +3,8 @@ use rustc_data_structures::graph::WithSuccessors; use rustc_index::bit_set::{HybridBitSet, SparseBitMatrix}; use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; +use rustc_infer::infer::outlives::test_type_match; +use rustc_infer::infer::region_constraints::VerifyIfEq; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; use rustc_middle::ty::{ @@ -622,6 +624,7 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { bug!(); }; let tcx = self.typeck.infcx.tcx; + let param_env = self.typeck.param_env; let mut outlives_bounds = tcx .item_bounds(alias_ty.def_id) .iter_instantiated(tcx, alias_ty.args) @@ -633,7 +636,24 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { } else { None } - }); + }) + .chain(param_env.caller_bounds().iter().filter_map(|clause| { + let outlives = clause.as_type_outlives_clause()?; + if let Some(outlives) = outlives.no_bound_vars() + && outlives.0 == t + { + Some(outlives.1) + } else { + test_type_match::extract_verify_if_eq( + tcx, + param_env, + &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { + VerifyIfEq { ty, bound } + }), + t, + ) + } + })); if let Some(r) = outlives_bounds.next() && !r.is_late_bound() && outlives_bounds.all(|other_r| { diff --git a/tests/ui/borrowck/alias-liveness/gat-static.rs b/tests/ui/borrowck/alias-liveness/gat-static.rs new file mode 100644 index 0000000000000..1ab40fe828c36 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-static.rs @@ -0,0 +1,20 @@ +// check-pass + +trait Foo { + type Assoc<'a> + where + Self: 'a; + + fn assoc(&mut self) -> Self::Assoc<'_>; +} + +fn test(mut t: T) +where + T: Foo, + for<'a> T::Assoc<'a>: 'static, +{ + let a = t.assoc(); + let b = t.assoc(); +} + +fn main() {} From 371d8a8215597dbdb048552d7bc6962d2a95909b Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 21 Sep 2023 05:51:40 +0000 Subject: [PATCH 3/5] Consider static specially --- .../src/type_check/liveness/trace.rs | 28 +++++++++++++------ .../ui/borrowck/alias-liveness/rtn-static.rs | 17 +++++++++++ .../borrowck/alias-liveness/rtn-static.stderr | 11 ++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 tests/ui/borrowck/alias-liveness/rtn-static.rs create mode 100644 tests/ui/borrowck/alias-liveness/rtn-static.stderr diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index d0078f71cd81c..e0eed812b6adf 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -619,13 +619,21 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { inflowing_loans: &'b SparseBitMatrix, } impl<'tcx> MakeAllRegionsLive<'_, '_, 'tcx> { + /// We can prove that an alias is live two ways: + /// 1. All the components are live. + /// 2. There is a known outlives bound or where-clause, and that + /// region is live. + /// We search through the item bounds and where clauses for + /// either `'static` or a unique outlives region, and if one is + /// found, we just need to prove that that region is still live. + /// If one is not found, then we continue to walk through the alias. fn make_alias_live(&mut self, t: Ty<'tcx>) -> ControlFlow { let ty::Alias(_kind, alias_ty) = t.kind() else { - bug!(); + bug!("`make_alias_live` only takes alias types"); }; let tcx = self.typeck.infcx.tcx; let param_env = self.typeck.param_env; - let mut outlives_bounds = tcx + let outlives_bounds: Vec<_> = tcx .item_bounds(alias_ty.def_id) .iter_instantiated(tcx, alias_ty.args) .filter_map(|clause| { @@ -653,12 +661,16 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { t, ) } - })); - if let Some(r) = outlives_bounds.next() - && !r.is_late_bound() - && outlives_bounds.all(|other_r| { - other_r == r - }) + })) + .collect(); + // If we find `'static`, then we know the alias doesn't capture *any* regions. + // Otherwise, all of the outlives regions should be equal -- if they're not, + // we don't really know how to proceed, so we continue recursing through the + // alias. + if outlives_bounds.contains(&tcx.lifetimes.re_static) { + ControlFlow::Continue(()) + } else if let Some(r) = outlives_bounds.first() + && outlives_bounds[1..].iter().all(|other_r| other_r == r) { r.visit_with(self) } else { diff --git a/tests/ui/borrowck/alias-liveness/rtn-static.rs b/tests/ui/borrowck/alias-liveness/rtn-static.rs new file mode 100644 index 0000000000000..c850f0122e5a4 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rtn-static.rs @@ -0,0 +1,17 @@ +// check-pass + +#![feature(return_type_notation)] +//~^ WARN the feature `return_type_notation` is incomplete + +trait Foo { + fn borrow(&mut self) -> impl Sized + '_; +} + +// Test that the `'_` item bound in `borrow` does not cause us to +// overlook the `'static` RTN bound. +fn test>(mut t: T) { + let x = t.borrow(); + let x = t.borrow(); +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/rtn-static.stderr b/tests/ui/borrowck/alias-liveness/rtn-static.stderr new file mode 100644 index 0000000000000..e9202db2c7902 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rtn-static.stderr @@ -0,0 +1,11 @@ +warning: the feature `return_type_notation` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/rtn-static.rs:3:12 + | +LL | #![feature(return_type_notation)] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #109417 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + From 10059704852424b735201ef9443b36361821a6c1 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 14 Oct 2023 15:25:58 +0100 Subject: [PATCH 4/5] Use a visitor that could be reused by opaque type capture check later --- .../src/type_check/liveness/trace.rs | 112 ++-------------- .../rustc_infer/src/infer/opaque_types.rs | 2 + .../src/infer/outlives/for_liveness.rs | 121 ++++++++++++++++++ .../rustc_infer/src/infer/outlives/mod.rs | 1 + 4 files changed, 137 insertions(+), 99 deletions(-) create mode 100644 compiler/rustc_infer/src/infer/outlives/for_liveness.rs diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index e0eed812b6adf..3281cd7a52b36 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -3,17 +3,13 @@ use rustc_data_structures::graph::WithSuccessors; use rustc_index::bit_set::{HybridBitSet, SparseBitMatrix}; use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; -use rustc_infer::infer::outlives::test_type_match; -use rustc_infer::infer::region_constraints::VerifyIfEq; +use rustc_infer::infer::outlives::for_liveness; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; -use rustc_middle::ty::{ - self, RegionVid, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, -}; +use rustc_middle::ty::{RegionVid, Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; use rustc_span::DUMMY_SP; use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; -use std::ops::ControlFlow; use std::rc::Rc; use rustc_mir_dataflow::impls::MaybeInitializedPlaces; @@ -612,107 +608,25 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { let num_loans = typeck.borrowck_context.borrow_set.len(); let value_loans = &mut HybridBitSet::new_empty(num_loans); - struct MakeAllRegionsLive<'a, 'b, 'tcx> { - typeck: &'b mut TypeChecker<'a, 'tcx>, - live_at: &'b IntervalSet, - value_loans: &'b mut HybridBitSet, - inflowing_loans: &'b SparseBitMatrix, - } - impl<'tcx> MakeAllRegionsLive<'_, '_, 'tcx> { - /// We can prove that an alias is live two ways: - /// 1. All the components are live. - /// 2. There is a known outlives bound or where-clause, and that - /// region is live. - /// We search through the item bounds and where clauses for - /// either `'static` or a unique outlives region, and if one is - /// found, we just need to prove that that region is still live. - /// If one is not found, then we continue to walk through the alias. - fn make_alias_live(&mut self, t: Ty<'tcx>) -> ControlFlow { - let ty::Alias(_kind, alias_ty) = t.kind() else { - bug!("`make_alias_live` only takes alias types"); - }; - let tcx = self.typeck.infcx.tcx; - let param_env = self.typeck.param_env; - let outlives_bounds: Vec<_> = tcx - .item_bounds(alias_ty.def_id) - .iter_instantiated(tcx, alias_ty.args) - .filter_map(|clause| { - if let Some(outlives) = clause.as_type_outlives_clause() - && outlives.skip_binder().0 == t - { - Some(outlives.skip_binder().1) - } else { - None - } - }) - .chain(param_env.caller_bounds().iter().filter_map(|clause| { - let outlives = clause.as_type_outlives_clause()?; - if let Some(outlives) = outlives.no_bound_vars() - && outlives.0 == t - { - Some(outlives.1) - } else { - test_type_match::extract_verify_if_eq( - tcx, - param_env, - &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { - VerifyIfEq { ty, bound } - }), - t, - ) - } - })) - .collect(); - // If we find `'static`, then we know the alias doesn't capture *any* regions. - // Otherwise, all of the outlives regions should be equal -- if they're not, - // we don't really know how to proceed, so we continue recursing through the - // alias. - if outlives_bounds.contains(&tcx.lifetimes.re_static) { - ControlFlow::Continue(()) - } else if let Some(r) = outlives_bounds.first() - && outlives_bounds[1..].iter().all(|other_r| other_r == r) - { - r.visit_with(self) - } else { - t.super_visit_with(self) - } - } - } - impl<'tcx> TypeVisitor> for MakeAllRegionsLive<'_, '_, 'tcx> { - type BreakTy = !; - - fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow { - if r.is_late_bound() { - return ControlFlow::Continue(()); - } - let live_region_vid = - self.typeck.borrowck_context.universal_regions.to_region_vid(r); + value.visit_with(&mut for_liveness::FreeRegionsVisitor { + tcx: typeck.tcx(), + param_env: typeck.param_env, + op: |r| { + let live_region_vid = typeck.borrowck_context.universal_regions.to_region_vid(r); - self.typeck + typeck .borrowck_context .constraints .liveness_constraints - .add_elements(live_region_vid, self.live_at); + .add_elements(live_region_vid, live_at); // There can only be inflowing loans for this region when we are using // `-Zpolonius=next`. - if let Some(inflowing) = self.inflowing_loans.row(live_region_vid) { - self.value_loans.union(inflowing); + if let Some(inflowing) = inflowing_loans.row(live_region_vid) { + value_loans.union(inflowing); } - ControlFlow::Continue(()) - } - - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { - if !t.has_free_regions() { - ControlFlow::Continue(()) - } else if let ty::Alias(..) = t.kind() { - self.make_alias_live(t) - } else { - t.super_visit_with(self) - } - } - } - value.visit_with(&mut MakeAllRegionsLive { typeck, live_at, value_loans, inflowing_loans }); + }, + }); // Record the loans reaching the value. if !value_loans.is_empty() { diff --git a/compiler/rustc_infer/src/infer/opaque_types.rs b/compiler/rustc_infer/src/infer/opaque_types.rs index 09df93fcc2fde..cbd9abc137b2e 100644 --- a/compiler/rustc_infer/src/infer/opaque_types.rs +++ b/compiler/rustc_infer/src/infer/opaque_types.rs @@ -380,6 +380,8 @@ impl<'tcx> InferCtxt<'tcx> { .collect(), ); + // FIXME(#42940): This should use the `FreeRegionsVisitor`, but that's + // not currently sound until we have existential regions. concrete_ty.visit_with(&mut ConstrainOpaqueTypeRegionVisitor { tcx: self.tcx, op: |r| self.member_constraint(opaque_type_key, span, concrete_ty, r, &choice_regions), diff --git a/compiler/rustc_infer/src/infer/outlives/for_liveness.rs b/compiler/rustc_infer/src/infer/outlives/for_liveness.rs new file mode 100644 index 0000000000000..ebc51b98be53f --- /dev/null +++ b/compiler/rustc_infer/src/infer/outlives/for_liveness.rs @@ -0,0 +1,121 @@ +use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; + +use std::ops::ControlFlow; + +use crate::infer::outlives::test_type_match; +use crate::infer::region_constraints::VerifyIfEq; + +/// Visits free regions in the type that are relevant for liveness computation. +/// These regions are passed to `OP`. +/// +/// Specifically, we visit all of the regions of types recursively, except if +/// the type is an alias, we look at the outlives bounds in the param-env +/// and alias's item bounds. If there is a unique outlives bound, then visit +/// that instead. If there is not a unique but there is a `'static` outlives +/// bound, then don't visit anything. Otherwise, walk through the opaque's +/// regions structurally. +pub struct FreeRegionsVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> { + pub tcx: TyCtxt<'tcx>, + pub param_env: ty::ParamEnv<'tcx>, + pub op: OP, +} + +impl<'tcx, OP> TypeVisitor> for FreeRegionsVisitor<'tcx, OP> +where + OP: FnMut(ty::Region<'tcx>), +{ + fn visit_binder>>( + &mut self, + t: &ty::Binder<'tcx, T>, + ) -> ControlFlow { + t.super_visit_with(self); + ControlFlow::Continue(()) + } + + fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow { + match *r { + // ignore bound regions, keep visiting + ty::ReLateBound(_, _) => ControlFlow::Continue(()), + _ => { + (self.op)(r); + ControlFlow::Continue(()) + } + } + } + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { + // We're only interested in types involving regions + if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { + return ControlFlow::Continue(()); + } + + match ty.kind() { + // We can prove that an alias is live two ways: + // 1. All the components are live. + // + // 2. There is a known outlives bound or where-clause, and that + // region is live. + // + // We search through the item bounds and where clauses for + // either `'static` or a unique outlives region, and if one is + // found, we just need to prove that that region is still live. + // If one is not found, then we continue to walk through the alias. + ty::Alias(kind, ty::AliasTy { def_id, args, .. }) => { + let tcx = self.tcx; + let param_env = self.param_env; + let outlives_bounds: Vec<_> = tcx + .item_bounds(def_id) + .iter_instantiated(tcx, args) + .chain(param_env.caller_bounds()) + .filter_map(|clause| { + let outlives = clause.as_type_outlives_clause()?; + if let Some(outlives) = outlives.no_bound_vars() + && outlives.0 == ty + { + Some(outlives.1) + } else { + test_type_match::extract_verify_if_eq( + tcx, + param_env, + &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { + VerifyIfEq { ty, bound } + }), + ty, + ) + } + }) + .collect(); + // If we find `'static`, then we know the alias doesn't capture *any* regions. + // Otherwise, all of the outlives regions should be equal -- if they're not, + // we don't really know how to proceed, so we continue recursing through the + // alias. + if outlives_bounds.contains(&tcx.lifetimes.re_static) { + // no + } else if let Some(r) = outlives_bounds.first() + && outlives_bounds[1..].iter().all(|other_r| other_r == r) + { + assert!(r.type_flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS)); + r.visit_with(self)?; + } else { + // Skip lifetime parameters that are not captures. + let variances = match kind { + ty::Opaque => Some(self.tcx.variances_of(*def_id)), + _ => None, + }; + + for (idx, s) in args.iter().enumerate() { + if variances.map(|variances| variances[idx]) != Some(ty::Variance::Bivariant) { + s.visit_with(self)?; + } + } + } + } + + _ => { + ty.super_visit_with(self)?; + } + } + + ControlFlow::Continue(()) + } +} diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index cb92fc6ddb64a..0987915f4fdb3 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -9,6 +9,7 @@ use rustc_middle::ty; pub mod components; pub mod env; +pub mod for_liveness; pub mod obligations; pub mod test_type_match; pub mod verify; From f0e385d6b71a8e4b2cef49a968fa9f630d01d2e6 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 14 Oct 2023 15:28:44 +0100 Subject: [PATCH 5/5] Flesh out tests more --- tests/ui/borrowck/alias-liveness/gat-static.rs | 11 ++++++++++- .../higher-ranked-outlives-for-capture.rs | 16 ++++++++++++++++ .../higher-ranked-outlives-for-capture.stderr | 16 ++++++++++++++++ .../ui/borrowck/alias-liveness/higher-ranked.rs | 16 ++++++++++++++++ .../borrowck/alias-liveness/opaque-capture.rs | 17 +++++++++++++++++ .../alias-liveness/opaque-type-param.rs | 14 ++++++++++++++ .../alias-liveness/opaque-type-param.stderr | 13 +++++++++++++ tests/ui/borrowck/alias-liveness/rpit-static.rs | 16 ++++++++++++---- .../ui/borrowck/alias-liveness/rpitit-static.rs | 8 +++++++- tests/ui/borrowck/alias-liveness/rtn-static.rs | 8 +++++++- .../impl-trait/bivariant-lifetime-liveness.rs | 15 +++++++++++++++ 11 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.rs create mode 100644 tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.stderr create mode 100644 tests/ui/borrowck/alias-liveness/higher-ranked.rs create mode 100644 tests/ui/borrowck/alias-liveness/opaque-capture.rs create mode 100644 tests/ui/borrowck/alias-liveness/opaque-type-param.rs create mode 100644 tests/ui/borrowck/alias-liveness/opaque-type-param.stderr create mode 100644 tests/ui/impl-trait/bivariant-lifetime-liveness.rs diff --git a/tests/ui/borrowck/alias-liveness/gat-static.rs b/tests/ui/borrowck/alias-liveness/gat-static.rs index 1ab40fe828c36..92153124af932 100644 --- a/tests/ui/borrowck/alias-liveness/gat-static.rs +++ b/tests/ui/borrowck/alias-liveness/gat-static.rs @@ -8,7 +8,7 @@ trait Foo { fn assoc(&mut self) -> Self::Assoc<'_>; } -fn test(mut t: T) +fn overlapping_mut(mut t: T) where T: Foo, for<'a> T::Assoc<'a>: 'static, @@ -17,4 +17,13 @@ where let b = t.assoc(); } +fn live_past_borrow(mut t: T) +where + T: Foo, + for<'a> T::Assoc<'a>: 'static { + let x = t.assoc(); + drop(t); + drop(x); +} + fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.rs b/tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.rs new file mode 100644 index 0000000000000..1f26c7babf210 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.rs @@ -0,0 +1,16 @@ +// known-bug: #42940 + +trait Captures<'a> {} +impl Captures<'_> for T {} + +trait Outlives<'a>: 'a {} +impl<'a, T: 'a> Outlives<'a> for T {} + +// Test that we treat `for<'a> Opaque: 'a` as `Opaque: 'static` +fn test<'o>(v: &'o Vec) -> impl Captures<'o> + for<'a> Outlives<'a> {} + +fn statik() -> impl Sized { + test(&vec![]) +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.stderr b/tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.stderr new file mode 100644 index 0000000000000..58a42d8afe4d7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.stderr @@ -0,0 +1,16 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/higher-ranked-outlives-for-capture.rs:13:11 + | +LL | test(&vec![]) + | ------^^^^^^- + | | | + | | creates a temporary value which is freed while still in use + | argument requires that borrow lasts for `'static` +LL | } + | - temporary value is freed at the end of this statement + | + = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/borrowck/alias-liveness/higher-ranked.rs b/tests/ui/borrowck/alias-liveness/higher-ranked.rs new file mode 100644 index 0000000000000..afd0d3b31e3f3 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/higher-ranked.rs @@ -0,0 +1,16 @@ +// check-pass + +trait Captures<'a> {} +impl Captures<'_> for T {} + +trait Outlives<'a>: 'a {} +impl<'a, T: 'a> Outlives<'a> for T {} + +// Test that we treat `for<'a> Opaque: 'a` as `Opaque: 'static` +fn test<'o>(v: &'o Vec) -> impl Captures<'o> + for<'a> Outlives<'a> {} + +fn opaque_doesnt_use_temporary() { + let a = test(&vec![]); +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/opaque-capture.rs b/tests/ui/borrowck/alias-liveness/opaque-capture.rs new file mode 100644 index 0000000000000..f4ca2728bdbed --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/opaque-capture.rs @@ -0,0 +1,17 @@ +// check-pass + +// Check that opaques capturing early and late-bound vars correctly mark +// regions required to be live using the item bounds. + +trait Captures<'a> {} +impl Captures<'_> for T {} + +fn captures_temp_late<'a>(x: &'a Vec) -> impl Sized + Captures<'a> + 'static {} +fn captures_temp_early<'a: 'a>(x: &'a Vec) -> impl Sized + Captures<'a> + 'static {} + +fn test() { + let x = captures_temp_early(&vec![]); + let y = captures_temp_late(&vec![]); +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/opaque-type-param.rs b/tests/ui/borrowck/alias-liveness/opaque-type-param.rs new file mode 100644 index 0000000000000..a292463b2ac45 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/opaque-type-param.rs @@ -0,0 +1,14 @@ +// known-bug: #42940 + +trait Trait {} +impl Trait for () {} + +fn foo<'a>(s: &'a str) -> impl Trait + 'static { + bar(s) +} + +fn bar>(s: P) -> impl Trait + 'static { + () +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/opaque-type-param.stderr b/tests/ui/borrowck/alias-liveness/opaque-type-param.stderr new file mode 100644 index 0000000000000..e1fbbc14f44ee --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/opaque-type-param.stderr @@ -0,0 +1,13 @@ +error[E0700]: hidden type for `impl Trait + 'static` captures lifetime that does not appear in bounds + --> $DIR/opaque-type-param.rs:7:5 + | +LL | fn foo<'a>(s: &'a str) -> impl Trait + 'static { + | -- -------------------- opaque type defined here + | | + | hidden type `impl Trait + 'static` captures the lifetime `'a` as defined here +LL | bar(s) + | ^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0700`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-static.rs b/tests/ui/borrowck/alias-liveness/rpit-static.rs index 8637d5fb720ea..45da3edb8780b 100644 --- a/tests/ui/borrowck/alias-liveness/rpit-static.rs +++ b/tests/ui/borrowck/alias-liveness/rpit-static.rs @@ -3,12 +3,20 @@ trait Captures<'a> {} impl Captures<'_> for T {} -fn foo(x: &i32) -> impl Sized + Captures<'_> + 'static {} +fn foo(x: &mut i32) -> impl Sized + Captures<'_> + 'static {} -fn main() { +fn overlapping_mut() { + let i = &mut 1; + let x = foo(i); + let y = foo(i); +} + +fn live_past_borrow() { let y; { - let x = 1; - y = foo(&x); + let x = &mut 1; + y = foo(x); } } + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/rpitit-static.rs b/tests/ui/borrowck/alias-liveness/rpitit-static.rs index ed7b496a42ba9..2cc68d2bf3d81 100644 --- a/tests/ui/borrowck/alias-liveness/rpitit-static.rs +++ b/tests/ui/borrowck/alias-liveness/rpitit-static.rs @@ -4,7 +4,13 @@ trait Foo { fn rpitit(&mut self) -> impl Sized + 'static; } -fn test(mut t: T) { +fn live_past_borrow(mut t: T) { + let x = t.rpitit(); + drop(t); + drop(x); +} + +fn overlapping_mut(mut t: T) { let a = t.rpitit(); let b = t.rpitit(); } diff --git a/tests/ui/borrowck/alias-liveness/rtn-static.rs b/tests/ui/borrowck/alias-liveness/rtn-static.rs index c850f0122e5a4..1f136b8b998b2 100644 --- a/tests/ui/borrowck/alias-liveness/rtn-static.rs +++ b/tests/ui/borrowck/alias-liveness/rtn-static.rs @@ -7,9 +7,15 @@ trait Foo { fn borrow(&mut self) -> impl Sized + '_; } +fn live_past_borrow>(mut t: T) { + let x = t.borrow(); + drop(t); + drop(x); +} + // Test that the `'_` item bound in `borrow` does not cause us to // overlook the `'static` RTN bound. -fn test>(mut t: T) { +fn overlapping_mut>(mut t: T) { let x = t.borrow(); let x = t.borrow(); } diff --git a/tests/ui/impl-trait/bivariant-lifetime-liveness.rs b/tests/ui/impl-trait/bivariant-lifetime-liveness.rs new file mode 100644 index 0000000000000..fe99fe3f34068 --- /dev/null +++ b/tests/ui/impl-trait/bivariant-lifetime-liveness.rs @@ -0,0 +1,15 @@ +// check-pass +// issue: 116794 + +// Uncaptured lifetimes should not be required to be live. + +struct Invariant(*mut T); + +fn opaque<'a: 'a>(_: &'a str) -> Invariant { + Invariant(&mut ()) +} + +fn main() { + let x = opaque(&String::new()); + drop(x); +}