diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 840210496eb44..25dc718003855 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -2,7 +2,6 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_index::bit_set::DenseBitSet; use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; -use rustc_infer::infer::outlives::for_liveness; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, HasLocalDecls, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; use rustc_middle::ty::relate::Relate; @@ -14,6 +13,7 @@ use rustc_mir_dataflow::{Analysis, ResultsCursor}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::ObligationCtxt; +use rustc_trait_selection::traits::outlives_for_liveness::FreeRegionsVisitor; use rustc_trait_selection::traits::query::dropck_outlives; use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOp, TypeOpOutput}; use tracing::debug; @@ -611,7 +611,7 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { values::pretty_print_points(location_map, live_at.iter()), ); - value.visit_with(&mut for_liveness::FreeRegionsVisitor { + value.visit_with(&mut FreeRegionsVisitor { tcx: typeck.tcx(), param_env: typeck.infcx.param_env, op: |r| { diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index f495df360e6d9..c37549faa629c 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -13,8 +13,8 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_hir::{AmbigArg, ItemKind, find_attr}; +use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::outlives::env::OutlivesEnvironment; -use rustc_infer::infer::{self, InferCtxt, SubregionOrigin, TyCtxtInferExt}; use rustc_infer::traits::PredicateObligations; use rustc_lint_defs::builtin::SHADOWING_SUPERTRAIT_ITEMS; use rustc_macros::Diagnostic; @@ -30,7 +30,9 @@ use rustc_middle::{bug, span_bug}; use rustc_session::errors::feature_err; use rustc_span::{DUMMY_SP, Span, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; -use rustc_trait_selection::regions::{InferCtxtRegionExt, OutlivesEnvironmentBuildExt}; +use rustc_trait_selection::regions::{ + InferCtxtRegionExt, OutlivesEnvironmentBuildExt, region_known_to_outlive, ty_known_to_outlive, +}; use rustc_trait_selection::traits::misc::{ ConstParamTyImplementationError, type_allowed_to_implement_const_param_ty, }; @@ -695,70 +697,6 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( Some(bounds) } -/// Given a known `param_env` and a set of well formed types, can we prove that -/// `ty` outlives `region`. -fn ty_known_to_outlive<'tcx>( - tcx: TyCtxt<'tcx>, - id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - wf_tys: &FxIndexSet>, - ty: Ty<'tcx>, - region: ty::Region<'tcx>, -) -> bool { - test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { - infcx.register_type_outlives_constraint_inner(infer::TypeOutlivesConstraint { - sub_region: region, - sup_type: ty, - origin: SubregionOrigin::RelateParamBound(DUMMY_SP, ty, None), - }); - }) -} - -/// Given a known `param_env` and a set of well formed types, can we prove that -/// `region_a` outlives `region_b` -fn region_known_to_outlive<'tcx>( - tcx: TyCtxt<'tcx>, - id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - wf_tys: &FxIndexSet>, - region_a: ty::Region<'tcx>, - region_b: ty::Region<'tcx>, -) -> bool { - test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { - infcx.sub_regions( - SubregionOrigin::RelateRegionParamBound(DUMMY_SP, None), - region_b, - region_a, - ty::VisibleForLeakCheck::Unreachable, - ); - }) -} - -/// Given a known `param_env` and a set of well formed types, set up an -/// `InferCtxt`, call the passed function (to e.g. set up region constraints -/// to be tested), then resolve region and return errors -fn test_region_obligations<'tcx>( - tcx: TyCtxt<'tcx>, - id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - wf_tys: &FxIndexSet>, - add_constraints: impl FnOnce(&InferCtxt<'tcx>), -) -> bool { - // Unfortunately, we have to use a new `InferCtxt` each call, because - // region constraints get added and solved there and we need to test each - // call individually. - let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); - - add_constraints(&infcx); - - let errors = infcx.resolve_regions(id, param_env, wf_tys.iter().copied()); - debug!(?errors, "errors"); - - // If we were able to prove that the type outlives the region without - // an error, it must be because of the implied or explicit bounds... - errors.is_empty() -} - /// TypeVisitor that looks for uses of GATs like /// `>::GAT` and adds the arguments `P0..Pm` into /// the two vectors, `regions` and `types` (depending on their kind). For each diff --git a/compiler/rustc_infer/src/infer/outlives/for_liveness.rs b/compiler/rustc_infer/src/infer/outlives/for_liveness.rs deleted file mode 100644 index b4594bd60f3ba..0000000000000 --- a/compiler/rustc_infer/src/infer/outlives/for_liveness.rs +++ /dev/null @@ -1,111 +0,0 @@ -use rustc_middle::ty::{ - self, Flags, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - Unnormalized, -}; - -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_region(&mut self, r: ty::Region<'tcx>) { - match r.kind() { - // ignore bound regions, keep visiting - ty::ReBound(_, _) => {} - _ => (self.op)(r), - } - } - - fn visit_ty(&mut self, ty: Ty<'tcx>) { - // We're only interested in types involving regions - if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { - return; - } - - // FIXME: Don't consider alias bounds on types that have escaping bound - // vars. See #117455. - if ty.has_escaping_bound_vars() { - return ty.super_visit_with(self); - } - - 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(ty::AliasTy { kind, args, .. }) => { - let tcx = self.tcx; - let param_env = self.param_env; - let outlives_bounds: Vec<_> = tcx - .item_bounds(kind.def_id()) - .iter_instantiated(tcx, args) - .map(Unnormalized::skip_norm_wip) - .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, - &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 captured, since they do - // not need to be live. - let variances = tcx.opt_alias_variances(kind); - - for (idx, s) in args.iter().enumerate() { - if variances.map(|variances| variances[idx]) != Some(ty::Bivariant) { - s.visit_with(self); - } - } - } - } - - _ => ty.super_visit_with(self), - } - } -} diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index 92b47295ade88..76db3830d3962 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -16,7 +16,6 @@ use crate::infer::lexical_region_resolve; use crate::infer::region_constraints::ConstraintKind; pub mod env; -pub mod for_liveness; pub mod obligations; pub mod test_type_match; pub(crate) mod verify; diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index cbd6afd68473a..cfb6f6569afe8 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -416,6 +416,7 @@ provide! { tcx, def_id, other, cdata, } anon_const_kind => { table } const_of_item => { table } + live_regions_for_opaque_from_outlives_bounds => { table } } pub(in crate::rmeta) fn provide(providers: &mut Providers) { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 1483de572c341..c8c3da201e274 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1620,6 +1620,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); } + record!(self.tables.live_regions_for_opaque_from_outlives_bounds[def_id] <- tcx.live_regions_for_opaque_from_outlives_bounds(def_id)); } if let DefKind::AnonConst = def_kind { record!(self.tables.anon_const_kind[def_id] <- self.tcx.anon_const_kind(def_id)); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 6a396faf914cc..8c6f25a078965 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -473,6 +473,7 @@ define_tables! { anon_const_kind: Table>, const_of_item: Table>>>, associated_types_for_impl_traits_in_trait_or_impl: Table>>>, + live_regions_for_opaque_from_outlives_bounds: Table>>>>>, } #[derive(TyEncodable, TyDecodable)] diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 1531584e99788..e87e6bc5992e4 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use rustc_data_structures::unord::UnordMap; use rustc_hir::def_id::DefIndex; use rustc_index::{Idx, IndexVec}; -use rustc_middle::ty::{Binder, EarlyBinder}; +use rustc_middle::ty::{Binder, EarlyBinder, Region}; use rustc_span::Symbol; use crate::rmeta::{LazyArray, LazyValue}; @@ -48,6 +48,10 @@ impl ParameterizedOverTcx for LazyArray { type Value<'tcx> = LazyArray>; } +impl ParameterizedOverTcx for Region<'static> { + type Value<'tcx> = Region<'tcx>; +} + macro_rules! trivially_parameterized_over_tcx { ($($ty:ty),+ $(,)?) => { $( diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 5add2cf09b7b8..4662de9aff45c 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2118,6 +2118,24 @@ rustc_queries! { desc { "listing captured lifetimes for opaque `{}`", tcx.def_path_str(def_id) } } + /// For an opaque type, return the list of potentially live args from the set of outlives bounds on that opaque. + /// ```ignore (illustrative) + /// // Edition 2024: all args are captured + /// fn foo<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized + 'a {} + /// fn bar<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized + 'static {} + /// fn baz<'a, 'b, T: 'static>(&'a &'b T) -> impl Sized {} + /// ``` + /// + /// In the above: + /// - `foo` outlives `'a`, but we know that `'b: 'a` holds, so `'b` is *also* potentially live + /// - `bar` outlives `'static`, so we know that all lifetimes are potentially live and we can return an empty set + /// - `baz` has not outlives bound, so return `None` and let the caller decide what to do + query live_regions_for_opaque_from_outlives_bounds(def_id: DefId) -> &'tcx Option>>> { + arena_cache + desc { "identifying live args for opaque `{}`", tcx.def_path_str(def_id) } + separate_provide_extern + } + /// Computes the visibility of the provided `def_id`. /// /// If the item from the `def_id` doesn't have a visibility, it will panic. For example diff --git a/compiler/rustc_trait_selection/src/regions.rs b/compiler/rustc_trait_selection/src/regions.rs index 866be1e532661..64de92b8765a0 100644 --- a/compiler/rustc_trait_selection/src/regions.rs +++ b/compiler/rustc_trait_selection/src/regions.rs @@ -1,11 +1,14 @@ +use rustc_data_structures::fx::FxIndexSet; use rustc_hir::def_id::LocalDefId; use rustc_infer::infer::outlives::env::OutlivesEnvironment; -use rustc_infer::infer::{InferCtxt, RegionResolutionError}; +use rustc_infer::infer::{ + InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt, TypeOutlivesConstraint, +}; use rustc_macros::extension; use rustc_middle::traits::ObligationCause; use rustc_middle::traits::query::NoSolution; -use rustc_middle::ty::{self, Ty, Unnormalized, elaborate}; -use rustc_span::Span; +use rustc_middle::ty::{self, Ty, TyCtxt, TypingMode, Unnormalized, elaborate}; +use rustc_span::{DUMMY_SP, Span}; use crate::traits::ScrubbedTraitError; use crate::traits::outlives_bounds::InferCtxtExt; @@ -120,3 +123,67 @@ impl<'tcx> InferCtxt<'tcx> { ) } } + +/// Given a known `param_env` and a set of well formed types, can we prove that +/// `ty` outlives `region`. +pub fn ty_known_to_outlive<'tcx>( + tcx: TyCtxt<'tcx>, + id: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + wf_tys: &FxIndexSet>, + ty: Ty<'tcx>, + region: ty::Region<'tcx>, +) -> bool { + test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { + infcx.register_type_outlives_constraint_inner(TypeOutlivesConstraint { + sub_region: region, + sup_type: ty, + origin: SubregionOrigin::RelateParamBound(DUMMY_SP, ty, None), + }); + }) +} + +/// Given a known `param_env` and a set of well formed types, can we prove that +/// `region_a` outlives `region_b` +pub fn region_known_to_outlive<'tcx>( + tcx: TyCtxt<'tcx>, + id: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + wf_tys: &FxIndexSet>, + region_a: ty::Region<'tcx>, + region_b: ty::Region<'tcx>, +) -> bool { + test_region_obligations(tcx, id, param_env, wf_tys, |infcx| { + infcx.sub_regions( + SubregionOrigin::RelateRegionParamBound(DUMMY_SP, None), + region_b, + region_a, + ty::VisibleForLeakCheck::Unreachable, + ); + }) +} + +/// Given a known `param_env` and a set of well formed types, set up an +/// `InferCtxt`, call the passed function (to e.g. set up region constraints +/// to be tested), then resolve region and return errors +pub fn test_region_obligations<'tcx>( + tcx: TyCtxt<'tcx>, + id: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + wf_tys: &FxIndexSet>, + add_constraints: impl FnOnce(&InferCtxt<'tcx>), +) -> bool { + // Unfortunately, we have to use a new `InferCtxt` each call, because + // region constraints get added and solved there and we need to test each + // call individually. + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + + add_constraints(&infcx); + + let errors = infcx.resolve_regions(id, param_env, wf_tys.iter().copied()); + tracing::debug!(?errors, "errors"); + + // If we were able to prove that the type outlives the region without + // an error, it must be because of the implied or explicit bounds... + errors.is_empty() +} diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index c37b50e47129a..edd32291fbdb2 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -12,6 +12,7 @@ mod fulfill; pub mod misc; pub mod normalize; pub mod outlives_bounds; +pub mod outlives_for_liveness; pub mod project; pub mod query; #[allow(hidden_glob_reexports)] @@ -947,6 +948,8 @@ pub fn provide(providers: &mut Providers) { specialization_enabled_in: specialize::specialization_enabled_in, instantiate_and_check_impossible_predicates, is_impossible_associated_item, + live_regions_for_opaque_from_outlives_bounds: + outlives_for_liveness::live_regions_for_opaque_from_outlives_bounds, ..*providers }; } diff --git a/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs new file mode 100644 index 0000000000000..3729f40f47d5d --- /dev/null +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -0,0 +1,291 @@ +use rustc_data_structures::indexmap::IndexSet; +use rustc_hir::def_id::LocalDefId; +use rustc_middle::ty::{ + self, Flags, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, + Unnormalized, +}; + +use crate::infer::outlives::test_type_match; +use crate::infer::region_constraints::VerifyIfEq; +use crate::regions::region_known_to_outlive; + +/// For a given opaque type, this returns the set of generic args that are relevant for liveness, that can be inferred +/// from outlives bounds on the opaque. +/// +/// There are three cases to consider: +/// 1. If there are *no* outlives bounds, then we return None. +/// 2. If there is a `'static` outlives bound, then we know that all regions are irrelevant, so we return an empty list. +/// 3. If there are *any* outlives bounds, Then we find any args that outlive those bounds. +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn live_regions_for_opaque_from_outlives_bounds<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> Option>>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + + // We first want to collect the outlives bounds of the opaque. + let bounds = tcx.item_bounds(def_id).instantiate_identity().skip_norm_wip(); + tracing::debug!(?bounds); + let alias_ty = Ty::new_opaque(tcx, def_id.to_def_id(), self_identity_args); + let opaque_outlives_regions: Vec<_> = bounds + .iter() + .filter_map(|clause| { + let outlives = clause.as_type_outlives_clause()?; + if let Some(outlives) = outlives.no_bound_vars() + && outlives.0 == alias_ty + { + Some(outlives.1) + } else { + test_type_match::extract_verify_if_eq( + tcx, + &outlives + .map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }), + alias_ty, + ) + } + }) + .collect(); + tracing::debug!(?opaque_outlives_regions); + + // If there are no outlives bounds, then all (non-bivariant) args are potentially live. + if opaque_outlives_regions.is_empty() { + return None; + } + + // If any of the outlives bounds are `'static`, then we know the opaque doesn't capture + // *any* regions, so we can skip visiting any regions at all. + // + // Thinking about it, I was originally a bit concerned about something like `'a: 'static`, and + // whether or not we need to mark `'a` as live. I don't think *today* we do, since I think regions + // that outlive `'static` are special enough, but I *could* imagine some world where we need to be + // more careful about this. Given I can't find a test that goes wrong, I'm going to leave in this + // optimization. + if opaque_outlives_regions.contains(&tcx.lifetimes.re_static) { + tracing::debug!("opaque has a 'static outlives bound, so skipping visiting any regions"); + return Some(ty::EarlyBinder::bind(vec![])); + } + + // Okay, so we know we have some outlives bounds, and that none of them are `'static`. + // Now, we need to find all other potentially-live regions, + // those that outlive an outlives-bound region and are captured. + // We will map both the opaque outlives regions and the set of captured regions + // back to the parent, and then use all bounds (explicit and implied) to + // find the set of captured regions that outlive the outlives bounds. + + let opaque_captured_lifetimes = tcx.opaque_captured_lifetimes(def_id); + tracing::debug!(?opaque_captured_lifetimes); + + // Map the outlives regions to the parent regions + let generics = tcx.generics_of(def_id); + let parent_outlives_regions: Vec<_> = opaque_outlives_regions + .iter() + .map(|opaque_region| { + let region_def_id = match opaque_region.kind() { + ty::ReEarlyParam(ebr) => generics.param_at(ebr.index as usize, tcx).def_id, + _ => panic!("unexpected region `{opaque_region}` in opaque bounds"), + }; + let region_param = + generics.own_params.iter().find(|param| param.def_id == region_def_id).unwrap(); + let (_, opaque_region_def_id) = opaque_captured_lifetimes + .iter() + .find(|(_, opaque_r)| opaque_r.to_def_id() == region_def_id) + .unwrap(); + let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_region_def_id); + tracing::debug!(?region_def_id, ?region_param, ?parent_region); + parent_region + }) + .collect(); + tracing::debug!(?parent_outlives_regions); + + // Map the captured regions to the parent regions + let mut parent_captured_regions: Vec<(ty::Region<'tcx>, LocalDefId)> = + Vec::with_capacity(opaque_captured_lifetimes.len()); + for (_, opaque_lt) in opaque_captured_lifetimes.iter() { + let parent_region = tcx.map_opaque_lifetime_to_parent_lifetime(*opaque_lt); + parent_captured_regions.push((parent_region, *opaque_lt)); + } + tracing::debug!(?parent_captured_regions); + + // For implied bounds, we need the set of WF types from the parents. + // - For functions, this is all the input and output types. + // - For type alias, there are no implied bounds, so this is empty. (FIXME: the alias type itself probably should be here?) + let (parent_def_id, wf_tys) = match tcx.opaque_ty_origin(def_id) { + rustc_hir::OpaqueTyOrigin::FnReturn { parent, .. } + | rustc_hir::OpaqueTyOrigin::AsyncFn { parent, .. } => { + let fn_sig = tcx.fn_sig(parent).instantiate_identity().skip_norm_wip(); + let (liberated_fn_sig, _) = tcx.instantiate_bound_regions(fn_sig, |br| { + let kind = ty::LateParamRegionKind::from_bound(br.var, br.kind); + ty::Region::new_late_param(tcx, parent, kind) + }); + let wf_tys = liberated_fn_sig.inputs_and_output.iter().collect(); + (parent, wf_tys) + } + rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => (parent, IndexSet::default()), + }; + + // Find all the opaque's captured regions that outlive the outlives bounds using the implied bounds + let parent_param_env = tcx.param_env(parent_def_id); + tracing::debug!(?parent_param_env); + let mut opaque_live_regions = Vec::with_capacity(parent_outlives_regions.len()); + for (parent_captured_region, opaque_captured_def_id) in parent_captured_regions.iter() { + let mut all_outlives = true; + for parent_outlives_region in parent_outlives_regions.iter() { + let known_outlives = region_known_to_outlive( + tcx, + parent_def_id.expect_local(), + parent_param_env, + &wf_tys, + *parent_captured_region, + *parent_outlives_region, + ); + tracing::debug!(?parent_captured_region, ?parent_outlives_region, ?known_outlives); + if !known_outlives { + all_outlives = false; + break; + } + } + + if all_outlives { + let param = generics + .own_params + .iter() + .find(|param| param.def_id == opaque_captured_def_id.to_def_id()) + .unwrap(); + let opaque_region = + ty::Region::new_early_param(tcx, param.to_early_bound_region_data()); + opaque_live_regions.push(opaque_region); + } + } + tracing::debug!(?opaque_live_regions); + + Some(ty::EarlyBinder::bind(opaque_live_regions)) +} + +/// 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_region(&mut self, r: ty::Region<'tcx>) { + match r.kind() { + // ignore bound regions, keep visiting + ty::ReBound(_, _) => {} + _ => (self.op)(r), + } + } + + #[tracing::instrument(skip(self), level = "debug")] + fn visit_ty(&mut self, ty: Ty<'tcx>) { + // We're only interested in types involving regions + if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { + return; + } + + // FIXME: Don't consider alias bounds on types that have escaping bound + // vars. See #117455. + if ty.has_escaping_bound_vars() { + return ty.super_visit_with(self); + } + + 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(ty::AliasTy { kind, args, .. }) => { + let tcx = self.tcx; + let param_env = self.param_env; + + // Opaques are special, because there are additional captured regions that we need to consider. + if let ty::AliasTyKind::Opaque { def_id } = kind { + let opaque_outlives_args = + tcx.live_regions_for_opaque_from_outlives_bounds(def_id); + + match opaque_outlives_args { + Some(opaque_live_regions) => { + for r in opaque_live_regions.as_ref().skip_binder() { + let r = ty::EarlyBinder::bind(*r) + .instantiate(tcx, args) + .skip_norm_wip(); + r.visit_with(self); + } + } + None => { + let variances = tcx.variances_of(def_id); + for (idx, s) in args.iter().enumerate() { + if variances[idx] != ty::Bivariant { + s.visit_with(self); + } + } + } + } + + return; + } + + let outlives_bounds: Vec<_> = tcx + .item_bounds(kind.def_id()) + .iter_instantiated(tcx, args) + .map(Unnormalized::skip_norm_wip) + .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, + &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { + VerifyIfEq { ty, bound } + }), + ty, + ) + } + }) + .collect(); + tracing::debug!(?outlives_bounds); + // 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 { + // Skip lifetime parameters that are not captured, since they do + // not need to be live. + let variances = tcx.opt_alias_variances(kind); + + for (idx, s) in args.iter().enumerate() { + if variances.map(|variances| variances[idx]) != Some(ty::Bivariant) { + s.visit_with(self); + } + } + } + } + + _ => ty.super_visit_with(self), + } + } +} diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr index 48efb0ca558c0..cbbbb0bcd4818 100644 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/rpit-hide-lifetime-for-swap.rs:26:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:24:5 | LL | fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { | -- -------------- opaque type defined here diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr index e4f5475cdb0a6..982acfb0cd106 100644 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0597]: `x` does not live long enough - --> $DIR/rpit-hide-lifetime-for-swap.rs:33:38 + --> $DIR/rpit-hide-lifetime-for-swap.rs:31:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..982acfb0cd106 --- /dev/null +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,17 @@ +error[E0597]: `x` does not live long enough + --> $DIR/rpit-hide-lifetime-for-swap.rs:31:38 + | +LL | let x = [1, 2, 3]; + | - binding `x` declared here +LL | let short = Rc::new(RefCell::new(&x)); + | ^^ borrowed value does not live long enough +... +LL | let res: &'static [i32; 3] = *long.borrow(); + | ----------------- type annotation requires that `x` is borrowed for `'static` +LL | res +LL | } + | - `x` dropped here while still borrowed + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs index de5335be73dbe..0ac48a78ca59f 100644 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs @@ -2,8 +2,6 @@ //@ ignore-compare-mode-polonius (explicit revisions) //@ [edition2015] edition: 2015 //@ [edition2024] edition: 2024 -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next @@ -31,7 +29,7 @@ fn dangle() -> &'static [i32; 3] { let long = Rc::new(RefCell::new(&[4, 5, 6])); let x = [1, 2, 3]; let short = Rc::new(RefCell::new(&x)); - //[edition2024]~^ ERROR `x` does not live long enough + //[edition2024,polonius_alpha]~^ ERROR `x` does not live long enough hide(long.clone()).swap(hide(short)); let res: &'static [i32; 3] = *long.borrow(); res diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr b/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr index bc545b40c6a0e..f337632062383 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr +++ b/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr @@ -1,5 +1,5 @@ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/hidden-lifetimes.rs:38:5 + --> $DIR/hidden-lifetimes.rs:36:5 | LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a { | -- -------------- opaque type defined here @@ -14,7 +14,7 @@ LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a + use<' | ++++++++++++++++ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/hidden-lifetimes.rs:55:5 + --> $DIR/hidden-lifetimes.rs:53:5 | LL | fn hide_rc_refcell<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { | -- -------------- opaque type defined here diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr b/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr index 5165e627022c1..e6bd8756f474b 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr +++ b/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/hidden-lifetimes.rs:46:5 + --> $DIR/hidden-lifetimes.rs:44:5 | LL | hide_ref(&mut res).swap(hide_ref(&mut &x)); | -- `x` is borrowed here @@ -7,7 +7,7 @@ LL | res | ^^^ returns a value referencing data owned by the current function error[E0597]: `x` does not live long enough - --> $DIR/hidden-lifetimes.rs:62:38 + --> $DIR/hidden-lifetimes.rs:60:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/impl-trait/hidden-lifetimes.polonius_alpha.stderr b/tests/ui/impl-trait/hidden-lifetimes.polonius_alpha.stderr new file mode 100644 index 0000000000000..e6bd8756f474b --- /dev/null +++ b/tests/ui/impl-trait/hidden-lifetimes.polonius_alpha.stderr @@ -0,0 +1,26 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/hidden-lifetimes.rs:44:5 + | +LL | hide_ref(&mut res).swap(hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error[E0597]: `x` does not live long enough + --> $DIR/hidden-lifetimes.rs:60:38 + | +LL | let x = [1, 2, 3]; + | - binding `x` declared here +LL | let short = Rc::new(RefCell::new(&x)); + | ^^ borrowed value does not live long enough +LL | hide_rc_refcell(long.clone()).swap(hide_rc_refcell(short)); +LL | let res: &'static [i32; 3] = *long.borrow(); + | ----------------- type annotation requires that `x` is borrowed for `'static` +LL | res +LL | } + | - `x` dropped here while still borrowed + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0515, E0597. +For more information about an error, try `rustc --explain E0515`. diff --git a/tests/ui/impl-trait/hidden-lifetimes.rs b/tests/ui/impl-trait/hidden-lifetimes.rs index d113953c5bbc7..e03d4452dd085 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.rs +++ b/tests/ui/impl-trait/hidden-lifetimes.rs @@ -2,8 +2,6 @@ //@ ignore-compare-mode-polonius (explicit revisions) //@ [edition2015] edition: 2015 //@ [edition2024] edition: 2024 -//@ [polonius_alpha] known-bug: #153215 -//@ [polonius_alpha] check-pass //@ [polonius_alpha] edition: 2024 //@ [polonius_alpha] compile-flags: -Zpolonius=next @@ -43,7 +41,7 @@ fn dangle_ref() -> &'static [i32; 3] { let mut res = &[4, 5, 6]; let x = [1, 2, 3]; hide_ref(&mut res).swap(hide_ref(&mut &x)); - res //[edition2024]~ ERROR cannot return value referencing local variable `x` + res //[edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` } // Here we are hiding `'b` making the caller believe that `Rc>` @@ -59,7 +57,7 @@ fn hide_rc_refcell<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + fn dangle_rc_refcell() -> &'static [i32; 3] { let long = Rc::new(RefCell::new(&[4, 5, 6])); let x = [1, 2, 3]; - let short = Rc::new(RefCell::new(&x)); //[edition2024]~ ERROR `x` does not live long enough + let short = Rc::new(RefCell::new(&x)); //[edition2024,polonius_alpha]~ ERROR `x` does not live long enough hide_rc_refcell(long.clone()).swap(hide_rc_refcell(short)); let res: &'static [i32; 3] = *long.borrow(); res