From 929ec42fce8dd68edf2b56bcfe70c97003439f48 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 2 Oct 2023 16:16:09 +0000 Subject: [PATCH] Use liveness visitor when considering regions in opaque hidden types --- .../src/type_check/liveness/trace.rs | 107 ++--------- .../rustc_infer/src/infer/opaque_types.rs | 98 +--------- .../src/infer/outlives/for_liveness.rs | 170 ++++++++++++++++++ .../rustc_infer/src/infer/outlives/mod.rs | 1 + .../borrowck/alias-liveness/opaque-capture.rs | 14 ++ .../alias-liveness/opaque-type-param.rs | 14 ++ 6 files changed, 215 insertions(+), 189 deletions(-) create mode 100644 compiler/rustc_infer/src/infer/outlives/for_liveness.rs create mode 100644 tests/ui/borrowck/alias-liveness/opaque-capture.rs create mode 100644 tests/ui/borrowck/alias-liveness/opaque-type-param.rs diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 053d35eedda69..e3e10d9f76fa7 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -2,17 +2,13 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_index::bit_set::HybridBitSet; 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::FreeRegionsVisitor; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; -use rustc_middle::ty::{ - self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, -}; +use rustc_middle::ty::{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; @@ -559,99 +555,18 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { "make_all_regions_live: live_at={}", values::location_set_str(elements, live_at.iter()), ); - - struct MakeAllRegionsLive<'a, 'b, 'tcx> { - typeck: &'b mut TypeChecker<'a, 'tcx>, - live_at: &'b IntervalSet, - } - 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); - self.typeck + value.visit_with(&mut FreeRegionsVisitor { + tcx: typeck.tcx(), + param_env: typeck.param_env, + op: |r| { + let live_region_vid = typeck.borrowck_context.universal_regions.to_region_vid(r); + typeck .borrowck_context .constraints .liveness_constraints - .add_elements(live_region_vid, self.live_at); - 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 }); + .add_elements(live_region_vid, live_at); + }, + }); } fn compute_drop_data( diff --git a/compiler/rustc_infer/src/infer/opaque_types.rs b/compiler/rustc_infer/src/infer/opaque_types.rs index 09df93fcc2fde..86b444d29733d 100644 --- a/compiler/rustc_infer/src/infer/opaque_types.rs +++ b/compiler/rustc_infer/src/infer/opaque_types.rs @@ -1,6 +1,7 @@ use super::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use super::{DefineOpaqueTypes, InferResult}; use crate::errors::OpaqueHiddenTypeDiag; +use crate::infer::outlives::for_liveness::FreeRegionsVisitor; use crate::infer::{InferCtxt, InferOk}; use crate::traits::{self, PredicateObligation}; use hir::def_id::{DefId, LocalDefId}; @@ -13,11 +14,10 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::GenericArgKind; use rustc_middle::ty::{ - self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, - TypeVisitable, TypeVisitableExt, TypeVisitor, + self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeVisitable, + TypeVisitableExt, }; use rustc_span::Span; -use std::ops::ControlFlow; mod table; @@ -380,8 +380,9 @@ impl<'tcx> InferCtxt<'tcx> { .collect(), ); - concrete_ty.visit_with(&mut ConstrainOpaqueTypeRegionVisitor { + concrete_ty.visit_with(&mut FreeRegionsVisitor { tcx: self.tcx, + param_env, op: |r| self.member_constraint(opaque_type_key, span, concrete_ty, r, &choice_regions), }); } @@ -415,95 +416,6 @@ impl<'tcx> InferCtxt<'tcx> { } } -/// Visitor that requires that (almost) all regions in the type visited outlive -/// `least_region`. We cannot use `push_outlives_components` because regions in -/// closure signatures are not included in their outlives components. We need to -/// ensure all regions outlive the given bound so that we don't end up with, -/// say, `ReVar` appearing in a return type and causing ICEs when other -/// functions end up with region constraints involving regions from other -/// functions. -/// -/// We also cannot use `for_each_free_region` because for closures it includes -/// the regions parameters from the enclosing item. -/// -/// We ignore any type parameters because impl trait values are assumed to -/// capture all the in-scope type parameters. -pub struct ConstrainOpaqueTypeRegionVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> { - pub tcx: TyCtxt<'tcx>, - pub op: OP, -} - -impl<'tcx, OP> TypeVisitor> for ConstrainOpaqueTypeRegionVisitor<'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() { - ty::Closure(_, ref args) => { - // Skip lifetime parameters of the enclosing item(s) - - for upvar in args.as_closure().upvar_tys() { - upvar.visit_with(self); - } - args.as_closure().sig_as_fn_ptr_ty().visit_with(self); - } - - ty::Generator(_, ref args, _) => { - // Skip lifetime parameters of the enclosing item(s) - // Also skip the witness type, because that has no free regions. - - for upvar in args.as_generator().upvar_tys() { - upvar.visit_with(self); - } - args.as_generator().return_ty().visit_with(self); - args.as_generator().yield_ty().visit_with(self); - args.as_generator().resume_ty().visit_with(self); - } - - ty::Alias(ty::Opaque, ty::AliasTy { def_id, ref args, .. }) => { - // Skip lifetime parameters that are not captures. - let variances = self.tcx.variances_of(*def_id); - - for (v, s) in std::iter::zip(variances, args.iter()) { - if *v != ty::Variance::Bivariant { - s.visit_with(self); - } - } - } - - _ => { - ty.super_visit_with(self); - } - } - - ControlFlow::Continue(()) - } -} - pub enum UseKind { DefiningUse, OpaqueUse, 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..10d58093bd5ce --- /dev/null +++ b/compiler/rustc_infer/src/infer/outlives/for_liveness.rs @@ -0,0 +1,170 @@ +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: +/// +/// 1. 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. +/// +/// 2. If the type is a closure, only walk through the signature of the closure +/// and the upvars. Similarly, if the type is a generator, walk through the +/// three "signature" types (yield/resume/return) and its upvars. All generator +/// interior regions are bound regions, so ther is no need to walk through +/// that type. +/// +/// # How does this differ from other region visitors? +/// +/// We cannot use `push_outlives_components` because regions in closure +/// signatures are not included in their outlives components. We need to +/// ensure all regions outlive the given bound so that we don't end up with, +/// say, `ReVar` appearing in a return type and causing ICEs when other +/// functions end up with region constraints involving regions from other +/// functions. +/// +/// We also cannot use `for_each_free_region` because for closures it includes +/// the regions parameters from the enclosing item, which we know are always +/// universal, so we don't particularly care about since they're not relevant +/// for opaque type captures or computing liveness. +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() { + ty::Closure(_, ref args) => { + // Skip lifetime parameters of the enclosing item(s) + + for upvar in args.as_closure().upvar_tys() { + upvar.visit_with(self); + } + args.as_closure().sig_as_fn_ptr_ty().visit_with(self); + } + + ty::Generator(_, ref args, _) => { + // Skip lifetime parameters of the enclosing item(s) + // Also skip the witness type, because that has no free regions. + + for upvar in args.as_generator().upvar_tys() { + upvar.visit_with(self); + } + args.as_generator().return_ty().visit_with(self); + args.as_generator().yield_ty().visit_with(self); + args.as_generator().resume_ty().visit_with(self); + } + + // 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) + .filter_map(|clause| { + if let Some(outlives) = clause.as_type_outlives_clause() + && outlives.skip_binder().0 == ty + { + 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 == 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) + { + 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; 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..611ead94c9a9f --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/opaque-capture.rs @@ -0,0 +1,14 @@ +// check-pass + +trait Captures<'a> {} +impl Captures<'_> for T {} + +fn captures_temp_early<'a>(x: &'a Vec) -> impl Sized + Captures<'a> + 'static {} +fn captures_temp_late<'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..96d1a4441425c --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/opaque-type-param.rs @@ -0,0 +1,14 @@ +// check-pass + +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() {}