Skip to content

Commit

Permalink
Rollup merge of #63499 - nikomatsakis:issuee-63388-async-fn-elision-s…
Browse files Browse the repository at this point in the history
…elf-mut-self, r=cramertj

handle elision in async fn correctly

We now always make fresh lifetimne parameters for all elided
lifetimes, whether they are in the inputs or outputs. But then
we generate `'_` in the case of elided lifetimes from the outputs.

Example:

```rust
async fn foo<'a>(x: &'a u32) -> &u32 { .. }
```

becomes

```rust
type Foo<'a, 'b> = impl Future<Output = &'b u32>;
fn foo<'a>(x: &'a u32) -> Foo<'a, '_>
```

Fixes #63388
  • Loading branch information
Centril authored Aug 13, 2019
2 parents c47cb61 + d7c7c52 commit 7a8b19b
Show file tree
Hide file tree
Showing 35 changed files with 445 additions and 1,772 deletions.
227 changes: 102 additions & 125 deletions src/librustc/hir/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,49 +337,6 @@ enum AnonymousLifetimeMode {

/// Pass responsibility to `resolve_lifetime` code for all cases.
PassThrough,

/// Used in the return types of `async fn` where there exists
/// exactly one argument-position elided lifetime.
///
/// In `async fn`, we lower the arguments types using the `CreateParameter`
/// mode, meaning that non-`dyn` elided lifetimes are assigned a fresh name.
/// If any corresponding elided lifetimes appear in the output, we need to
/// replace them with references to the fresh name assigned to the corresponding
/// elided lifetime in the arguments.
///
/// For **Modern cases**, replace the anonymous parameter with a
/// reference to a specific freshly-named lifetime that was
/// introduced in argument
///
/// For **Dyn Bound** cases, pass responsibility to
/// `resole_lifetime` code.
Replace(LtReplacement),
}

/// The type of elided lifetime replacement to perform on `async fn` return types.
#[derive(Copy, Clone)]
enum LtReplacement {
/// Fresh name introduced by the single non-dyn elided lifetime
/// in the arguments of the async fn.
Some(ParamName),

/// There is no single non-dyn elided lifetime because no lifetimes
/// appeared in the arguments.
NoLifetimes,

/// There is no single non-dyn elided lifetime because multiple
/// lifetimes appeared in the arguments.
MultipleLifetimes,
}

/// Calculates the `LtReplacement` to use for elided lifetimes in the return
/// type based on the fresh elided lifetimes introduced in argument position.
fn get_elided_lt_replacement(arg_position_lifetimes: &[(Span, ParamName)]) -> LtReplacement {
match arg_position_lifetimes {
[] => LtReplacement::NoLifetimes,
[(_span, param)] => LtReplacement::Some(*param),
_ => LtReplacement::MultipleLifetimes,
}
}

