Auto merge of #118420 - compiler-errors:async-gen, r=eholk

Introduce support for `async gen` blocks

I'm delighted to demonstrate that `async gen` block are not very difficult to support. They're simply coroutines that yield `Poll<Option<T>>` and return `()`.

**This PR is WIP and in draft mode for now** -- I'm mostly putting it up to show folks that it's possible. This PR needs a lang-team experiment associated with it or possible an RFC, since I don't think it falls under the jurisdiction of the `gen` RFC that was recently authored by oli (https://github.com/rust-lang/rfcs/pull/3513, https://github.com/rust-lang/rust/issues/117078).

### Technical note on the pre-generator-transform yield type:

The reason that the underlying coroutines yield `Poll<Option<T>>` and not `Poll<T>` (which would make more sense, IMO, for the pre-transformed coroutine), is because the `TransformVisitor` that is used to turn coroutines into built-in state machine functions would have to destructure and reconstruct the latter into the former, which requires at least inserting a new basic block (for a `switchInt` terminator, to match on the `Poll` discriminant).

This does mean that the desugaring (at the `rustc_ast_lowering` level) of `async gen` blocks is a bit more involved. However, since we already need to intercept both `.await` and `yield` operators, I don't consider it much of a technical burden.

r? `@ghost`
This commit is contained in:
bors 2023-12-08 19:13:57 +00:00
commit f967532a47
61 changed files with 1120 additions and 357 deletions

View file

@ -1442,20 +1442,21 @@ impl<'a> Parser<'a> {
} else if this.token.uninterpolated_span().at_least_rust_2018() {
// `Span:.at_least_rust_2018()` is somewhat expensive; don't get it repeatedly.
if this.check_keyword(kw::Async) {
if this.is_gen_block(kw::Async) {
// Check for `async {` and `async move {`.
// FIXME(gen_blocks): Parse `gen async` and suggest swap
if this.is_gen_block(kw::Async, 0) {
// Check for `async {` and `async move {`,
// or `async gen {` and `async gen move {`.
this.parse_gen_block()
} else {
this.parse_expr_closure()
}
} else if this.eat_keyword(kw::Await) {
} else if this.token.uninterpolated_span().at_least_rust_2024()
&& (this.is_gen_block(kw::Gen, 0)
|| (this.check_keyword(kw::Async) && this.is_gen_block(kw::Gen, 1)))
{
this.parse_gen_block()
} else if this.eat_keyword_noexpect(kw::Await) {
this.recover_incorrect_await_syntax(lo, this.prev_token.span)
} else if this.token.uninterpolated_span().at_least_rust_2024() {
if this.is_gen_block(kw::Gen) {
this.parse_gen_block()
} else {
this.parse_expr_lit()
}
} else {
this.parse_expr_lit()
}
@ -2234,8 +2235,8 @@ impl<'a> Parser<'a> {
let movability =
if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable };
let asyncness = if self.token.uninterpolated_span().at_least_rust_2018() {
self.parse_asyncness(Case::Sensitive)
let coroutine_kind = if self.token.uninterpolated_span().at_least_rust_2018() {
self.parse_coroutine_kind(Case::Sensitive)
} else {
None
};
@ -2261,9 +2262,17 @@ impl<'a> Parser<'a> {
}
};
if let Some(CoroutineKind::Async { span, .. }) = asyncness {
// Feature-gate `async ||` closures.
self.sess.gated_spans.gate(sym::async_closure, span);
match coroutine_kind {
Some(CoroutineKind::Async { span, .. }) => {
// Feature-gate `async ||` closures.
self.sess.gated_spans.gate(sym::async_closure, span);
}
Some(CoroutineKind::Gen { span, .. }) | Some(CoroutineKind::AsyncGen { span, .. }) => {
// Feature-gate `gen ||` and `async gen ||` closures.
// FIXME(gen_blocks): This perhaps should be a different gate.
self.sess.gated_spans.gate(sym::gen_blocks, span);
}
None => {}
}
if self.token.kind == TokenKind::Semi
@ -2284,7 +2293,7 @@ impl<'a> Parser<'a> {
binder,
capture_clause,
constness,
coro_kind: asyncness,
coroutine_kind,
movability,
fn_decl,
body,
@ -3207,7 +3216,7 @@ impl<'a> Parser<'a> {
fn parse_gen_block(&mut self) -> PResult<'a, P<Expr>> {
let lo = self.token.span;
let kind = if self.eat_keyword(kw::Async) {
GenBlockKind::Async
if self.eat_keyword(kw::Gen) { GenBlockKind::AsyncGen } else { GenBlockKind::Async }
} else {
assert!(self.eat_keyword(kw::Gen));
self.sess.gated_spans.gate(sym::gen_blocks, lo.to(self.token.span));
@ -3219,22 +3228,26 @@ impl<'a> Parser<'a> {
Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs))
}
fn is_gen_block(&self, kw: Symbol) -> bool {
self.token.is_keyword(kw)
fn is_gen_block(&self, kw: Symbol, lookahead: usize) -> bool {
self.is_keyword_ahead(lookahead, &[kw])
&& ((
// `async move {`
self.is_keyword_ahead(1, &[kw::Move])
&& self.look_ahead(2, |t| {
self.is_keyword_ahead(lookahead + 1, &[kw::Move])
&& self.look_ahead(lookahead + 2, |t| {
*t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block()
})
) || (
// `async {`
self.look_ahead(1, |t| {
self.look_ahead(lookahead + 1, |t| {
*t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block()
})
))
}
pub(super) fn is_async_gen_block(&self) -> bool {
self.token.is_keyword(kw::Async) && self.is_gen_block(kw::Gen, 1)
}
fn is_certainly_not_a_block(&self) -> bool {
self.look_ahead(1, |t| t.is_ident())
&& (