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 bf8593eb61882..b16095e7b0658 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, }; @@ -691,70 +693,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/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 bc1c1d8240559..c5c2182d1f715 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -417,6 +417,7 @@ provide! { tcx, def_id, other, cdata, } anon_const_kind => { table } const_of_item => { table } + args_known_to_outlive_alias_params => { 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 c232f595d4229..5736f1c3e76ee 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1613,6 +1613,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.tables .type_alias_is_lazy .set(def_id.index, self.tcx.type_alias_is_lazy(def_id)); + if self.tcx.type_alias_is_lazy(def_id) { + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } } if let DefKind::OpaqueTy = def_kind { self.encode_explicit_item_bounds(def_id); @@ -1623,6 +1626,19 @@ 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.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } + if let DefKind::AssocTy = def_kind { + let assoc_item = tcx.associated_item(def_id); + match assoc_item.container { + ty::AssocContainer::Trait => { + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } + ty::AssocContainer::InherentImpl => { + record!(self.tables.args_known_to_outlive_alias_params[def_id] <- tcx.args_known_to_outlive_alias_params(def_id)); + } + ty::AssocContainer::TraitImpl(_) => {} + } } 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 90721f0f1fc11..cccd613aa8ac1 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -480,6 +480,8 @@ define_tables! { anon_const_kind: Table>, const_of_item: Table>>>, associated_types_for_impl_traits_in_trait_or_impl: Table>>>, + live_args_for_alias_from_outlives_bounds: Table>>>>>, + args_known_to_outlive_alias_params: Table, Vec>)>>>>, } #[derive(TyEncodable, TyDecodable)] diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 40f1c85417b04..feb6859eabd5b 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, GenericArg, Region}; use rustc_span::Symbol; use crate::rmeta::{LazyArray, LazyValue}; @@ -48,6 +48,14 @@ impl ParameterizedOverTcx for LazyArray { type Value<'tcx> = LazyArray>; } +impl ParameterizedOverTcx for Region<'static> { + type Value<'tcx> = Region<'tcx>; +} + +impl ParameterizedOverTcx for GenericArg<'static> { + type Value<'tcx> = GenericArg<'tcx>; +} + macro_rules! trivially_parameterized_over_tcx { ($($ty:ty),+ $(,)?) => { $( @@ -61,6 +69,7 @@ macro_rules! trivially_parameterized_over_tcx { trivially_parameterized_over_tcx! { bool, + u32, u64, usize, std::string::String, diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index f0557c3d3381a..5d9530b86af15 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2122,6 +2122,36 @@ rustc_queries! { desc { "listing captured lifetimes for opaque `{}`", tcx.def_path_str(def_id) } } + /// For an opaque type or trait associated type, return the list of potentially live + /// (identity) generic args from the set of outlives bounds on that alias. Callers should + /// instantiate the returned args with the concrete args of the alias. + /// ```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 + /// (and so is `T`, since `T: 'static` implies `T: 'a`) + /// - `bar` outlives `'static`, so we know that no args are potentially live and we can return an empty set + /// - `baz` has no outlives bound, so return `None` and let the caller decide what to do + query live_args_for_alias_from_outlives_bounds(kind: ty::AliasTyKind<'tcx>) -> &'tcx Option>>> { + arena_cache + desc { "identifying live args for alias `{:?}`", kind } + } + + /// For each region param of an alias, the identity args that are known to + /// outlive it given only the alias's declared where-clauses. Used for liveness: + /// these are the only args whose regions the underlying type of the alias + /// could capture while satisfying an outlives bound on that param. + query args_known_to_outlive_alias_params(def_id: DefId) -> &'tcx ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + arena_cache + desc { "computing the args known to outlive each region param of alias `{}`", 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_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index 5eedeb4f9ed2e..772caa0b9f505 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -271,6 +271,18 @@ impl<'tcx> QueryKey for ty::Clauses<'tcx> { } } +impl<'tcx> QueryKey for ty::AliasTyKind<'tcx> { + fn default_span(&self, tcx: TyCtxt<'_>) -> Span { + let def_id = match self { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + tcx.def_span(*def_id) + } +} + impl<'tcx, T: QueryKey> QueryKey for ty::PseudoCanonicalInput<'tcx, T> { fn default_span(&self, tcx: TyCtxt<'_>) -> Span { self.value.default_span(tcx) 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 c062198861ebe..8b74b9039fd7f 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,10 @@ pub fn provide(providers: &mut Providers) { specialization_enabled_in: specialize::specialization_enabled_in, instantiate_and_check_impossible_predicates, is_impossible_associated_item, + live_args_for_alias_from_outlives_bounds: + outlives_for_liveness::live_args_for_alias_from_outlives_bounds, + args_known_to_outlive_alias_params: + outlives_for_liveness::args_known_to_outlive_alias_params, ..*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..69df20499f80c --- /dev/null +++ b/compiler/rustc_trait_selection/src/traits/outlives_for_liveness.rs @@ -0,0 +1,581 @@ +use rustc_data_structures::fx::FxIndexSet; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_middle::bug; +use rustc_middle::ty::{ + self, Flags, ImplTraitInTraitData, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, +}; + +use crate::infer::outlives::test_type_match; +use crate::infer::region_constraints::VerifyIfEq; +use crate::regions::{region_known_to_outlive, ty_known_to_outlive}; + +/// For a given alias type, this returns the set of (identity) generic args that +/// are relevant for liveness, that can be inferred from outlives bounds on the +/// alias itself, and the explicit and implicit outlives clauses of the alias. +/// Callers should instantiate the returned args with the concrete args of the alias. +/// +/// 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 args are +/// irrelevant, so we return an empty list. +/// 3. If there are *any* outlives bounds, then we find any args that are known +/// to outlive those bounds, since those are the args whose regions the +/// underlying type could capture. +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn live_args_for_alias_from_outlives_bounds<'tcx>( + tcx: TyCtxt<'tcx>, + kind: ty::AliasTyKind<'tcx>, +) -> Option>>> { + let def_id = match kind { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + + // We first want to collect the outlives bounds of the alias. + let bounds = tcx.item_bounds(def_id).instantiate_identity().skip_norm_wip(); + tracing::debug!(?bounds); + let alias_ty = Ty::new_alias( + tcx, + ty::IsRigid::No, + ty::AliasTy::new_from_args(tcx, kind, self_identity_args), + ); + let 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 }), + // FIXME(#155345): Region handling should generally only + // deal with rigid aliases, making sure we do so correctly + // everywhere is effort, so we're just using `No` everywhere + // for now. This should change soon. + alias_ty, + ) + } + }) + .collect(); + tracing::debug!(?outlives_regions); + + // If there are no outlives bounds, then all (non-bivariant) args are potentially live. + if outlives_regions.is_empty() { + return None; + } + + // If any of the outlives bounds are `'static`, then we know the alias + // doesn't capture *any* regions, so we can skip visiting any regions at all. + // + // 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 that we do. + // + // To dig in a bit: Think about the function using this alias. For the alias + // to be well-formed, then it must be proven that the arg (`'a` in this case) + // outlives `'static`. Well, if that is proven *once*, then it must be true + // across the entire function (because `'static` is free). + // + // I think this similarly applies to any other free region, like `'a: 'b` + // where `'b` is *also* free. Though, we of course can't know *here* which + // regions are going to be instantiated with free regions. + if outlives_regions.contains(&tcx.lifetimes.re_static) { + tracing::debug!("alias has a 'static outlives bound, so skipping visiting any regions"); + return Some(ty::EarlyBinder::bind(tcx, 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 args, those that outlive + // an outlives-bound region. `args_known_to_outlive_alias_params` does this + // for us, and in the case of opaques only includes *captured* regions, too. + + let args_known_to_outlive = + tcx.args_known_to_outlive_alias_params(def_id).as_ref().skip_binder(); + tracing::debug!(?args_known_to_outlive); + let mut live_args: Option>> = None; + for outlives_region in outlives_regions { + let Some(outlives_params) = + args_known_to_outlive.iter().find(|(r, _)| *r == outlives_region) + else { + continue; + }; + let new_live_args = outlives_params.1.iter().copied().collect(); + match &mut live_args { + None => live_args = Some(new_live_args), + Some(prev) => *prev = prev.intersection(&new_live_args).copied().collect(), + }; + } + live_args.map(|c| ty::EarlyBinder::bind(tcx, c.into_iter().collect())) +} + +/// For each region param of this alias compute the identity args that are known +/// to outlive it, given only the alias's declared where-clauses. +/// +/// Note: for opaques (including synthetic associated types from RPITITs), +/// the outlives relationships are identified in the context of the *parent*, +/// since bounds and well-formed types are not lowered. +// FIXME: this likely should return a `BitSet` instead of a `Vec>` +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn args_known_to_outlive_alias_params<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + match tcx.def_kind(def_id) { + DefKind::OpaqueTy => args_known_to_outlive_opaque_params(tcx, def_id), + DefKind::AssocTy + if let Some(ImplTraitInTraitData::Trait { fn_def_id: _, opaque_def_id }) = + tcx.opt_rpitit_info(def_id.to_def_id()) => + { + args_known_to_outlive_opaque_params(tcx, opaque_def_id.expect_local()) + } + DefKind::AssocTy | DefKind::TyAlias => { + args_known_to_outlive_associated_type_params(tcx, def_id) + } + kind => { + bug!("improper def_kind {kind:?} passed to `live_args_for_alias_from_outlives_bounds`") + } + } +} + +/// For each *captured* region of this alias, compute the *captured* identity +/// args that are known to outlive it, given the definition of the opaque type +/// in the the *parent* context. +/// +/// Some examples: +/// ```ignore (illustrative) +/// // Returns `[('a, ['a]), ('b, ['b])]` +/// fn foo<'a, 'b>() -> impl Sized + use<'a, 'b> {} +/// +/// // Returns `[('a, ['a]), ('b, ['b])]` +/// fn foo<'a: 'a, 'b>() -> impl Sized + use<'a, 'b> {} +/// +/// // Returns `[('a, ['a])]` +/// fn foo<'a, 'b>() -> impl Sized + use<'a> {} +/// +/// // Returns `[('a, ['a, 'b]), ('b, ['b])]` +/// fn foo<'a, 'b: 'a>() -> impl Sized + use<'a, 'b> {} +/// +/// // Returns `[('a, ['a, 'b]), ('b, ['b])]` +/// fn foo<'a, 'b>(_: &'a &'b ()) -> impl Sized + use<'a, 'b> {} +/// ``` +/// +/// Importantly: +/// - *All* captured regions are considered (not just those in outlives bounds) +/// - It doesn't matter if the captured region is early-bound or late-bound +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn args_known_to_outlive_opaque_params<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + + let mut result = Vec::new(); + + // 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. + let (parent_def_id, wf_tys) = match tcx.opaque_ty_origin(def_id) { + rustc_hir::OpaqueTyOrigin::FnReturn { parent, .. } + | rustc_hir::OpaqueTyOrigin::AsyncFn { parent, .. } + | rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } => { + let wf_tys = FxIndexSet::from_iter( + tcx.assumed_wf_types(parent.expect_local()).iter().map(|(ty, _)| *ty), + ); + (parent, wf_tys) + } + }; + let parent_param_env = tcx.param_env(parent_def_id); + tracing::debug!(?parent_param_env); + + // Map the outlives regions to the parent regions + let generics = tcx.generics_of(def_id); + let mut parent_outlives_regions = Vec::with_capacity(generics.own_params.len()); + for opaque_arg in self_identity_args[generics.parent_count..].iter() { + let Some(opaque_region) = opaque_arg.as_region() else { + continue; + }; + 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 parent_region = + tcx.map_opaque_lifetime_to_parent_lifetime(region_def_id.expect_local()); + tracing::debug!(?region_def_id, ?parent_region); + parent_outlives_regions.push((parent_region, opaque_region)); + } + tracing::debug!(?parent_outlives_regions); + + // For every captured region, we want to consider outlived args from two sources: + // 1) *Types*: These come from *parent* generics (and are not duplicated to the opaque) + // 2) *Captured Regions* + // + // In both cases, we need to check known outlives for the *parent* region, because that's where the param_env and wf_tys are. + for (parent_outlived_region, opaque_outlived_region) in parent_outlives_regions.iter() { + let mut opaque_outlives_args = Vec::with_capacity(self_identity_args.len()); + for parent_outlives_arg in self_identity_args[..generics.parent_count].iter() { + let type_outlives = match parent_outlives_arg.kind() { + // Consts don't have any non-static regions + ty::GenericArgKind::Const(_) => continue, + // Lifetimes should be captured + ty::GenericArgKind::Lifetime(_) => continue, + ty::GenericArgKind::Type(t) => ty_known_to_outlive( + tcx, + def_id, + parent_param_env, + &wf_tys, + t, + *parent_outlived_region, + ), + }; + if !type_outlives { + continue; + } + + // Types aren't captured, so don't need to map to the opaque + opaque_outlives_args.push(*parent_outlives_arg); + } + + for &(parent_outlives_region, opaque_region) in parent_outlives_regions.iter() { + let region_outlives = parent_outlives_region == *parent_outlived_region + || region_known_to_outlive( + tcx, + def_id, + parent_param_env, + &wf_tys, + parent_outlives_region, + *parent_outlived_region, + ); + if !region_outlives { + continue; + } + + opaque_outlives_args.push(opaque_region.into()); + } + + result.push((*opaque_outlived_region, opaque_outlives_args)); + } + + ty::EarlyBinder::bind(tcx, result) +} + +#[tracing::instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn args_known_to_outlive_associated_type_params<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> ty::EarlyBinder<'tcx, Vec<(ty::Region<'tcx>, Vec>)>> { + let self_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + let param_env = tcx.param_env(def_id); + tracing::debug!(?param_env); + let wf_tys = FxIndexSet::default(); + let mut result = Vec::new(); + for outlived_arg in self_identity_args.iter() { + let Some(outlived_region) = outlived_arg.as_region() else { + continue; + }; + let outliving_args = self_identity_args + .iter() + .filter(|arg| match arg.kind() { + ty::GenericArgKind::Lifetime(r) => { + region_known_to_outlive(tcx, def_id, param_env, &wf_tys, r, outlived_region) + } + ty::GenericArgKind::Type(t) => { + ty_known_to_outlive(tcx, def_id, param_env, &wf_tys, t, outlived_region) + } + ty::GenericArgKind::Const(_) => false, + }) + .collect(); + result.push((outlived_region, outliving_args)); + } + ty::EarlyBinder::bind(tcx, result) +} + +/// For a param-env clause `for<'v..> ::Assoc<..>: 'bound` that +/// applies to `ty` (an alias with `alias_def_id`), returns the set of (identity) args +/// that the underlying type could possibly capture, as restricted by this clause. +/// +/// As an example, let's imagine we had the following associated type definition: +/// ```ignore (illustrative) +/// type Assoc<'a, 'b, 'c: 'a> = (&'a &'c (), &'b ()); +/// ``` +/// +/// the following clause: +/// ```ignore (illustrative) +/// for<'x, 'y> T::Assoc<'x, 'x, 'y>: 'x +/// ``` +/// +/// We know from the clause alone that *given some substitution of `T:Assoc`*, +/// we know that it can capture either the first or the second region. However, +/// the bounds on the associated type itself additionally imply that the +/// third region can *also* be captured, because it outlives the first. +/// +/// Now, let's assume we had this clause: +/// ```ignore (illustrative) +/// for<'x, 'y> T::Assoc<'x, 'y, 'x>: 'x +/// ``` +/// +/// Here, we know that `'a` and `'c` could be captured, but there is no outlives +/// relationship to `'b` for either of those, so the underlying type can't +/// capture any arg containing `'b`. +/// +/// Note: because higher-ranked bounds don't have implications, there will be +/// some cases (like `for<'x, 'y, 'z> T::Assoc<'x, 'y, 'z>: 'x`) that won't +/// be satisfiable today, but the logic here should hold whenever there *is*. +/// +/// Returns `None` if the clause doesn't apply to `ty` or gives us no information. +#[tracing::instrument(level = "debug", skip(tcx), ret)] +fn live_args_for_outlives_clause<'tcx>( + tcx: TyCtxt<'tcx>, + alias_def_id: DefId, + ty: Ty<'tcx>, + outlives: ty::Binder<'tcx, ty::TypeOutlivesPredicate<'tcx>>, +) -> Option>>> { + // N.B. it's okay to skip the binder here (and in the rest of the function), + // because all variables under binders do not escape + let ty::Alias(_, ty::AliasTy { kind: clause_alias_kind, args: clause_args, .. }) = + *outlives.skip_binder().0.kind() + else { + return None; + }; + let clause_def_id = match clause_alias_kind { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + if clause_def_id != alias_def_id { + return None; + } + + // Here, we're just using this to check if the clause *could apply* to `ty`, + // but importantly we don't want to use the returned region, because that is + // the "last visited" region in `ty` that matches the outlves bound. Actually, + // we want *all* the identity regions in `ty` that match the outlives bound. + test_type_match::extract_verify_if_eq( + tcx, + &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| VerifyIfEq { ty, bound }), + ty, + )?; + + let bound_region = outlives.skip_binder().1; + let clause_identity_args = ty::GenericArgs::identity_for_item(tcx, alias_def_id); + match bound_region.kind() { + // The underlying type must outlive `'static`, so it can't capture any of the args at all. + // + // Of course, you may ask: "what if the function has a `'a: 'static` bound?" See the corresponding + // comment in `live_args_for_alias_from_outlives_bounds` for why we don't need to worry about that. + ty::ReStatic => Some(FxIndexSet::default()), + ty::ReBound(_, br) => { + // The bound is one of the clause's higher-ranked vars. Find the arg + // positions it occupies, then (at the alias's identity level) find + // all args that are known to outlive one of those positions given + // the alias's declared bounds -- only those can be captured by the + // underlying type. + let mut outlived_regions = Vec::new(); + for (clause_arg, identity_arg) in clause_args.iter().zip(clause_identity_args.iter()) { + match clause_arg.kind() { + ty::GenericArgKind::Lifetime(r) => { + if let ty::ReBound(_, arg_br) = r.kind() + && arg_br.var == br.var + { + outlived_regions.push(identity_arg.expect_region()); + } + } + ty::GenericArgKind::Type(_) | ty::GenericArgKind::Const(_) => { + // A bound var inside a type or const arg (e.g. + // `for<'a> >::Output: 'a`) + // can't be reasoned about at the identity-param level, + // so conservatively treat the clause as giving no + // restriction at all. + if clause_arg.has_escaping_bound_vars() { + return None; + } + } + } + } + if outlived_regions.is_empty() { + // The bound var doesn't appear in the args at all, so the clause + // requires the underlying type to outlive *every* region, which + // is equivalent to a `'static` bound. + return Some(FxIndexSet::default()); + } + + // The underlying type can capture any arg that's known to outlive one + // of the bound var's positions (they're all instantiated to the same + // region at any use site this clause applies to). + let args_known_to_outlive = tcx.args_known_to_outlive_alias_params(alias_def_id); + tracing::debug!(?outlived_regions, ?args_known_to_outlive); + let mut capturable_args = FxIndexSet::default(); + for &outlived_region in &outlived_regions { + // There's a bit of a dance here around `Earlybinder::skip_binder` + // and then later a `Earlybinder::bind`. This is because there's + // no real good way today to move the `EarlyBinder` inward + // declaratively without cloning the entire thing. + let (_, outliving_args) = args_known_to_outlive + .as_ref() + .skip_binder() + .iter() + .find(|(region, _)| *region == outlived_region) + .unwrap(); + capturable_args + .extend(outliving_args.iter().copied().map(|a| ty::EarlyBinder::bind(tcx, a))); + } + Some(capturable_args) + } + // A free region (e.g. `for T::Assoc<'a, 'x>: 'x`, where `'x` is free). + // This is effectively the same as `for<'a, 'b> T::Assoc<'a, 'b>: 'b`, + // but that only is sound if we either know that the second substituted + // lifetime equals `'x` or if we *constrain* that lifetime to be `'x`. + // + // In either case, something like this doesn't work today: + // ```ignore (illustrative) + // fn bar<'a, 'b>(a: &'a mut (), b: &'b ()) -> ::Assoc<'a, 'b> { b } + // fn foo<'x>() + // where + // for<'h> ::Assoc<'h, 'x>: 'x, + // { + // let a = &mut (); + // let b: &'x () = &(); + // let val1 = rpit(a, b); + // let val2 = rpit(a, b); + // drop(val1); + // drop(val32); + // } + // ``` + // So, we conservatively treat this as giving no restriction on which args can be captured. + ty::ReEarlyParam(..) => None, + // Don't know that we actually hit this (maybe `ReError`), go ahead and be conservative. + _ => None, + } +} + +/// 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 +/// the alias's item bounds. Each such bound restricts which of the alias's +/// args the underlying type could have captured, so only those (capturable) +/// args are visited. If there are no applicable bounds, we walk through the +/// alias's (non-bivariant) args 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; + } + + 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; + + // For aliases other than opaques, we have to consider two + // sources of information to identity potentially-live args: + // - Bounds on alias item itself + // - Outlives clauses on the current function that apply to the alias + // + // Each source of information *restricts* the set of potentially-live + // args independently: only the args that can be live for *every* + // source of information can be actually live, so we take the intersection. + let def_id = match kind { + ty::AliasTyKind::Projection { def_id } + | ty::AliasTyKind::Inherent { def_id } + | ty::AliasTyKind::Opaque { def_id } + | ty::AliasTyKind::Free { def_id } => def_id, + }; + let mut capturable: Option< + FxIndexSet>>, + > = None; + let mut restrict = + |capturable_args: FxIndexSet>>| { + match &mut capturable { + None => capturable = Some(capturable_args), + Some(prev) => { + *prev = prev.intersection(&capturable_args).copied().collect() + } + }; + }; + + if let Some(live_args) = tcx.live_args_for_alias_from_outlives_bounds(kind) { + restrict( + live_args + .as_ref() + .skip_binder() + .iter() + .copied() + .map(|a| ty::EarlyBinder::bind(tcx, a)) + .collect(), + ); + } + + for clause in param_env.caller_bounds() { + let Some(outlives) = clause.as_type_outlives_clause() else { + continue; + }; + if let Some(capturable_args) = + live_args_for_outlives_clause(tcx, def_id, ty, outlives) + { + restrict(capturable_args); + } + } + tracing::debug!(?capturable); + + match capturable { + Some(capturable_args) => { + for arg in capturable_args { + let arg = arg.instantiate(tcx, args).skip_norm_wip(); + arg.visit_with(self); + } + } + None => { + // 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_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 89fac9d200214..66e76d6b9d0e2 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -27,7 +27,7 @@ use crate::{ mod closure; -#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)] +#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..3c31169a8675d --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-for-swap.rs:33:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..3c31169a8675d --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-for-swap.rs:33:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..3c31169a8675d --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-for-swap.rs:33:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs new file mode 100644 index 0000000000000..9cbaa5102f015 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-for-swap.rs @@ -0,0 +1,48 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +// Test to show what happens if we were not careful and allowed invariant +// lifetimes to escape through a GAT. +// +// Specifically we swap a long lived and short lived reference, giving us a +// dangling pointer. + +trait Swap: Sized { + fn swap(self, other: Self); +} +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} +trait Hider { + type Hidden<'a, 'b, T: 'static>: Swap + 'a + where + Self: 'a, + 'b: 'a; + fn hide_ref<'a, 'b, T: 'static>(&self, x: &'a mut &'b T) -> Self::Hidden<'a, 'b, T>; +} +fn dangle_ref(h: &H) -> &'static [i32; 3] { + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` +} +struct H; +impl Hider for H { + type Hidden<'a, 'b, T: 'static> + = &'a mut &'b T + where + Self: 'a, + 'b: 'a; + fn hide_ref<'a, 'b, T: 'static>(&self, x: &'a mut &'b T) -> Self::Hidden<'a, 'b, T> { + x + } +} +fn main() { + println!("{:?}", dangle_ref(&H)); +} diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..ccc8dfbe5fc88 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | h.hide(&mut res).swap(h.hide(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..ccc8dfbe5fc88 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | h.hide(&mut res).swap(h.hide(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..ccc8dfbe5fc88 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/gat-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | h.hide(&mut res).swap(h.hide(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs new file mode 100644 index 0000000000000..3513bd432a9c6 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-hide-lifetime-in-type-arg-for-swap.rs @@ -0,0 +1,48 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +// Like gat-hide-lifetime-for-swap, but the invariant lifetime is hidden +// inside a *type* argument of the GAT rather than being a region argument. +// The underlying type can capture `U` (and thus all of its regions) because +// of the declared `U: 'a` bound, so the regions inside the type argument +// must be considered live too. + +trait Swap: Sized { + fn swap(self, other: Self); +} +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} +trait Hider { + type Hidden<'a, U>: Swap + 'a + where + Self: 'a, + U: Swap + 'a; + fn hide<'a, U: Swap + 'a>(&'a self, x: U) -> Self::Hidden<'a, U>; +} +fn dangle(h: &H) -> &'static [i32; 3] { + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + h.hide(&mut res).swap(h.hide(&mut &x)); + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` +} +struct H; +impl Hider for H { + type Hidden<'a, U> + = U + where + Self: 'a, + U: Swap + 'a; + fn hide<'a, U: Swap + 'a>(&'a self, x: U) -> Self::Hidden<'a, U> { + x + } +} +fn main() { + println!("{:?}", dangle(&H)); +} diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr new file mode 100644 index 0000000000000..9b71acde986f7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2015.stderr @@ -0,0 +1,13 @@ +error[E0499]: cannot borrow `t` as mutable more than once at a time + --> $DIR/gat-outlives-env.rs:49:13 + | +LL | let a = t.changes(static_unit); + | - first mutable borrow occurs here +LL | let b = t.changes(static_unit); + | ^ second mutable borrow occurs here +LL | } + | - first borrow might be used here, when `a` is dropped and runs the destructor for type `::Changes<'_, '_>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr new file mode 100644 index 0000000000000..9b71acde986f7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.edition2024.stderr @@ -0,0 +1,13 @@ +error[E0499]: cannot borrow `t` as mutable more than once at a time + --> $DIR/gat-outlives-env.rs:49:13 + | +LL | let a = t.changes(static_unit); + | - first mutable borrow occurs here +LL | let b = t.changes(static_unit); + | ^ second mutable borrow occurs here +LL | } + | - first borrow might be used here, when `a` is dropped and runs the destructor for type `::Changes<'_, '_>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr new file mode 100644 index 0000000000000..9b71acde986f7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.polonius_alpha.stderr @@ -0,0 +1,13 @@ +error[E0499]: cannot borrow `t` as mutable more than once at a time + --> $DIR/gat-outlives-env.rs:49:13 + | +LL | let a = t.changes(static_unit); + | - first mutable borrow occurs here +LL | let b = t.changes(static_unit); + | ^ second mutable borrow occurs here +LL | } + | - first borrow might be used here, when `a` is dropped and runs the destructor for type `::Changes<'_, '_>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs new file mode 100644 index 0000000000000..1c1b728e486b1 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-env.rs @@ -0,0 +1,52 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +// Demonstrates that you can use a outlives bound on an associated type in a +// where clause to restrict the lifetimes that may be contained within. + +trait Updater { + type Changes<'a, 'b: 'a> + where + Self: 'a; + fn changes<'a, 'b>(&'a mut self, _value: &'b u8) -> Self::Changes<'a, 'b>; +} + +// Nothing can be captured. +fn overlapping_mut(mut t: T) +where + T: Updater, + for<'a, 'b> T::Changes<'a, 'b>: 'static, +{ + let static_unit: &'static u8 = &0; + let a = t.changes(static_unit); + let b = t.changes(static_unit); +} + +// Only `&mut self` is captured not `&'static u8`, and that borrow is killed on the next call. +fn overlapping_mut2(mut t: T) +where + T: Updater, + for<'a, 'b> T::Changes<'a, 'b>: 'b, +{ + let static_unit: &'static u8 = &0; + let a = t.changes(static_unit); + let b = t.changes(static_unit); +} + +// Both `&mut self` and `&'static u8` are captured, because the `'static` lifetime +// makes the associated type live for the entire function body. +fn overlapping_mut3<'b, T>(mut t: T) +where + T: Updater, + for<'a> T::Changes<'a, 'b>: 'a, +{ + let static_unit: &'static u8 = &0; + let a = t.changes(static_unit); + let b = t.changes(static_unit); //[edition2015,edition2024,polonius_alpha]~ ERROR cannot borrow `t` as mutable more than once at a time +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr new file mode 100644 index 0000000000000..9317846041925 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.edition2024.stderr @@ -0,0 +1,13 @@ +error[E0506]: cannot assign to `cluster` because it is borrowed + --> $DIR/gat-outlives-implied.rs:19:9 + | +LL | let changes = self.changes(&cluster); + | -------- `cluster` is borrowed here +LL | cluster = 1; + | ^^^^^^^^^^^ `cluster` is assigned to here but it was already borrowed +LL | let _ = changes; + | ------- borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr new file mode 100644 index 0000000000000..9317846041925 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.polonius_alpha.stderr @@ -0,0 +1,13 @@ +error[E0506]: cannot assign to `cluster` because it is borrowed + --> $DIR/gat-outlives-implied.rs:19:9 + | +LL | let changes = self.changes(&cluster); + | -------- `cluster` is borrowed here +LL | cluster = 1; + | ^^^^^^^^^^^ `cluster` is assigned to here but it was already borrowed +LL | let _ = changes; + | ------- borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs new file mode 100644 index 0000000000000..d5afb11742c35 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives-implied.rs @@ -0,0 +1,24 @@ +//@ revisions: edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ edition: 2024 +//@ check-fail + +// Tests that liveness for regions in associated types considers outlives +// bounds, and the transitive implied outlives bounds from those. + +trait Updater { + // Because `'b` is known to outlive `'a`, then we must consider that `'b` + // may be live in `Self::Changes<'a, 'b>`. + type Changes<'a, 'b: 'a>: 'a + where + Self: 'a; + fn changes<'a, 'b>(&'a self, _value: &'b u8) -> Self::Changes<'a, 'b>; + fn run(&self) { + let mut cluster = 0u8; + let changes = self.changes(&cluster); + cluster = 1; //[edition2024,polonius_alpha]~ ERROR cannot assign to `cluster` because it is borrowed + let _ = changes; + } +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/gat-outlives.rs b/tests/ui/borrowck/alias-liveness/gat-outlives.rs new file mode 100644 index 0000000000000..ce6beffb19043 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-outlives.rs @@ -0,0 +1,23 @@ +//@ revisions: edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ check-pass + +// Tests that liveness for regions in associated types considers outlives bounds. + + +trait Updater { + // Because `'b` isn't known to outlive `'a`, then we know that + // `Self::Changes<'a, 'b>` should not need to consider `'b` to be live. + type Changes<'a, 'b>: 'a + where + Self: 'a; + fn changes<'a, 'b>(&'a self, _value: &'b u8) -> Self::Changes<'a, 'b>; + fn run(&self) { + let mut cluster = 0u8; + let changes = self.changes(&cluster); + cluster = 1; + let _ = changes; + } +} + +fn main() {} diff --git a/tests/ui/borrowck/alias-liveness/nested-rpit.rs b/tests/ui/borrowck/alias-liveness/nested-rpit.rs new file mode 100644 index 0000000000000..f927fe8c5d12a --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/nested-rpit.rs @@ -0,0 +1,77 @@ +//@ edition: 2024 +//@ compile-flags: --crate-type lib +//@ check-pass + +struct ScopeGuard +where + F: FnMut(&mut T), +{ + dropfn: F, + value: T, +} + +impl core::ops::Deref for ScopeGuard +where + F: FnMut(&mut T), +{ + type Target = T; + #[inline] + fn deref(&self) -> &T { + &self.value + } +} + +impl core::ops::DerefMut for ScopeGuard +where + F: FnMut(&mut T), +{ + #[inline] + fn deref_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl Drop for ScopeGuard +where + F: FnMut(&mut T), +{ + #[inline] + fn drop(&mut self) { + (self.dropfn)(&mut self.value); + } +} + +struct RawTableInner; + +impl RawTableInner { + fn prepare_resize<'a, A>( + &self, + alloc: &'a A, + ) -> Result, ()> + { + Ok(ScopeGuard { + value: RawTableInner, + dropfn: move |self_| {}, + }) + } + + fn resize_inner( + &mut self, + alloc: &A, + hasher: &dyn Fn(&mut Self, usize) -> u64, + ) -> Result<(), ()> + { + let mut new_table = self.prepare_resize(alloc)?; + + for _ in 0..10 { + hasher(self, 0); + new_table.prepare_insert_index(); + } + + core::mem::swap(self, &mut new_table); + + Ok(()) + } + + fn prepare_insert_index(&mut self) {} +} diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2015.stderr diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.edition2024.stderr diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.rs b/tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.rs similarity index 100% rename from tests/ui/impl-trait/alias-liveness/rpit-hidden-erased-unsoundness.rs rename to tests/ui/borrowck/alias-liveness/rpit-hidden-erased-unsoundness.rs diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr similarity index 94% rename from tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr index bc545b40c6a0e..ac839b0c5dc48 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr +++ b/tests/ui/borrowck/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/hidden-lifetimes.rs:38:5 + --> $DIR/rpit-hide-lifetime-for-swap.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/rpit-hide-lifetime-for-swap.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/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr similarity index 91% rename from tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr rename to tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr index 5165e627022c1..17916dac8afd7 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/hidden-lifetimes.rs:46:5 + --> $DIR/rpit-hide-lifetime-for-swap.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/rpit-hide-lifetime-for-swap.rs:60:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..17916dac8afd7 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,26 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-for-swap.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/rpit-hide-lifetime-for-swap.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/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs similarity index 88% rename from tests/ui/impl-trait/hidden-lifetimes.rs rename to tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-for-swap.rs index d113953c5bbc7..e03d4452dd085 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.rs +++ b/tests/ui/borrowck/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 @@ -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 diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..aa7fc52d7d463 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..aa7fc52d7d463 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..aa7fc52d7d463 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpit-hide-lifetime-in-type-arg-for-swap.rs:33:5 + | +LL | hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs new file mode 100644 index 0000000000000..35d51fe791a84 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpit-hide-lifetime-in-type-arg-for-swap.rs @@ -0,0 +1,38 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +// Like rpit-hide-lifetime-for-swap, but the invariant lifetime is hidden +// inside a *type* argument of the opaque rather than being captured as a +// region argument. The hidden type can capture `U` (and thus all of its +// regions) because of the `U: 'a` bound, so the regions inside the type +// argument must be considered live too. + +trait Swap: Sized { + fn swap(self, other: Self); +} + +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} + +fn hide<'a, U: Swap + 'a>(_: &'a u8, x: U) -> impl Swap + 'a { + x +} + +fn dangle() -> &'static [i32; 3] { + let lock = 0u8; + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + hide(&lock, &mut res).swap(hide(&lock, &mut &x)); + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` +} + +fn main() { + println!("{:?}", dangle()); +} diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr new file mode 100644 index 0000000000000..0c2e1d3c7817a --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2015.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpitit-hide-lifetime-for-swap.rs:31:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr new file mode 100644 index 0000000000000..0c2e1d3c7817a --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.edition2024.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpitit-hide-lifetime-for-swap.rs:31:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr new file mode 100644 index 0000000000000..0c2e1d3c7817a --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.polonius_alpha.stderr @@ -0,0 +1,11 @@ +error[E0515]: cannot return value referencing local variable `x` + --> $DIR/rpitit-hide-lifetime-for-swap.rs:31:5 + | +LL | h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + | -- `x` is borrowed here +LL | res + | ^^^ returns a value referencing data owned by the current function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs new file mode 100644 index 0000000000000..ee20d00ae77cb --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/rpitit-hide-lifetime-for-swap.rs @@ -0,0 +1,38 @@ +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + +// Test to show what happens if we were not careful and allowed invariant +// lifetimes to escape though an impl trait. +// +// Specifically we swap a long lived and short lived reference, giving us a +// dangling pointer. + +trait Swap: Sized { + fn swap(self, other: Self); +} +impl Swap for &mut T { + fn swap(self, other: Self) { + std::mem::swap(self, other); + } +} +trait Hider { + fn hide_ref<'a, 'b, T: 'static>(&self, x: &'a mut &'b T) -> impl Swap + 'a { + x + } +} +fn dangle_ref(h: &H) -> &'static [i32; 3] { + let mut res = &[4, 5, 6]; + let x = [1, 2, 3]; + h.hide_ref(&mut res).swap(h.hide_ref(&mut &x)); + res //[edition2015,edition2024,polonius_alpha]~ ERROR cannot return value referencing local variable `x` +} +struct H; +impl Hider for H {} + +fn main() { + println!("{:?}", dangle_ref(&H)); +} diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.rs b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.rs similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.rs rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.rs diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.stderr b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness-2.stderr rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness-2.stderr diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.rs b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.rs similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.rs rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.rs diff --git a/tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.stderr b/tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.stderr similarity index 100% rename from tests/ui/impl-trait/alias-liveness/tait-hidden-erased-unsoundness.stderr rename to tests/ui/borrowck/alias-liveness/tait-hidden-erased-unsoundness.stderr 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 deleted file mode 100644 index 48efb0ca558c0..0000000000000 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr +++ /dev/null @@ -1,18 +0,0 @@ -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 - | -LL | fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { - | -- -------------- opaque type defined here - | | - | hidden type `Rc>` captures the lifetime `'b` as defined here -LL | x - | ^ - | -help: add a `use<...>` bound to explicitly capture `'b` - | -LL | fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a + use<'a, 'b, T> { - | ++++++++++++++++ - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0700`. 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 deleted file mode 100644 index e4f5475cdb0a6..0000000000000 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0597]: `x` does not live long enough - --> $DIR/rpit-hide-lifetime-for-swap.rs:33: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 deleted file mode 100644 index de5335be73dbe..0000000000000 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs +++ /dev/null @@ -1,42 +0,0 @@ -//@ revisions: edition2015 edition2024 polonius_alpha -//@ 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 - -// This test should never pass! - -use std::cell::RefCell; -use std::rc::Rc; - -trait Swap: Sized { - fn swap(self, other: Self); -} - -impl Swap for Rc> { - fn swap(self, other: Self) { - >::swap(&self, &other); - } -} - -fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { - x - //[edition2015]~^ ERROR hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds -} - -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 - hide(long.clone()).swap(hide(short)); - let res: &'static [i32; 3] = *long.borrow(); - res -} - -fn main() { - println!("{:?}", dangle()); -}