Skip to content

Commit

Permalink
typeck: Prohibit RPIT types that inherit lifetimes
Browse files Browse the repository at this point in the history
This commit prohibits return position `impl Trait` types that "inherit
lifetimes" from the parent scope. The intent is to forbid cases that are
challenging until they can be addressed properly.
  • Loading branch information
davidtwco committed Jul 21, 2019
1 parent 38798c6 commit 62f89e0
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 5 deletions.
81 changes: 81 additions & 0 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1315,7 +1315,88 @@ fn check_union(tcx: TyCtxt<'_>, id: hir::HirId, span: Span) {
check_packed(tcx, span, def_id);
}

/// Checks that an opaque type does not contain cycles and does not use `Self` or `T::Foo`
/// projections that would result in "inheriting lifetimes".
fn check_opaque<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, substs: SubstsRef<'tcx>, span: Span) {
check_opaque_for_inheriting_lifetimes(tcx, def_id, span);
check_opaque_for_cycles(tcx, def_id, substs, span);
}

/// Checks that an opaque type does not use `Self` or `T::Foo` projections that would result
/// in "inheriting lifetimes".
fn check_opaque_for_inheriting_lifetimes(
tcx: TyCtxt<'tcx>,
def_id: DefId,
span: Span,
) {
let item = tcx.hir().expect_item(
tcx.hir().as_local_hir_id(def_id).expect("existential type is not local"));
debug!("check_opaque_for_inheriting_lifetimes: def_id={:?} span={:?} item={:?}",
def_id, span, item);

#[derive(Debug)]
struct ProhibitOpaqueVisitor<'tcx> {
opaque_identity_ty: Ty<'tcx>,
generics: &'tcx ty::Generics,
};

impl<'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueVisitor<'tcx> {
fn visit_ty(&mut self, t: Ty<'tcx>) -> bool {
debug!("check_opaque_for_inheriting_lifetimes: (visit_ty) t={:?}", t);
if t == self.opaque_identity_ty { false } else { t.super_visit_with(self) }
}

fn visit_region(&mut self, r: ty::Region<'tcx>) -> bool {
debug!("check_opaque_for_inheriting_lifetimes: (visit_region) r={:?}", r);
if let RegionKind::ReEarlyBound(ty::EarlyBoundRegion { index, .. }) = r {
return *index < self.generics.parent_count as u32;
}

r.super_visit_with(self)
}
}

let prohibit_opaque = match item.node {
ItemKind::Existential(hir::ExistTy { origin: hir::ExistTyOrigin::AsyncFn, .. }) |
ItemKind::Existential(hir::ExistTy { origin: hir::ExistTyOrigin::ReturnImplTrait, .. }) => {
let mut visitor = ProhibitOpaqueVisitor {
opaque_identity_ty: tcx.mk_opaque(
def_id, InternalSubsts::identity_for_item(tcx, def_id)),
generics: tcx.generics_of(def_id),
};
debug!("check_opaque_for_inheriting_lifetimes: visitor={:?}", visitor);

tcx.predicates_of(def_id).predicates.iter().any(
|(predicate, _)| predicate.visit_with(&mut visitor))
},
_ => false,
};

debug!("check_opaque_for_inheriting_lifetimes: prohibit_opaque={:?}", prohibit_opaque);
if prohibit_opaque {
let is_async = match item.node {
ItemKind::Existential(hir::ExistTy { origin, .. }) => match origin {
hir::ExistTyOrigin::AsyncFn => true,
_ => false,
},
_ => unreachable!(),
};

tcx.sess.span_err(span, &format!(
"`{}` return type cannot contain a projection or `Self` that references lifetimes from \
a parent scope",
if is_async { "async fn" } else { "impl Trait" },
));
}
}