struct ImplTraitTypeIdVisitor<'a> { ids: &'a mut SmallVec<[NodeId; 1]> }
Expand Down Expand Up @@ -1953,8 +1910,7 @@ impl<'a> LoweringContext<'a> {
err.emit();
}
AnonymousLifetimeMode::PassThrough |
AnonymousLifetimeMode::ReportError |
AnonymousLifetimeMode::Replace(_) => {
AnonymousLifetimeMode::ReportError => {
self.sess.buffer_lint_with_diagnostic(
ELIDED_LIFETIMES_IN_PATHS,
CRATE_NODE_ID,
Expand Down Expand Up @@ -2141,7 +2097,6 @@ impl<'a> LoweringContext<'a> {

// Remember how many lifetimes were already around so that we can
// only look at the lifetime parameters introduced by the arguments.
let lifetime_count_before_args = self.lifetimes_to_define.len();
let inputs = self.with_anonymous_lifetime_mode(lt_mode, |this| {
decl.inputs
.iter()
Expand All @@ -2156,16 +2111,10 @@ impl<'a> LoweringContext<'a> {
});

let output = if let Some(ret_id) = make_ret_async {
// Calculate the `LtReplacement` to use for any return-position elided
// lifetimes based on the elided lifetime parameters introduced in the args.
let lt_replacement = get_elided_lt_replacement(
&self.lifetimes_to_define[lifetime_count_before_args..]
);
self.lower_async_fn_ret_ty(
&decl.output,
in_band_ty_params.expect("`make_ret_async` but no `fn_def_id`").0,
ret_id,
lt_replacement,
)
} else {
match decl.output {
Expand Down Expand Up @@ -2230,7 +2179,6 @@ impl<'a> LoweringContext<'a> {
output: &FunctionRetTy,
fn_def_id: DefId,
opaque_ty_node_id: NodeId,
elided_lt_replacement: LtReplacement,
) -> hir::FunctionRetTy {
let span = output.span();

Expand All @@ -2248,9 +2196,65 @@ impl<'a> LoweringContext<'a> {

self.allocate_hir_id_counter(opaque_ty_node_id);

// 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:
//
// - 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.
//
// So for example in this snippet:
//
// ```rust
// impl<'a> Foo<'a> {
// async fn bar<'b>(&self, x: &'b Vec<f64>, y: &str) -> &u32 {
// // ^ '0 ^ '1 ^ '2
// // elided lifetimes used below
// }
// }
// ```
//
// we would create an opaque type like:
//
// ```
// type Bar<'a, 'b, '0, '1, '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, '_>
// }
// ```
//
// 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.
//
// The variable `input_lifetimes_count` tracks the number of
// lifetime parameters to the opaque type *not counting* those
// lifetimes elided in the return type. This includes those
// that are explicitly declared (`in_scope_lifetimes`) and
// those elided lifetimes we found in the arguments (current
// content of `lifetimes_to_define`). Next, we will process
// the return type, which will cause `lifetimes_to_define` to
// grow.
let input_lifetimes_count = self.in_scope_lifetimes.len() + self.lifetimes_to_define.len();

let (opaque_ty_id, lifetime_params) = 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
// lifetime in the return type. So, given a return type
// like `async fn foo(..) -> &[&u32]`, we lower to `impl
// Future<Output = &'1 [ &'2 u32 ]>`.
//
// Then, we will create `fn foo(..) -> Foo<'_, '_>`, and
// hence the elision takes place at the fn site.
let future_bound = this.with_anonymous_lifetime_mode(
AnonymousLifetimeMode::Replace(elided_lt_replacement),
AnonymousLifetimeMode::CreateParameter,
|this| this.lower_async_fn_output_type_to_future_bound(
output,
fn_def_id,
Expand Down Expand Up @@ -2304,19 +2308,52 @@ impl<'a> LoweringContext<'a> {
(opaque_ty_id, lifetime_params)
});

let generic_args =
lifetime_params
.iter().cloned()
.map(|(span, hir_name)| {
GenericArg::Lifetime(hir::Lifetime {
hir_id: self.next_id(),
span,
name: hir::LifetimeName::Param(hir_name),
})
// 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:
//
// ```
// Bar<'a, 'b, '0, '1, '_>
// ```
//
// 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 `'_`.
let mut generic_args: Vec<_> =
lifetime_params[..input_lifetimes_count]
.iter()
.map(|&(span, hir_name)| {
// Input lifetime like `'a` or `'1`:
GenericArg::Lifetime(hir::Lifetime {
hir_id: self.next_id(),
span,
name: hir::LifetimeName::Param(hir_name),
})
.collect();
})
.collect();
generic_args.extend(
lifetime_params[input_lifetimes_count..]
.iter()
.map(|&(span, _)| {
// Output lifetime like `'_`.
GenericArg::Lifetime(hir::Lifetime {
hir_id: self.next_id(),
span,
name: hir::LifetimeName::Implicit,
})
})
);

let opaque_ty_ref = hir::TyKind::Def(hir::ItemId { id: opaque_ty_id }, generic_args);
// Create the `Foo<...>` refernece itself. Note that the `type
// Foo = impl Trait` is, internally, created as a child of the
// async fn, so the *type parameters* are inherited. It's
// only the lifetime parameters that we must supply.
let opaque_ty_ref = hir::TyKind::Def(hir::ItemId { id: opaque_ty_id }, generic_args.into());

hir::FunctionRetTy::Return(P(hir::Ty {
node: opaque_ty_ref,
Expand Down Expand Up @@ -2412,11 +2449,6 @@ impl<'a> LoweringContext<'a> {
}

AnonymousLifetimeMode::ReportError => self.new_error_lifetime(Some(l.id), span),

AnonymousLifetimeMode::Replace(replacement) => {
let hir_id = self.lower_node_id(l.id);
self.replace_elided_lifetime(hir_id, span, replacement)
}
},
ident => {
self.maybe_collect_in_band_lifetime(ident);
Expand All @@ -2439,39 +2471,6 @@ impl<'a> LoweringContext<'a> {
}
}

/// Replace a return-position elided lifetime with the elided lifetime
/// from the arguments.
fn replace_elided_lifetime(
&mut self,
hir_id: hir::HirId,
span: Span,
replacement: LtReplacement,
) -> hir::Lifetime {
let multiple_or_none = match replacement {
LtReplacement::Some(name) => {
return hir::Lifetime {
hir_id,
span,
name: hir::LifetimeName::Param(name),
};
}
LtReplacement::MultipleLifetimes => "multiple",
LtReplacement::NoLifetimes => "none",
};

let mut err = crate::middle::resolve_lifetime::report_missing_lifetime_specifiers(
self.sess,
span,
1,
);
err.note(&format!(
"return-position elided lifetimes require exactly one \
input-position elided lifetime, found {}.", multiple_or_none));
err.emit();

hir::Lifetime { hir_id, span, name: hir::LifetimeName::Error }
}

fn lower_generic_params(
&mut self,
params: &[GenericParam],
Expand Down Expand Up @@ -3174,10 +3173,6 @@ impl<'a> LoweringContext<'a> {
AnonymousLifetimeMode::ReportError => self.new_error_lifetime(None, span),

AnonymousLifetimeMode::PassThrough => self.new_implicit_lifetime(span),

AnonymousLifetimeMode::Replace(replacement) => {
self.new_replacement_lifetime(replacement, span)
}
}
}

Expand Down Expand Up @@ -3231,10 +3226,6 @@ impl<'a> LoweringContext<'a> {
// This is the normal case.
AnonymousLifetimeMode::PassThrough => self.new_implicit_lifetime(span),

AnonymousLifetimeMode::Replace(replacement) => {
self.new_replacement_lifetime(replacement, span)
}

AnonymousLifetimeMode::ReportError => self.new_error_lifetime(None, span),
}
}
Expand Down Expand Up @@ -3266,25 +3257,11 @@ impl<'a> LoweringContext<'a> {

// This is the normal case.
AnonymousLifetimeMode::PassThrough => {}

// We don't need to do any replacement here as this lifetime
// doesn't refer to an elided lifetime elsewhere in the function
// signature.
AnonymousLifetimeMode::Replace(_) => {}
}

self.new_implicit_lifetime(span)
}

fn new_replacement_lifetime(
&mut self,
replacement: LtReplacement,
span: Span,
) -> hir::Lifetime {
let hir_id = self.next_id();
self.replace_elided_lifetime(hir_id, span, replacement)
}

fn new_implicit_lifetime(&mut self, span: Span) -> hir::Lifetime {
hir::Lifetime {
hir_id: self.next_id(),
Expand Down
20 changes: 20 additions & 0 deletions src/test/ui/async-await/issues/issue-63388-1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// edition:2018

#![feature(async_await)]

struct Xyz {
a: u64,
}

trait Foo {}

impl Xyz {
async fn do_sth<'a>(
&'a self, foo: &dyn Foo
) -> &dyn Foo //~ ERROR lifetime mismatch
{
foo
}
}

fn main() {}
12 changes: 12 additions & 0 deletions src/test/ui/async-await/issues/issue-63388-1.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0623]: lifetime mismatch
--> $DIR/issue-63388-1.rs:14:10
|
LL | &'a self, foo: &dyn Foo
| -------- this parameter and the return type are declared with different lifetimes...
LL | ) -> &dyn Foo
| ^^^^^^^^
| |
| ...but data from `foo` is returned here

error: aborting due to previous error

20 changes: 20 additions & 0 deletions src/test/ui/async-await/issues/issue-63388-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// edition:2018

#![feature(async_await)]

struct Xyz {
a: u64,
}

trait Foo {}

impl Xyz {
async fn do_sth<'a>(
foo: &dyn Foo, bar: &'a dyn Foo //~ ERROR cannot infer
) -> &dyn Foo //~ ERROR missing lifetime specifier
{
foo
}
}

fn main() {}
Loading

0 comments on commit 7a8b19b

Please sign in to comment.