From 4ecdf5ff00a9af18160bc7214cbcb8a6a1e01d10 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Sat, 21 Oct 2023 16:28:51 +0000 Subject: [PATCH] except equal parameters from the uniqueness check --- .../src/region_infer/opaque_types.rs | 98 ++++++++++++++++++- tests/ui/error-codes/E0657.rs | 2 + tests/ui/error-codes/E0657.stderr | 27 ++++- .../rpit/non-defining-use-lifetimes.rs | 38 +++++++ .../rpit/non-defining-use-lifetimes.stderr | 36 +++++++ .../equal-lifetime-params-ok.rs | 50 ++++++++++ 6 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 tests/ui/impl-trait/rpit/non-defining-use-lifetimes.rs create mode 100644 tests/ui/impl-trait/rpit/non-defining-use-lifetimes.stderr create mode 100644 tests/ui/type-alias-impl-trait/equal-lifetime-params-ok.rs diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs index 4958e2c1ade..0474dd18d45 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs @@ -432,6 +432,10 @@ fn check_opaque_type_well_formed<'tcx>( } } +/// Opaque type parameter validity check as documented in the [rustc-dev-guide chapter]. +/// +/// [rustc-dev-guide chapter]: +/// https://rustc-dev-guide.rust-lang.org/opaque-types-region-infer-restrictions.html fn check_opaque_type_parameter_valid<'tcx>( tcx: TyCtxt<'tcx>, opaque_type_key: OpaqueTypeKey<'tcx>, @@ -444,6 +448,7 @@ fn check_opaque_type_parameter_valid<'tcx>( }; let opaque_generics = tcx.generics_of(opaque_type_key.def_id); + let opaque_env = LazyOpaqueTyEnv::new(tcx, opaque_type_key.def_id); let mut seen_params: FxIndexMap<_, Vec<_>> = FxIndexMap::default(); for (i, arg) in opaque_type_key.iter_captured_args(tcx) { @@ -451,6 +456,7 @@ fn check_opaque_type_parameter_valid<'tcx>( GenericArgKind::Type(ty) => matches!(ty.kind(), ty::Param(_)), GenericArgKind::Lifetime(lt) if is_ty_alias => { matches!(*lt, ty::ReEarlyParam(_) | ty::ReLateParam(_)) + || (lt.is_static() && opaque_env.param_equal_static(i)) } // FIXME(#113916): we can't currently check for unique lifetime params, // see that issue for more. We will also have to ignore unused lifetime @@ -460,7 +466,13 @@ fn check_opaque_type_parameter_valid<'tcx>( }; if arg_is_param { - seen_params.entry(arg).or_default().push(i); + // Register if the same lifetime appears multiple times in the generic args. + // There is an exception when the opaque type *requires* the lifetimes to be equal. + // See [rustc-dev-guide chapter] ยง "An exception to uniqueness rule". + let seen_where = seen_params.entry(arg).or_default(); + if !seen_where.first().is_some_and(|&prev_i| opaque_env.params_equal(i, prev_i)) { + seen_where.push(i); + } } else { // Prevent `fn foo() -> Foo` from being defining. let opaque_param = opaque_generics.param_at(i, tcx); @@ -494,3 +506,87 @@ fn check_opaque_type_parameter_valid<'tcx>( Ok(()) } + +/// Computes if an opaque type requires a lifetime parameter to be equal to +/// another one or to the `'static` lifetime. +/// These requirements are derived from the explicit and implied bounds. +struct LazyOpaqueTyEnv<'tcx> { + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, + + /// Equal parameters will have the same name. Computed Lazily. + /// Example: + /// `type Opaque<'a: 'static, 'b: 'c, 'c: 'b> = impl Sized;` + /// Identity args: `['a, 'b, 'c]` + /// Canonical args: `['static, 'b, 'b]` + canonical_args: std::cell::OnceCell>, +} + +impl<'tcx> LazyOpaqueTyEnv<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self { + Self { tcx, def_id, canonical_args: std::cell::OnceCell::new() } + } + + pub fn param_equal_static(&self, param_index: usize) -> bool { + self.get_canonical_args()[param_index].expect_region().is_static() + } + + pub fn params_equal(&self, param1: usize, param2: usize) -> bool { + let canonical_args = self.get_canonical_args(); + canonical_args[param1] == canonical_args[param2] + } + + fn get_canonical_args(&self) -> ty::GenericArgsRef<'tcx> { + use rustc_hir as hir; + use rustc_infer::infer::outlives::env::OutlivesEnvironment; + use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _; + + if let Some(&canonical_args) = self.canonical_args.get() { + return canonical_args; + } + + let &Self { tcx, def_id, .. } = self; + let origin = tcx.opaque_type_origin(def_id); + let parent = match origin { + hir::OpaqueTyOrigin::FnReturn(parent) + | hir::OpaqueTyOrigin::AsyncFn(parent) + | hir::OpaqueTyOrigin::TyAlias { parent, .. } => parent, + }; + let param_env = tcx.param_env(parent); + let args = GenericArgs::identity_for_item(tcx, parent).extend_to( + tcx, + def_id.to_def_id(), + |param, _| { + tcx.map_opaque_lifetime_to_parent_lifetime(param.def_id.expect_local()).into() + }, + ); + + let infcx = tcx.infer_ctxt().build(); + let ocx = ObligationCtxt::new(&infcx); + + let wf_tys = ocx.assumed_wf_types(param_env, parent).unwrap_or_else(|_| { + tcx.dcx().span_delayed_bug(tcx.def_span(def_id), "error getting implied bounds"); + Default::default() + }); + let implied_bounds = infcx.implied_bounds_tys(param_env, parent, &wf_tys); + let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); + + let mut seen = vec![tcx.lifetimes.re_static]; + let canonical_args = tcx.fold_regions(args, |r1, _| { + if r1.is_error() { + r1 + } else if let Some(&r2) = seen.iter().find(|&&r2| { + let free_regions = outlives_env.free_region_map(); + free_regions.sub_free_regions(tcx, r1, r2) + && free_regions.sub_free_regions(tcx, r2, r1) + }) { + r2 + } else { + seen.push(r1); + r1 + } + }); + self.canonical_args.set(canonical_args).unwrap(); + canonical_args + } +} diff --git a/tests/ui/error-codes/E0657.rs b/tests/ui/error-codes/E0657.rs index 212c1d9e581..d70c0b334fa 100644 --- a/tests/ui/error-codes/E0657.rs +++ b/tests/ui/error-codes/E0657.rs @@ -11,6 +11,7 @@ fn free_fn_capture_hrtb_in_impl_trait() //~^ ERROR `impl Trait` cannot capture higher-ranked lifetime from `dyn` type { Box::new(()) + //~^ ERROR expected generic lifetime parameter, found `'static` } struct Foo; @@ -20,6 +21,7 @@ impl Foo { //~^ ERROR `impl Trait` cannot capture higher-ranked lifetime from `dyn` type { Box::new(()) + //~^ ERROR expected generic lifetime parameter, found `'static` } } diff --git a/tests/ui/error-codes/E0657.stderr b/tests/ui/error-codes/E0657.stderr index c539007cdcf..28b989aa429 100644 --- a/tests/ui/error-codes/E0657.stderr +++ b/tests/ui/error-codes/E0657.stderr @@ -10,18 +10,37 @@ note: lifetime declared here LL | -> Box Id>> | ^^ +error[E0792]: expected generic lifetime parameter, found `'static` + --> $DIR/E0657.rs:13:5 + | +LL | -> Box Id>> + | -- cannot use static lifetime; use a bound lifetime instead or remove the lifetime parameter from the opaque type +... +LL | Box::new(()) + | ^^^^^^^^^^^^ + error[E0657]: `impl Trait` cannot capture higher-ranked lifetime from `dyn` type - --> $DIR/E0657.rs:19:35 + --> $DIR/E0657.rs:20:35 | LL | -> Box Id>> | ^^ | note: lifetime declared here - --> $DIR/E0657.rs:19:20 + --> $DIR/E0657.rs:20:20 | LL | -> Box Id>> | ^^ -error: aborting due to 2 previous errors +error[E0792]: expected generic lifetime parameter, found `'static` + --> $DIR/E0657.rs:23:9 + | +LL | -> Box Id>> + | -- cannot use static lifetime; use a bound lifetime instead or remove the lifetime parameter from the opaque type +... +LL | Box::new(()) + | ^^^^^^^^^^^^ -For more information about this error, try `rustc --explain E0657`. +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0657, E0792. +For more information about an error, try `rustc --explain E0657`. diff --git a/tests/ui/impl-trait/rpit/non-defining-use-lifetimes.rs b/tests/ui/impl-trait/rpit/non-defining-use-lifetimes.rs new file mode 100644 index 00000000000..aa60ec27e00 --- /dev/null +++ b/tests/ui/impl-trait/rpit/non-defining-use-lifetimes.rs @@ -0,0 +1,38 @@ +// issue: #111935 +// FIXME(aliemjay): outdated due to "once modulo regions" restriction. +// FIXME(aliemjay): mod `infer` should fail. + +#![allow(unconditional_recursion)] + +// Lt indirection is necessary to make the lifetime of the function late-bound, +// in order to bypass some other bugs. +type Lt<'lt> = Option<*mut &'lt ()>; + +mod statik { + use super::*; + // invalid defining use: Opaque<'static> := () + fn foo<'a>(_: Lt<'a>) -> impl Sized + 'a { + let _: () = foo(Lt::<'static>::None); + //~^ ERROR opaque type used twice with different lifetimes + } +} + +mod infer { + use super::*; + // invalid defining use: Opaque<'_> := () + fn foo<'a>(_: Lt<'a>) -> impl Sized + 'a { + let _: () = foo(Lt::<'_>::None); + } +} + +mod equal { + use super::*; + // invalid defining use: Opaque<'a, 'a> := () + // because of the use of equal lifetimes in args + fn foo<'a, 'b>(_: Lt<'a>, _: Lt<'b>) -> impl Sized + 'a + 'b { + let _: () = foo(Lt::<'a>::None, Lt::<'a>::None); + //~^ ERROR opaque type used twice with different lifetimes + } +} + +fn main() {} diff --git a/tests/ui/impl-trait/rpit/non-defining-use-lifetimes.stderr b/tests/ui/impl-trait/rpit/non-defining-use-lifetimes.stderr new file mode 100644 index 00000000000..fe6bf71abdb --- /dev/null +++ b/tests/ui/impl-trait/rpit/non-defining-use-lifetimes.stderr @@ -0,0 +1,36 @@ +error: opaque type used twice with different lifetimes + --> $DIR/non-defining-use-lifetimes.rs:15:16 + | +LL | fn foo<'a>(_: Lt<'a>) -> impl Sized + 'a { + | ______________________________________________- +LL | | let _: () = foo(Lt::<'static>::None); + | | ^^ lifetime `'static` used here +LL | | +LL | | } + | |_____- lifetime `'a` previously used here + | +note: if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types + --> $DIR/non-defining-use-lifetimes.rs:15:16 + | +LL | let _: () = foo(Lt::<'static>::None); + | ^^ + +error: opaque type used twice with different lifetimes + --> $DIR/non-defining-use-lifetimes.rs:33:16 + | +LL | fn foo<'a, 'b>(_: Lt<'a>, _: Lt<'b>) -> impl Sized + 'a + 'b { + | __________________________________________________________________- +LL | | let _: () = foo(Lt::<'a>::None, Lt::<'a>::None); + | | ^^ lifetime `'a` used here +LL | | +LL | | } + | |_____- lifetime `'b` previously used here + | +note: if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types + --> $DIR/non-defining-use-lifetimes.rs:33:16 + | +LL | let _: () = foo(Lt::<'a>::None, Lt::<'a>::None); + | ^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/type-alias-impl-trait/equal-lifetime-params-ok.rs b/tests/ui/type-alias-impl-trait/equal-lifetime-params-ok.rs new file mode 100644 index 00000000000..6e3f72a1ebe --- /dev/null +++ b/tests/ui/type-alias-impl-trait/equal-lifetime-params-ok.rs @@ -0,0 +1,50 @@ +// FIXME: description +// issue: #113916 +//@ check-pass + +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +trait Trait<'a, 'b> {} +impl Trait<'_, '_> for T {} + +mod equal_params { + type Opaque<'a: 'b, 'b: 'a> = impl super::Trait<'a, 'b>; + fn test<'a: 'b, 'b: 'a>() -> Opaque<'a, 'b> { + let _ = None::<&'a &'b &'a ()>; + 0u8 + } +} + +mod equal_static { + type Opaque<'a: 'static> = impl Sized + 'a; + fn test<'a: 'static>() -> Opaque<'a> { + let _ = None::<&'static &'a ()>; + 0u8 + } +} + +mod implied_bounds { + trait Traitor { + type Assoc; + fn define(self) -> Self::Assoc; + } + + impl<'a> Traitor for &'static &'a () { + type Assoc = impl Sized + 'a; + fn define(self) -> Self::Assoc { + let _ = None::<&'static &'a ()>; + 0u8 + } + } + + impl<'a, 'b> Traitor for (&'a &'b (), &'b &'a ()) { + type Assoc = impl Sized + 'a + 'b; + fn define(self) -> Self::Assoc { + let _ = None::<(&'a &'b (), &'b &'a ())>; + 0u8 + } + } +} + +fn main() {}