/// Checks that an opaque type does not contain cycles.
fn check_opaque_for_cycles<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
substs: SubstsRef<'tcx>,
span: Span,
) {
if let Err(partially_expanded_type) = tcx.try_expand_impl_trait_type(def_id, substs) {
let mut err = struct_span_err!(
tcx.sess, span, E0720,
Expand Down
28 changes: 28 additions & 0 deletions src/test/ui/async-await/issue-61949-self-return-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// ignore-tidy-linelength
// edition:2018
#![feature(async_await)]

// This test checks that `Self` is prohibited as a return type. See #61949 for context.

pub struct Foo<'a> {
pub bar: &'a i32,
}

impl<'a> Foo<'a> {
pub async fn new(_bar: &'a i32) -> Self {
//~^ ERROR `async fn` return type cannot contain a projection or `Self` that references lifetimes from a parent scope
Foo {
bar: &22
}
}
}

async fn foo() {
let x = {
let bar = 22;
Foo::new(&bar).await
};
drop(x);
}

fn main() { }
8 changes: 8 additions & 0 deletions src/test/ui/async-await/issue-61949-self-return-type.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
error: `async fn` return type cannot contain a projection or `Self` that references lifetimes from a parent scope
--> $DIR/issue-61949-self-return-type.rs:12:40
|
LL | pub async fn new(_bar: &'a i32) -> Self {
| ^^^^

error: aborting due to previous error

4 changes: 3 additions & 1 deletion src/test/ui/impl-trait/bound-normalization-fail.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// compile-fail
// ignore-tidy-linelength
// edition:2018

#![feature(async_await)]
Expand Down Expand Up @@ -45,7 +46,8 @@ mod lifetimes {

/// Missing bound constraining `Assoc`, `T::Assoc` can't be normalized further.
fn foo2_fail<'a, T: Trait<'a>>() -> impl FooLike<Output=T::Assoc> {
//~^ ERROR: type mismatch
//~^ ERROR: type mismatch
//~^^ ERROR `impl Trait` return type cannot contain a projection or `Self` that references lifetimes from a parent scope
Foo(())
}
}
Expand Down
14 changes: 10 additions & 4 deletions src/test/ui/impl-trait/bound-normalization-fail.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
warning: the feature `impl_trait_in_bindings` is incomplete and may cause the compiler to crash
--> $DIR/bound-normalization-fail.rs:6:12
--> $DIR/bound-normalization-fail.rs:7:12
|
LL | #![feature(impl_trait_in_bindings)]
| ^^^^^^^^^^^^^^^^^^^^^^

error[E0271]: type mismatch resolving `<Foo<()> as FooLike>::Output == <T as impl_trait::Trait>::Assoc`
--> $DIR/bound-normalization-fail.rs:30:32
--> $DIR/bound-normalization-fail.rs:31:32
|
LL | fn foo_fail<T: Trait>() -> impl FooLike<Output=T::Assoc> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found associated type
Expand All @@ -14,8 +14,14 @@ LL | fn foo_fail<T: Trait>() -> impl FooLike<Output=T::Assoc> {
found type `<T as impl_trait::Trait>::Assoc`
= note: the return type of a function must have a statically known size

error: `impl Trait` return type cannot contain a projection or `Self` that references lifetimes from a parent scope
--> $DIR/bound-normalization-fail.rs:48:41
|
LL | fn foo2_fail<'a, T: Trait<'a>>() -> impl FooLike<Output=T::Assoc> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0271]: type mismatch resolving `<Foo<()> as FooLike>::Output == <T as lifetimes::Trait<'static>>::Assoc`
--> $DIR/bound-normalization-fail.rs:47:41
--> $DIR/bound-normalization-fail.rs:48:41
|
LL | fn foo2_fail<'a, T: Trait<'a>>() -> impl FooLike<Output=T::Assoc> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found associated type
Expand All @@ -24,6 +30,6 @@ LL | fn foo2_fail<'a, T: Trait<'a>>() -> impl FooLike<Output=T::Assoc> {
found type `<T as lifetimes::Trait<'static>>::Assoc`
= note: the return type of a function must have a statically known size

error: aborting due to 2 previous errors
error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0271`.

0 comments on commit 62f89e0

Please sign in to comment.