Skip to content

Commit

Permalink
Inherit lifetimes for async fn instead of duplicating them.
Browse files Browse the repository at this point in the history
  • Loading branch information
cjgillot committed Nov 30, 2021
1 parent 72d4713 commit 88af837
Show file tree
Hide file tree
Showing 23 changed files with 178 additions and 296 deletions.
69 changes: 19 additions & 50 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1649,11 +1649,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {

// When we create the opaque type for this async fn, it is going to have
// to capture all the lifetimes involved in the signature (including in the
// return type). This is done by introducing lifetime parameters for:
// return type). This is done by:
//
// - all the explicitly declared lifetimes from the impl and function itself;
// - all the elided lifetimes in the fn arguments;
// - all the elided lifetimes in the return type.
// - making the opaque type inherit all lifetime parameters from its parent;
// - make all the elided lifetimes in the fn arguments into parameters;
// - manually introducing parameters on the opaque type for elided
// lifetimes in the return type.
//
// So for example in this snippet:
//
Expand All @@ -1669,44 +1670,22 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// we would create an opaque type like:
//
// ```
// type Bar<'a, 'b, '0, '1, '2> = impl Future<Output = &'2 u32>;
// type Foo<'a>::bar<'b, '0, '1>::Bar<'2> = impl Future<Output = &'2 u32>;
// ```
//
// and we would then desugar `bar` to the equivalent of:
//
// ```rust
// impl<'a> Foo<'a> {
// fn bar<'b, '0, '1>(&'0 self, x: &'b Vec<f64>, y: &'1 str) -> Bar<'a, 'b, '0, '1, '_>
// fn bar<'b, '0, '1>(&'0 self, x: &'b Vec<f64>, y: &'1 str) -> Bar<'_>
// }
// ```
//
// Note that the final parameter to `Bar` is `'_`, not `'2` --
// this is because the elided lifetimes from the return type
// should be figured out using the ordinary elision rules, and
// this desugaring achieves that.

debug!("lower_async_fn_ret_ty: in_scope_lifetimes={:#?}", self.in_scope_lifetimes);
debug!("lower_async_fn_ret_ty: lifetimes_to_define={:#?}", self.lifetimes_to_define);

// Calculate all the lifetimes that should be captured
// by the opaque type. This should include all in-scope
// lifetime parameters, including those defined in-band.
//
// `lifetime_params` is a vector of tuple (span, parameter name, lifetime name).

// Input lifetime like `'a` or `'1`:
let mut lifetime_params: Vec<_> = self
.in_scope_lifetimes
.iter()
.cloned()
.map(|name| (name.ident().span, name, hir::LifetimeName::Param(name)))
.chain(
self.lifetimes_to_define
.iter()
.map(|&(span, name)| (span, name, hir::LifetimeName::Param(name))),
)
.collect();

let mut lifetime_params = Vec::new();
self.with_hir_id_owner(opaque_ty_node_id, |this| {
// We have to be careful to get elision right here. The
// idea is that we create a lifetime parameter for each
Expand All @@ -1725,16 +1704,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
debug!("lower_async_fn_ret_ty: future_bound={:#?}", future_bound);
debug!("lower_async_fn_ret_ty: lifetimes_to_define={:#?}", lifetimes_to_define);

lifetime_params.extend(
// Output lifetime like `'_`:
lifetimes_to_define
.into_iter()
.map(|(span, name)| (span, name, hir::LifetimeName::Implicit)),
);
// Output lifetime like `'_`:
lifetime_params = lifetimes_to_define;
debug!("lower_async_fn_ret_ty: lifetime_params={:#?}", lifetime_params);

let generic_params =
this.arena.alloc_from_iter(lifetime_params.iter().map(|&(span, hir_name, _)| {
this.arena.alloc_from_iter(lifetime_params.iter().map(|&(span, hir_name)| {
this.lifetime_to_generic_param(span, hir_name, opaque_ty_def_id)
}));

Expand All @@ -1752,28 +1727,22 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
this.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span)
});

// As documented above on the variable
// `input_lifetimes_count`, we need to create the lifetime
// arguments to our opaque type. Continuing with our example,
// we're creating the type arguments for the return type:
// We need to create the lifetime arguments to our opaque type.
// Continuing with our example, we're creating the type arguments
// for the return type:
//
// ```
// Bar<'a, 'b, '0, '1, '_>
// For<'a>::bar<'b, '0, '1>::Bar<'_>
// ```
//
// For the "input" lifetime parameters, we wish to create
// references to the parameters themselves, including the
// "implicit" ones created from parameter types (`'a`, `'b`,
// '`0`, `'1`).
//
// For the "output" lifetime parameters, we just want to
// generate `'_`.
// For the "input" lifetime parameters are inherited automatically.
// For the "output" lifetime parameters, we just want to generate `'_`.
let generic_args =
self.arena.alloc_from_iter(lifetime_params.into_iter().map(|(span, _, name)| {
self.arena.alloc_from_iter(lifetime_params.into_iter().map(|(span, _)| {
GenericArg::Lifetime(hir::Lifetime {
hir_id: self.next_id(),
span: self.lower_span(span),
name,
name: hir::LifetimeName::Implicit,
})
}));

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_infer/src/infer/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
debug!(?concrete_ty);

let first_own_region = match opaque_defn.origin {
hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) => {
hir::OpaqueTyOrigin::FnReturn(..) => {
// We lower
//
// fn foo<'l0..'ln>() -> impl Trait<'l0..'lm>
Expand All @@ -291,7 +291,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
// These opaque type inherit all lifetime parameters from their
// parent, so we have to check them all.
hir::OpaqueTyOrigin::TyAlias => 0,
hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::TyAlias => 0,
};

// For a case like `impl Foo<'a, 'b>`, we would generate a constraint
Expand Down
34 changes: 26 additions & 8 deletions compiler/rustc_resolve/src/late/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,16 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
match item.kind {
hir::ItemKind::Fn(ref sig, ref generics, _) => {
self.missing_named_lifetime_spots.push(generics.into());
self.visit_early_late(None, item.hir_id(), &sig.decl, generics, |this| {
intravisit::walk_item(this, item);
});
self.visit_early_late(
None,
item.hir_id(),
&sig.decl,
generics,
sig.header.asyncness,
|this| {
intravisit::walk_item(this, item);
},
);
self.missing_named_lifetime_spots.pop();
}

Expand Down Expand Up @@ -851,11 +858,16 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {

fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'tcx>) {
match item.kind {
hir::ForeignItemKind::Fn(ref decl, _, ref generics) => {
self.visit_early_late(None, item.hir_id(), decl, generics, |this| {
hir::ForeignItemKind::Fn(ref decl, _, ref generics) => self.visit_early_late(
None,
item.hir_id(),
decl,
generics,
hir::IsAsync::NotAsync,
|this| {
intravisit::walk_foreign_item(this, item);
})
}
},
),
hir::ForeignItemKind::Static(..) => {
intravisit::walk_foreign_item(self, item);
}
Expand Down Expand Up @@ -1141,6 +1153,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
trait_item.hir_id(),
&sig.decl,
&trait_item.generics,
sig.header.asyncness,
|this| intravisit::walk_trait_item(this, trait_item),
);
self.missing_named_lifetime_spots.pop();
Expand Down Expand Up @@ -1210,6 +1223,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
impl_item.hir_id(),
&sig.decl,
&impl_item.generics,
sig.header.asyncness,
|this| intravisit::walk_impl_item(this, impl_item),
);
self.missing_named_lifetime_spots.pop();
Expand Down Expand Up @@ -2187,11 +2201,15 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
hir_id: hir::HirId,
decl: &'tcx hir::FnDecl<'tcx>,
generics: &'tcx hir::Generics<'tcx>,
asyncness: hir::IsAsync,
walk: F,
) where
F: for<'b, 'c> FnOnce(&'b mut LifetimeContext<'c, 'tcx>),
{
insert_late_bound_lifetimes(self.map, decl, generics);
// Async fns need all their lifetime parameters to be early bound.
if asyncness != hir::IsAsync::Async {
insert_late_bound_lifetimes(self.map, decl, generics);
}

// Find the start of nested early scopes, e.g., in methods.
let mut next_early_index = 0;
Expand Down
15 changes: 5 additions & 10 deletions compiler/rustc_typeck/src/astconv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2337,16 +2337,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
let def_id = item_id.def_id.to_def_id();

match opaque_ty.kind {
hir::ItemKind::OpaqueTy(hir::OpaqueTy { origin, .. }) => self
.impl_trait_ty_to_ty(
def_id,
lifetimes,
matches!(
origin,
hir::OpaqueTyOrigin::FnReturn(..)
| hir::OpaqueTyOrigin::AsyncFn(..)
),
),
hir::ItemKind::OpaqueTy(hir::OpaqueTy { origin, .. }) => {
let replace_parent_lifetimes =
matches!(origin, hir::OpaqueTyOrigin::FnReturn(..));
self.impl_trait_ty_to_ty(def_id, lifetimes, replace_parent_lifetimes)
}
ref i => bug!("`impl Trait` pointed to non-opaque type?? {:#?}", i),
}
}
Expand Down
15 changes: 3 additions & 12 deletions compiler/rustc_typeck/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,10 +540,8 @@ pub(super) fn check_opaque_for_inheriting_lifetimes(
}
}

if let ItemKind::OpaqueTy(hir::OpaqueTy {
origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..),
..
}) = item.kind
if let ItemKind::OpaqueTy(hir::OpaqueTy { origin: hir::OpaqueTyOrigin::FnReturn(..), .. }) =
item.kind
{
let mut visitor = ProhibitOpaqueVisitor {
opaque_identity_ty: tcx.mk_opaque(
Expand All @@ -565,20 +563,13 @@ pub(super) fn check_opaque_for_inheriting_lifetimes(

if let Some(ty) = prohibit_opaque.break_value() {
visitor.visit_item(&item);
let is_async = match item.kind {
ItemKind::OpaqueTy(hir::OpaqueTy { origin, .. }) => {
matches!(origin, hir::OpaqueTyOrigin::AsyncFn(..))
}
_ => unreachable!(),
};

let mut err = struct_span_err!(
tcx.sess,
span,
E0760,
"`{}` return type cannot contain a projection or `Self` that references lifetimes from \
"`impl Trait` return type cannot contain a projection or `Self` that references lifetimes from \
a parent scope",
if is_async { "async fn" } else { "impl Trait" },
);

for (span, name) in visitor.selftys {
Expand Down
5 changes: 2 additions & 3 deletions compiler/rustc_typeck/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2063,8 +2063,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericP
generics
}
ItemKind::OpaqueTy(OpaqueTy {
origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..),
..
origin: hir::OpaqueTyOrigin::FnReturn(..), ..
}) => {
// return-position impl trait
//
Expand All @@ -2084,7 +2083,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericP
}
ItemKind::OpaqueTy(OpaqueTy {
ref generics,
origin: hir::OpaqueTyOrigin::TyAlias,
origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::TyAlias,
..
}) => {
// type-alias impl trait
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/async-await/issue-61949-self-return-type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ pub struct Foo<'a> {

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
}
Expand All @@ -19,6 +18,7 @@ async fn foo() {
let x = {
let bar = 22;
Foo::new(&bar).await
//~^ ERROR `bar` does not live long enough [E0597]
};
drop(x);
}
Expand Down
16 changes: 11 additions & 5 deletions src/test/ui/async-await/issue-61949-self-return-type.stderr
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
error[E0760]: `async fn` return type cannot contain a projection or `Self` that references lifetimes from a parent scope
--> $DIR/issue-61949-self-return-type.rs:10:40
error[E0597]: `bar` does not live long enough
--> $DIR/issue-61949-self-return-type.rs:20:18
|
LL | pub async fn new(_bar: &'a i32) -> Self {
| ^^^^ help: consider spelling out the type instead: `Foo<'a>`
LL | let x = {
| - borrow later stored here
LL | let bar = 22;
LL | Foo::new(&bar).await
| ^^^^ borrowed value does not live long enough
LL |
LL | };
| - `bar` dropped here while still borrowed

error: aborting due to previous error

For more information about this error, try `rustc --explain E0760`.
For more information about this error, try `rustc --explain E0597`.
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
error[E0506]: cannot assign to `*x` because it is borrowed
--> $DIR/issue-74072-lifetime-name-annotations.rs:9:5
|
LL | pub async fn async_fn(x: &mut i32) -> &i32 {
| - let's call the lifetime of this reference `'1`
LL | let y = &*x;
| --- borrow of `*x` occurs here
LL | *x += 1;
| ^^^^^^^ assignment to borrowed `*x` occurs here
LL | y
| - returning this value requires that `*x` is borrowed for `'1`

error[E0506]: cannot assign to `*x` because it is borrowed
--> $DIR/issue-74072-lifetime-name-annotations.rs:16:9
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
error[E0506]: cannot assign to `*x` because it is borrowed
--> $DIR/issue-75785-confusing-named-region.rs:9:5
|
LL | pub async fn async_fn(x: &mut i32) -> (&i32, &i32) {
| - let's call the lifetime of this reference `'1`
LL | let y = &*x;
| --- borrow of `*x` occurs here
LL | *x += 1;
| ^^^^^^^ assignment to borrowed `*x` occurs here
LL | (&32, y)
| -------- returning this value requires that `*x` is borrowed for `'1`

error: aborting due to previous error

Expand Down
14 changes: 4 additions & 10 deletions src/test/ui/async-await/issue-76547.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@ error[E0623]: lifetime mismatch
--> $DIR/issue-76547.rs:20:13
|
LL | async fn fut(bufs: &mut [&mut [u8]]) {
| --------- -
| | |
| | this `async fn` implicitly returns an `impl Future<Output = ()>`
| this parameter and the returned future are declared with different lifetimes...
| ---------------- these two types are declared with different lifetimes...
LL | ListFut(bufs).await
| ^^^^ ...but data from `bufs` is held across an await point here
| ^^^^ ...but data from `bufs` flows into `bufs` here

error[E0623]: lifetime mismatch
--> $DIR/issue-76547.rs:34:14
|
LL | async fn fut2(bufs: &mut [&mut [u8]]) -> i32 {
| --------- ---
| | |
| | this `async fn` implicitly returns an `impl Future<Output = i32>`
| this parameter and the returned future are declared with different lifetimes...
| ---------------- these two types are declared with different lifetimes...
LL | ListFut2(bufs).await
| ^^^^ ...but data from `bufs` is held across an await point here
| ^^^^ ...but data from `bufs` flows into `bufs` here

error: aborting due to 2 previous errors

Expand Down
Loading

0 comments on commit 88af837

Please sign in to comment.