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:
commit
f967532a47
61 changed files with 1120 additions and 357 deletions
|
@ -562,13 +562,6 @@ pub(crate) struct GenFn {
|
|||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(parse_async_gen_fn)]
|
||||
pub(crate) struct AsyncGenFn {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(parse_comma_after_base_struct)]
|
||||
#[note]
|
||||
|
|
|
@ -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())
|
||||
&& (
|
||||
|
|
|
@ -2359,8 +2359,10 @@ impl<'a> Parser<'a> {
|
|||
|| case == Case::Insensitive
|
||||
&& t.is_non_raw_ident_where(|i| quals.iter().any(|qual| qual.as_str() == i.name.as_str().to_lowercase()))
|
||||
)
|
||||
// Rule out unsafe extern block.
|
||||
&& !self.is_unsafe_foreign_mod())
|
||||
// Rule out `unsafe extern {`.
|
||||
&& !self.is_unsafe_foreign_mod()
|
||||
// Rule out `async gen {` and `async gen move {`
|
||||
&& !self.is_async_gen_block())
|
||||
})
|
||||
// `extern ABI fn`
|
||||
|| self.check_keyword_case(kw::Extern, case)
|
||||
|
@ -2392,10 +2394,7 @@ impl<'a> Parser<'a> {
|
|||
let constness = self.parse_constness(case);
|
||||
|
||||
let async_start_sp = self.token.span;
|
||||
let asyncness = self.parse_asyncness(case);
|
||||
|
||||
let _gen_start_sp = self.token.span;
|
||||
let genness = self.parse_genness(case);
|
||||
let coroutine_kind = self.parse_coroutine_kind(case);
|
||||
|
||||
let unsafe_start_sp = self.token.span;
|
||||
let unsafety = self.parse_unsafety(case);
|
||||
|
@ -2403,7 +2402,7 @@ impl<'a> Parser<'a> {
|
|||
let ext_start_sp = self.token.span;
|
||||
let ext = self.parse_extern(case);
|
||||
|
||||
if let Some(CoroutineKind::Async { span, .. }) = asyncness {
|
||||
if let Some(CoroutineKind::Async { span, .. }) = coroutine_kind {
|
||||
if span.is_rust_2015() {
|
||||
self.sess.emit_err(errors::AsyncFnIn2015 {
|
||||
span,
|
||||
|
@ -2412,16 +2411,11 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(CoroutineKind::Gen { span, .. }) = genness {
|
||||
self.sess.gated_spans.gate(sym::gen_blocks, span);
|
||||
}
|
||||
|
||||
if let (
|
||||
Some(CoroutineKind::Async { span: async_span, .. }),
|
||||
Some(CoroutineKind::Gen { span: gen_span, .. }),
|
||||
) = (asyncness, genness)
|
||||
{
|
||||
self.sess.emit_err(errors::AsyncGenFn { span: async_span.to(gen_span) });
|
||||
match coroutine_kind {
|
||||
Some(CoroutineKind::Gen { span, .. }) | Some(CoroutineKind::AsyncGen { span, .. }) => {
|
||||
self.sess.gated_spans.gate(sym::gen_blocks, span);
|
||||
}
|
||||
Some(CoroutineKind::Async { .. }) | None => {}
|
||||
}
|
||||
|
||||
if !self.eat_keyword_case(kw::Fn, case) {
|
||||
|
@ -2440,7 +2434,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
// We may be able to recover
|
||||
let mut recover_constness = constness;
|
||||
let mut recover_asyncness = asyncness;
|
||||
let mut recover_coroutine_kind = coroutine_kind;
|
||||
let mut recover_unsafety = unsafety;
|
||||
// This will allow the machine fix to directly place the keyword in the correct place or to indicate
|
||||
// that the keyword is already present and the second instance should be removed.
|
||||
|
@ -2453,15 +2447,24 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
} else if self.check_keyword(kw::Async) {
|
||||
match asyncness {
|
||||
match coroutine_kind {
|
||||
Some(CoroutineKind::Async { span, .. }) => {
|
||||
Some(WrongKw::Duplicated(span))
|
||||
}
|
||||
Some(CoroutineKind::AsyncGen { span, .. }) => {
|
||||
Some(WrongKw::Duplicated(span))
|
||||
}
|
||||
Some(CoroutineKind::Gen { .. }) => {
|
||||
panic!("not sure how to recover here")
|
||||
recover_coroutine_kind = Some(CoroutineKind::AsyncGen {
|
||||
span: self.token.span,
|
||||
closure_id: DUMMY_NODE_ID,
|
||||
return_impl_trait_id: DUMMY_NODE_ID,
|
||||
});
|
||||
// FIXME(gen_blocks): This span is wrong, didn't want to think about it.
|
||||
Some(WrongKw::Misplaced(unsafe_start_sp))
|
||||
}
|
||||
None => {
|
||||
recover_asyncness = Some(CoroutineKind::Async {
|
||||
recover_coroutine_kind = Some(CoroutineKind::Async {
|
||||
span: self.token.span,
|
||||
closure_id: DUMMY_NODE_ID,
|
||||
return_impl_trait_id: DUMMY_NODE_ID,
|
||||
|
@ -2559,7 +2562,7 @@ impl<'a> Parser<'a> {
|
|||
return Ok(FnHeader {
|
||||
constness: recover_constness,
|
||||
unsafety: recover_unsafety,
|
||||
coro_kind: recover_asyncness,
|
||||
coroutine_kind: recover_coroutine_kind,
|
||||
ext,
|
||||
});
|
||||
}
|
||||
|
@ -2569,13 +2572,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
let coro_kind = match asyncness {
|
||||
Some(CoroutineKind::Async { .. }) => asyncness,
|
||||
Some(CoroutineKind::Gen { .. }) => unreachable!("asycness cannot be Gen"),
|
||||
None => genness,
|
||||
};
|
||||
|
||||
Ok(FnHeader { constness, unsafety, coro_kind, ext })
|
||||
Ok(FnHeader { constness, unsafety, coroutine_kind, ext })
|
||||
}
|
||||
|
||||
/// Parses the parameter list and result type of a function declaration.
|
||||
|
|
|
@ -1125,23 +1125,30 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
/// Parses asyncness: `async` or nothing.
|
||||
fn parse_asyncness(&mut self, case: Case) -> Option<CoroutineKind> {
|
||||
fn parse_coroutine_kind(&mut self, case: Case) -> Option<CoroutineKind> {
|
||||
let span = self.token.uninterpolated_span();
|
||||
if self.eat_keyword_case(kw::Async, case) {
|
||||
let span = self.prev_token.uninterpolated_span();
|
||||
Some(CoroutineKind::Async {
|
||||
span,
|
||||
closure_id: DUMMY_NODE_ID,
|
||||
return_impl_trait_id: DUMMY_NODE_ID,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses genness: `gen` or nothing.
|
||||
fn parse_genness(&mut self, case: Case) -> Option<CoroutineKind> {
|
||||
if self.token.span.at_least_rust_2024() && self.eat_keyword_case(kw::Gen, case) {
|
||||
let span = self.prev_token.uninterpolated_span();
|
||||
// FIXME(gen_blocks): Do we want to unconditionally parse `gen` and then
|
||||
// error if edition <= 2024, like we do with async and edition <= 2018?
|
||||
if self.token.uninterpolated_span().at_least_rust_2024()
|
||||
&& self.eat_keyword_case(kw::Gen, case)
|
||||
{
|
||||
let gen_span = self.prev_token.uninterpolated_span();
|
||||
Some(CoroutineKind::AsyncGen {
|
||||
span: span.to(gen_span),
|
||||
closure_id: DUMMY_NODE_ID,
|
||||
return_impl_trait_id: DUMMY_NODE_ID,
|
||||
})
|
||||
} else {
|
||||
Some(CoroutineKind::Async {
|
||||
span,
|
||||
closure_id: DUMMY_NODE_ID,
|
||||
return_impl_trait_id: DUMMY_NODE_ID,
|
||||
})
|
||||
}
|
||||
} else if self.token.uninterpolated_span().at_least_rust_2024()
|
||||
&& self.eat_keyword_case(kw::Gen, case)
|
||||
{
|
||||
Some(CoroutineKind::Gen {
|
||||
span,
|
||||
closure_id: DUMMY_NODE_ID,
|
||||
|
|
|
@ -598,7 +598,7 @@ impl<'a> Parser<'a> {
|
|||
tokens: None,
|
||||
};
|
||||
let span_start = self.token.span;
|
||||
let ast::FnHeader { ext, unsafety, constness, coro_kind } =
|
||||
let ast::FnHeader { ext, unsafety, constness, coroutine_kind } =
|
||||
self.parse_fn_front_matter(&inherited_vis, Case::Sensitive)?;
|
||||
if self.may_recover() && self.token.kind == TokenKind::Lt {
|
||||
self.recover_fn_ptr_with_generics(lo, &mut params, param_insertion_point)?;
|
||||
|
@ -611,7 +611,7 @@ impl<'a> Parser<'a> {
|
|||
// cover it.
|
||||
self.sess.emit_err(FnPointerCannotBeConst { span: whole_span, qualifier: span });
|
||||
}
|
||||
if let Some(ast::CoroutineKind::Async { span, .. }) = coro_kind {
|
||||
if let Some(ast::CoroutineKind::Async { span, .. }) = coroutine_kind {
|
||||
self.sess.emit_err(FnPointerCannotBeAsync { span: whole_span, qualifier: span });
|
||||
}
|
||||
// FIXME(gen_blocks): emit a similar error for `gen fn()`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue