Auto merge of #118847 - eholk:for-await, r=compiler-errors

Add support for `for await` loops

This adds support for `for await` loops. This includes parsing, desugaring in AST->HIR lowering, and adding some support functions to the library.

Given a loop like:
```rust
for await i in iter {
    ...
}
```
this is desugared to something like:
```rust
let mut iter = iter.into_async_iter();
while let Some(i) = loop {
    match core::pin::Pin::new(&mut iter).poll_next(cx) {
        Poll::Ready(i) => break i,
        Poll::Pending => yield,
    }
} {
    ...
}
```

This PR also adds a basic `IntoAsyncIterator` trait. This is partly for symmetry with the way `Iterator` and `IntoIterator` work. The other reason is that for async iterators it's helpful to have a place apart from the data structure being iterated over to store state. `IntoAsyncIterator` gives us a good place to do this.

I've gated this feature behind `async_for_loop` and opened #118898 as the feature tracking issue.

r? `@compiler-errors`
This commit is contained in:
bors 2023-12-22 14:17:10 +00:00
commit 208dd2032b
34 changed files with 367 additions and 79 deletions

View File

@ -1249,7 +1249,7 @@ impl Expr {
ExprKind::Let(..) => ExprPrecedence::Let,
ExprKind::If(..) => ExprPrecedence::If,
ExprKind::While(..) => ExprPrecedence::While,
ExprKind::ForLoop(..) => ExprPrecedence::ForLoop,
ExprKind::ForLoop { .. } => ExprPrecedence::ForLoop,
ExprKind::Loop(..) => ExprPrecedence::Loop,
ExprKind::Match(..) => ExprPrecedence::Match,
ExprKind::Closure(..) => ExprPrecedence::Closure,
@ -1411,10 +1411,10 @@ pub enum ExprKind {
While(P<Expr>, P<Block>, Option<Label>),
/// A `for` loop, with an optional label.
///
/// `'label: for pat in expr { block }`
/// `'label: for await? pat in iter { block }`
///
/// This is desugared to a combination of `loop` and `match` expressions.
ForLoop(P<Pat>, P<Expr>, P<Block>, Option<Label>),
ForLoop { pat: P<Pat>, iter: P<Expr>, body: P<Block>, label: Option<Label>, kind: ForLoopKind },
/// Conditionless loop (can be exited with `break`, `continue`, or `return`).
///
/// `'label: loop { block }`
@ -1517,6 +1517,13 @@ pub enum ExprKind {
Err,
}
/// Used to differentiate between `for` loops and `for await` loops.
#[derive(Clone, Copy, Encodable, Decodable, Debug, PartialEq, Eq)]
pub enum ForLoopKind {
For,
ForAwait,
}
/// Used to differentiate between `async {}` blocks and `gen {}` blocks.
#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq)]
pub enum GenBlockKind {

View File

@ -1389,7 +1389,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
vis.visit_block(body);
visit_opt(label, |label| vis.visit_label(label));
}
ExprKind::ForLoop(pat, iter, body, label) => {
ExprKind::ForLoop { pat, iter, body, label, kind: _ } => {
vis.visit_pat(pat);
vis.visit_expr(iter);
vis.visit_block(body);

View File

@ -19,7 +19,7 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
| ast::ExprKind::Block(..)
| ast::ExprKind::While(..)
| ast::ExprKind::Loop(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::TryBlock(..)
| ast::ExprKind::ConstBlock(..)
)
@ -48,8 +48,16 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
Closure(closure) => {
expr = &closure.body;
}
Gen(..) | Block(..) | ForLoop(..) | If(..) | Loop(..) | Match(..) | Struct(..)
| TryBlock(..) | While(..) | ConstBlock(_) => break Some(expr),
Gen(..)
| Block(..)
| ForLoop { .. }
| If(..)
| Loop(..)
| Match(..)
| Struct(..)
| TryBlock(..)
| While(..)
| ConstBlock(_) => break Some(expr),
// FIXME: These can end in `}`, but changing these would break stable code.
InlineAsm(_) | OffsetOf(_, _) | MacCall(_) | IncludedBytes(_) | FormatArgs(_) => {

View File

@ -844,11 +844,11 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
visitor.visit_expr(subexpression);
visitor.visit_block(block);
}
ExprKind::ForLoop(pattern, subexpression, block, opt_label) => {
walk_list!(visitor, visit_label, opt_label);
visitor.visit_pat(pattern);
visitor.visit_expr(subexpression);
visitor.visit_block(block);
ExprKind::ForLoop { pat, iter, body, label, kind: _ } => {
walk_list!(visitor, visit_label, label);
visitor.visit_pat(pat);
visitor.visit_expr(iter);
visitor.visit_block(body);
}
ExprKind::Loop(block, opt_label, _) => {
walk_list!(visitor, visit_label, opt_label);

View File

@ -56,12 +56,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
return ex;
}
// Desugar `ExprForLoop`
// from: `[opt_ident]: for <pat> in <head> <body>`
// from: `[opt_ident]: for await? <pat> in <iter> <body>`
//
// This also needs special handling because the HirId of the returned `hir::Expr` will not
// correspond to the `e.id`, so `lower_expr_for` handles attribute lowering itself.
ExprKind::ForLoop(pat, head, body, opt_label) => {
return self.lower_expr_for(e, pat, head, body, *opt_label);
ExprKind::ForLoop { pat, iter, body, label, kind } => {
return self.lower_expr_for(e, pat, iter, body, *label, *kind);
}
_ => (),
}
@ -337,7 +337,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
),
ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr),
ExprKind::Paren(_) | ExprKind::ForLoop(..) => {
ExprKind::Paren(_) | ExprKind::ForLoop { .. } => {
unreachable!("already handled")
}
@ -874,6 +874,17 @@ impl<'hir> LoweringContext<'_, 'hir> {
/// }
/// ```
fn lower_expr_await(&mut self, await_kw_span: Span, expr: &Expr) -> hir::ExprKind<'hir> {
let expr = self.arena.alloc(self.lower_expr_mut(expr));
self.make_lowered_await(await_kw_span, expr, FutureKind::Future)
}
/// Takes an expr that has already been lowered and generates a desugared await loop around it
fn make_lowered_await(
&mut self,
await_kw_span: Span,
expr: &'hir hir::Expr<'hir>,
await_kind: FutureKind,
) -> hir::ExprKind<'hir> {
let full_span = expr.span.to(await_kw_span);
let is_async_gen = match self.coroutine_kind {
@ -887,13 +898,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
}
};
let span = self.mark_span_with_reason(DesugaringKind::Await, await_kw_span, None);
let features = match await_kind {
FutureKind::Future => None,
FutureKind::AsyncIterator => Some(self.allow_for_await.clone()),
};
let span = self.mark_span_with_reason(DesugaringKind::Await, await_kw_span, features);
let gen_future_span = self.mark_span_with_reason(
DesugaringKind::Await,
full_span,
Some(self.allow_gen_future.clone()),
);
let expr = self.lower_expr_mut(expr);
let expr_hir_id = expr.hir_id;
// Note that the name of this binding must not be changed to something else because
@ -934,11 +948,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::LangItem::GetContext,
arena_vec![self; task_context],
);
let call = self.expr_call_lang_item_fn(
span,
hir::LangItem::FuturePoll,
arena_vec![self; new_unchecked, get_context],
);
let call = match await_kind {
FutureKind::Future => self.expr_call_lang_item_fn(
span,
hir::LangItem::FuturePoll,
arena_vec![self; new_unchecked, get_context],
),
FutureKind::AsyncIterator => self.expr_call_lang_item_fn(
span,
hir::LangItem::AsyncIteratorPollNext,
arena_vec![self; new_unchecked, get_context],
),
};
self.arena.alloc(self.expr_unsafe(call))
};
@ -1020,11 +1041,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
let awaitee_arm = self.arm(awaitee_pat, loop_expr);
// `match ::std::future::IntoFuture::into_future(<expr>) { ... }`
let into_future_expr = self.expr_call_lang_item_fn(
span,
hir::LangItem::IntoFutureIntoFuture,
arena_vec![self; expr],
);
let into_future_expr = match await_kind {
FutureKind::Future => self.expr_call_lang_item_fn(
span,
hir::LangItem::IntoFutureIntoFuture,
arena_vec![self; *expr],
),
// Not needed for `for await` because we expect to have already called
// `IntoAsyncIterator::into_async_iter` on it.
FutureKind::AsyncIterator => expr,
};
// match <into_future_expr> {
// mut __awaitee => loop { .. }
@ -1685,6 +1711,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
head: &Expr,
body: &Block,
opt_label: Option<Label>,
loop_kind: ForLoopKind,
) -> hir::Expr<'hir> {
let head = self.lower_expr_mut(head);
let pat = self.lower_pat(pat);
@ -1713,17 +1740,41 @@ impl<'hir> LoweringContext<'_, 'hir> {
let (iter_pat, iter_pat_nid) =
self.pat_ident_binding_mode(head_span, iter, hir::BindingAnnotation::MUT);
// `match Iterator::next(&mut iter) { ... }`
let match_expr = {
let iter = self.expr_ident(head_span, iter, iter_pat_nid);
let ref_mut_iter = self.expr_mut_addr_of(head_span, iter);
let next_expr = self.expr_call_lang_item_fn(
head_span,
hir::LangItem::IteratorNext,
arena_vec![self; ref_mut_iter],
);
let next_expr = match loop_kind {
ForLoopKind::For => {
// `Iterator::next(&mut iter)`
let ref_mut_iter = self.expr_mut_addr_of(head_span, iter);
self.expr_call_lang_item_fn(
head_span,
hir::LangItem::IteratorNext,
arena_vec![self; ref_mut_iter],
)
}
ForLoopKind::ForAwait => {
// we'll generate `unsafe { Pin::new_unchecked(&mut iter) })` and then pass this
// to make_lowered_await with `FutureKind::AsyncIterator` which will generator
// calls to `poll_next`. In user code, this would probably be a call to
// `Pin::as_mut` but here it's easy enough to do `new_unchecked`.
// `&mut iter`
let iter = self.expr_mut_addr_of(head_span, iter);
// `Pin::new_unchecked(...)`
let iter = self.arena.alloc(self.expr_call_lang_item_fn_mut(
head_span,
hir::LangItem::PinNewUnchecked,
arena_vec![self; iter],
));
// `unsafe { ... }`
let iter = self.arena.alloc(self.expr_unsafe(iter));
let kind = self.make_lowered_await(head_span, iter, FutureKind::AsyncIterator);
self.arena.alloc(hir::Expr { hir_id: self.next_id(), kind, span: head_span })
}
};
let arms = arena_vec![self; none_arm, some_arm];
// `match $next_expr { ... }`
self.expr_match(head_span, next_expr, arms, hir::MatchSource::ForLoopDesugar)
};
let match_stmt = self.stmt_expr(for_span, match_expr);
@ -1743,13 +1794,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
// `mut iter => { ... }`
let iter_arm = self.arm(iter_pat, loop_expr);
// `match ::std::iter::IntoIterator::into_iter(<head>) { ... }`
let into_iter_expr = {
self.expr_call_lang_item_fn(
head_span,
hir::LangItem::IntoIterIntoIter,
arena_vec![self; head],
)
let into_iter_expr = match loop_kind {
ForLoopKind::For => {
// `::std::iter::IntoIterator::into_iter(<head>)`
self.expr_call_lang_item_fn(
head_span,
hir::LangItem::IntoIterIntoIter,
arena_vec![self; head],
)
}
ForLoopKind::ForAwait => self.arena.alloc(head),
};
let match_expr = self.arena.alloc(self.expr_match(
@ -2152,3 +2206,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
}
}
}
/// Used by [`LoweringContext::make_lowered_await`] to customize the desugaring based on what kind
/// of future we are awaiting.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum FutureKind {
/// We are awaiting a normal future
Future,
/// We are awaiting something that's known to be an AsyncIterator (i.e. we are in the header of
/// a `for await` loop)
AsyncIterator,
}

View File

@ -130,6 +130,7 @@ struct LoweringContext<'a, 'hir> {
allow_try_trait: Lrc<[Symbol]>,
allow_gen_future: Lrc<[Symbol]>,
allow_async_iterator: Lrc<[Symbol]>,
allow_for_await: Lrc<[Symbol]>,
/// Mapping from generics `def_id`s to TAIT generics `def_id`s.
/// For each captured lifetime (e.g., 'a), we create a new lifetime parameter that is a generic
@ -174,6 +175,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
} else {
[sym::gen_future].into()
},
allow_for_await: [sym::async_iterator].into(),
// FIXME(gen_blocks): how does `closure_track_caller`/`async_fn_track_caller`
// interact with `gen`/`async gen` blocks
allow_async_iterator: [sym::gen_future, sym::async_iterator].into(),

View File

@ -527,6 +527,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
"async closures are unstable",
"to use an async block, remove the `||`: `async {`"
);
gate_all!(async_for_loop, "`for await` loops are experimental");
gate_all!(
closure_lifetime_binder,
"`for<...>` binders for closures are experimental",

View File

@ -1,5 +1,6 @@
use crate::pp::Breaks::Inconsistent;
use crate::pprust::state::{AnnNode, PrintState, State, INDENT_UNIT};
use ast::ForLoopKind;
use itertools::{Itertools, Position};
use rustc_ast::ptr::P;
use rustc_ast::token;
@ -418,20 +419,23 @@ impl<'a> State<'a> {
self.space();
self.print_block_with_attrs(blk, attrs);
}
ast::ExprKind::ForLoop(pat, iter, blk, opt_label) => {
if let Some(label) = opt_label {
ast::ExprKind::ForLoop { pat, iter, body, label, kind } => {
if let Some(label) = label {
self.print_ident(label.ident);
self.word_space(":");
}
self.cbox(0);
self.ibox(0);
self.word_nbsp("for");
if kind == &ForLoopKind::ForAwait {
self.word_nbsp("await");
}
self.print_pat(pat);
self.space();
self.word_space("in");
self.print_expr_as_cond(iter);
self.space();
self.print_block_with_attrs(blk, attrs);
self.print_block_with_attrs(body, attrs);
}
ast::ExprKind::Loop(blk, opt_label, _) => {
if let Some(label) = opt_label {

View File

@ -303,7 +303,7 @@ impl<'cx, 'a> Context<'cx, 'a> {
| ExprKind::Continue(_)
| ExprKind::Err
| ExprKind::Field(_, _)
| ExprKind::ForLoop(_, _, _, _)
| ExprKind::ForLoop { .. }
| ExprKind::FormatArgs(_)
| ExprKind::IncludedBytes(..)
| ExprKind::InlineAsm(_)

View File

@ -357,6 +357,8 @@ declare_features! (
(unstable, async_closure, "1.37.0", Some(62290)),
/// Allows `#[track_caller]` on async functions.
(unstable, async_fn_track_caller, "1.73.0", Some(110011)),
/// Allows `for await` loops.
(unstable, async_for_loop, "CURRENT_RUSTC_VERSION", Some(118898)),
/// Allows builtin # foo() syntax
(unstable, builtin_syntax, "1.71.0", Some(110680)),
/// Treat `extern "C"` function as nounwind.

View File

@ -307,6 +307,8 @@ language_item_table! {
Context, sym::Context, context, Target::Struct, GenericRequirement::None;
FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
AsyncIteratorPollNext, sym::async_iterator_poll_next, async_iterator_poll_next, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::Exact(0);
Option, sym::Option, option_type, Target::Enum, GenericRequirement::None;
OptionSome, sym::Some, option_some_variant, Target::Variant, GenericRequirement::None;
OptionNone, sym::None, option_none_variant, Target::Variant, GenericRequirement::None;

View File

@ -852,8 +852,8 @@ trait UnusedDelimLint {
(cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right), true)
}
ForLoop(_, ref cond, ref block, ..) => {
(cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo()), true)
ForLoop { ref iter, ref body, .. } => {
(iter, UnusedDelimsCtx::ForIterExpr, true, None, Some(body.span.lo()), true)
}
Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
@ -1085,7 +1085,7 @@ impl EarlyLintPass for UnusedParens {
}
match e.kind {
ExprKind::Let(ref pat, _, _, _) | ExprKind::ForLoop(ref pat, ..) => {
ExprKind::Let(ref pat, _, _, _) | ExprKind::ForLoop { ref pat, .. } => {
self.check_unused_parens_pat(cx, pat, false, false, (true, true));
}
// We ignore parens in cases like `if (((let Some(0) = Some(1))))` because we already

View File

@ -10,7 +10,7 @@ use super::{
use crate::errors;
use crate::maybe_recover_from_interpolated_ty_qpath;
use ast::mut_visit::{noop_visit_expr, MutVisitor};
use ast::{CoroutineKind, GenBlockKind, Pat, Path, PathSegment};
use ast::{CoroutineKind, ForLoopKind, GenBlockKind, Pat, Path, PathSegment};
use core::mem;
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
@ -1801,7 +1801,7 @@ impl<'a> Parser<'a> {
&& matches!(
expr.kind,
ExprKind::While(_, _, None)
| ExprKind::ForLoop(_, _, _, None)
| ExprKind::ForLoop { label: None, .. }
| ExprKind::Loop(_, None, _)
| ExprKind::Block(_, None)
)
@ -2685,8 +2685,17 @@ impl<'a> Parser<'a> {
Ok((pat, expr))
}
/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
/// Parses `for await? <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
let is_await =
self.token.uninterpolated_span().at_least_rust_2018() && self.eat_keyword(kw::Await);
if is_await {
self.sess.gated_spans.gate(sym::async_for_loop, self.prev_token.span);
}
let kind = if is_await { ForLoopKind::ForAwait } else { ForLoopKind::For };
let (pat, expr) = self.parse_for_head()?;
// Recover from missing expression in `for` loop
if matches!(expr.kind, ExprKind::Block(..))
@ -2699,13 +2708,13 @@ impl<'a> Parser<'a> {
let block = self.mk_block(thin_vec![], BlockCheckMode::Default, self.prev_token.span);
return Ok(self.mk_expr(
lo.to(self.prev_token.span),
ExprKind::ForLoop(pat, err_expr, block, opt_label),
ExprKind::ForLoop { pat, iter: err_expr, body: block, label: opt_label, kind },
));
}
let (attrs, loop_block) = self.parse_inner_attrs_and_block()?;
let kind = ExprKind::ForLoop(pat, expr, loop_block, opt_label);
let kind = ExprKind::ForLoop { pat, iter: expr, body: loop_block, label: opt_label, kind };
self.recover_loop_else("for", lo)?;
@ -3800,7 +3809,7 @@ impl MutVisitor for CondChecker<'_> {
| ExprKind::Lit(_)
| ExprKind::If(_, _, _)
| ExprKind::While(_, _, _)
| ExprKind::ForLoop(_, _, _, _)
| ExprKind::ForLoop { .. }
| ExprKind::Loop(_, _, _)
| ExprKind::Match(_, _)
| ExprKind::Closure(_)

View File

@ -4258,11 +4258,11 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
});
}
ExprKind::ForLoop(ref pat, ref iter_expr, ref block, label) => {
self.visit_expr(iter_expr);
ExprKind::ForLoop { ref pat, ref iter, ref body, label, kind: _ } => {
self.visit_expr(iter);
self.with_rib(ValueNS, RibKind::Normal, |this| {
this.resolve_pattern_top(pat, PatternSource::For);
this.resolve_labeled_block(label, expr.id, block);
this.resolve_labeled_block(label, expr.id, body);
});
}

View File

@ -1409,7 +1409,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
| ExprKind::Unary(..)
| ExprKind::If(..)
| ExprKind::While(..)
| ExprKind::ForLoop(..)
| ExprKind::ForLoop { .. }
| ExprKind::Match(..),
..
}),

View File

@ -426,7 +426,9 @@ symbols! {
async_closure,
async_fn_in_trait,
async_fn_track_caller,
async_for_loop,
async_iterator,
async_iterator_poll_next,
atomic,
atomic_mod,
atomics,
@ -893,6 +895,7 @@ symbols! {
instruction_set,
integer_: "integer", // underscore to avoid clashing with the function `sym::integer` below
integral,
into_async_iter_into_iter,
into_future,
into_iter,
intra_doc_pointers,

View File

@ -47,6 +47,7 @@ pub trait AsyncIterator {
/// Rust's usual rules apply: calls must never cause undefined behavior
/// (memory corruption, incorrect use of `unsafe` functions, or the like),
/// regardless of the async iterator's state.
#[cfg_attr(not(bootstrap), lang = "async_iterator_poll_next")]
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>;
/// Returns the bounds on the remaining length of the async iterator.

View File

@ -220,7 +220,11 @@ where
F: FnMut(&ast::Block, Option<&ast::Label>),
{
if let ast::ExprKind::While(_, loop_block, label)
| ast::ExprKind::ForLoop(_, _, loop_block, label)
| ast::ExprKind::ForLoop {
body: loop_block,
label,
..
}
| ast::ExprKind::Loop(loop_block, label, ..) = &expr.kind
{
func(loop_block, label.as_ref());

View File

@ -111,7 +111,7 @@ impl<'ast> Visitor<'ast> for BreakVisitor {
ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els),
ExprKind::If(_, _, None)
// ignore loops for simplicity
| ExprKind::While(..) | ExprKind::ForLoop(..) | ExprKind::Loop(..) => false,
| ExprKind::While(..) | ExprKind::ForLoop { .. } | ExprKind::Loop(..) => false,
_ => {
walk_expr(self, expr);
return;

View File

@ -554,7 +554,7 @@ fn ident_difference_expr_with_base_location(
| (Closure(_), Closure(_))
| (Match(_, _), Match(_, _))
| (Loop(_, _, _), Loop(_, _, _))
| (ForLoop(_, _, _, _), ForLoop(_, _, _, _))
| (ForLoop { .. }, ForLoop { .. })
| (While(_, _, _), While(_, _, _))
| (If(_, _, _), If(_, _, _))
| (Let(_, _, _, _), Let(_, _, _, _))

View File

@ -169,9 +169,22 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
(Let(lp, le, _, _), Let(rp, re, _, _)) => eq_pat(lp, rp) && eq_expr(le, re),
(If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
(While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
(ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
},
(
ForLoop {
pat: lp,
iter: li,
body: lt,
label: ll,
kind: lk,
},
ForLoop {
pat: rp,
iter: ri,
body: rt,
label: rl,
kind: rk,
},
) => eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt) && lk == rk,
(Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll, rl) && eq_block(lt, rt),
(Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
(TryBlock(l), TryBlock(r)) => eq_block(l, r),

View File

@ -197,7 +197,7 @@ impl<'a> Sugg<'a> {
| ast::ExprKind::Continue(..)
| ast::ExprKind::Yield(..)
| ast::ExprKind::Field(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::Index(..)
| ast::ExprKind::InlineAsm(..)
| ast::ExprKind::OffsetOf(..)

View File

@ -448,7 +448,7 @@ fn is_block_closure_forced(context: &RewriteContext<'_>, expr: &ast::Expr) -> bo
fn is_block_closure_forced_inner(expr: &ast::Expr, version: Version) -> bool {
match expr.kind {
ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop(..) => true,
ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop { .. } => true,
ast::ExprKind::Loop(..) if version == Version::Two => true,
ast::ExprKind::AddrOf(_, _, ref expr)
| ast::ExprKind::Try(ref expr)
@ -473,7 +473,7 @@ fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
| ast::ExprKind::Block(..)
| ast::ExprKind::While(..)
| ast::ExprKind::Loop(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::TryBlock(..) => false,
_ => true,
}

View File

@ -3,7 +3,7 @@ use std::cmp::min;
use itertools::Itertools;
use rustc_ast::token::{Delimiter, Lit, LitKind};
use rustc_ast::{ast, ptr, token};
use rustc_ast::{ast, ptr, token, ForLoopKind};
use rustc_span::{BytePos, Span};
use crate::chains::rewrite_chain;
@ -134,7 +134,7 @@ pub(crate) fn format_expr(
}
ast::ExprKind::Let(ref pat, ref expr, _span, _) => rewrite_let(context, shape, pat, expr),
ast::ExprKind::If(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::Loop(..)
| ast::ExprKind::While(..) => to_control_flow(expr, expr_type)
.and_then(|control_flow| control_flow.rewrite(context, shape)),
@ -682,9 +682,15 @@ fn to_control_flow(expr: &ast::Expr, expr_type: ExprType) -> Option<ControlFlow<
expr.span,
))
}
ast::ExprKind::ForLoop(ref pat, ref cond, ref block, label) => {
Some(ControlFlow::new_for(pat, cond, block, label, expr.span))
}
ast::ExprKind::ForLoop {
ref pat,
ref iter,
ref body,
label,
kind,
} => Some(ControlFlow::new_for(
pat, iter, body, label, expr.span, kind,
)),
ast::ExprKind::Loop(ref block, label, _) => {
Some(ControlFlow::new_loop(block, label, expr.span))
}
@ -771,6 +777,7 @@ impl<'a> ControlFlow<'a> {
block: &'a ast::Block,
label: Option<ast::Label>,
span: Span,
kind: ForLoopKind,
) -> ControlFlow<'a> {
ControlFlow {
cond: Some(cond),
@ -778,7 +785,10 @@ impl<'a> ControlFlow<'a> {
else_block: None,
label,
pat: Some(pat),
keyword: "for",
keyword: match kind {
ForLoopKind::For => "for",
ForLoopKind::ForAwait => "for await",
},
matcher: "",
connector: " in",
allow_single_line: false,
@ -1364,7 +1374,7 @@ pub(crate) fn can_be_overflowed_expr(
|| context.config.overflow_delimited_expr()
}
ast::ExprKind::If(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::Loop(..)
| ast::ExprKind::While(..) => {
context.config.combine_control_expr() && context.use_block_indent() && args_len == 1

View File

@ -591,7 +591,7 @@ fn can_flatten_block_around_this(body: &ast::Expr) -> bool {
ast::ExprKind::If(..) => false,
// We do not allow collapsing a block around expression with condition
// to avoid it being cluttered with match arm.
ast::ExprKind::ForLoop(..) | ast::ExprKind::While(..) => false,
ast::ExprKind::ForLoop { .. } | ast::ExprKind::While(..) => false,
ast::ExprKind::Loop(..)
| ast::ExprKind::Match(..)
| ast::ExprKind::Block(..)

View File

@ -409,7 +409,7 @@ impl<'a> Context<'a> {
// When overflowing the expressions which consists of a control flow
// expression, avoid condition to use multi line.
ast::ExprKind::If(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::Loop(..)
| ast::ExprKind::While(..)
| ast::ExprKind::Match(..) => {

View File

@ -295,7 +295,7 @@ pub(crate) fn semicolon_for_stmt(
) -> bool {
match stmt.kind {
ast::StmtKind::Semi(ref expr) => match expr.kind {
ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop(..) => {
ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop { .. } => {
false
}
ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => {
@ -476,7 +476,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
| ast::ExprKind::ConstBlock(..)
| ast::ExprKind::Gen(..)
| ast::ExprKind::Loop(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::TryBlock(..)
| ast::ExprKind::Match(..) => repr.contains('\n'),
ast::ExprKind::Paren(ref expr)

View File

@ -0,0 +1,23 @@
// edition:2021
// gate-test-async_for_loop
#![feature(async_iter_from_iter, async_iterator)]
fn f() {
let _ = async {
for await _i in core::async_iter::from_iter(0..3) {
//~^ ERROR `for await` loops are experimental
}
};
}
#[cfg(FALSE)]
fn g() {
let _ = async {
for await _i in core::async_iter::from_iter(0..3) {
//~^ ERROR `for await` loops are experimental
}
};
}
fn main() {}

View File

@ -0,0 +1,21 @@
error[E0658]: `for await` loops are experimental
--> $DIR/feature-async-for-loop.rs:8:13
|
LL | for await _i in core::async_iter::from_iter(0..3) {
| ^^^^^
|
= note: see issue #118898 <https://github.com/rust-lang/rust/issues/118898> for more information
= help: add `#![feature(async_for_loop)]` to the crate attributes to enable
error[E0658]: `for await` loops are experimental
--> $DIR/feature-async-for-loop.rs:17:13
|
LL | for await _i in core::async_iter::from_iter(0..3) {
| ^^^^^
|
= note: see issue #118898 <https://github.com/rust-lang/rust/issues/118898> for more information
= help: add `#![feature(async_for_loop)]` to the crate attributes to enable
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,10 @@
// check-pass
#![feature(async_for_loop)]
// Make sure we don't break `for await` loops in the 2015 edition, where `await` was allowed as an
// identifier.
fn main() {
for await in 0..3 {}
}

View File

@ -0,0 +1,20 @@
// edition: 2021
#![feature(async_iterator, async_iter_from_iter, const_waker, async_for_loop, noop_waker)]
use std::future::Future;
// a test to make sure `for await` consumes the iterator
async fn real_main() {
let iter = core::async_iter::from_iter(0..3);
let mut count = 0;
for await i in iter {
}
// make sure iter has been moved and we can't iterate over it again.
for await i in iter {
//~^ ERROR: use of moved value: `iter`
}
}
fn main() {
}

View File

@ -0,0 +1,27 @@
error[E0382]: use of moved value: `iter`
--> $DIR/for-await-consumes-iter.rs:14:20
|
LL | let iter = core::async_iter::from_iter(0..3);
| ---- move occurs because `iter` has type `FromIter<std::ops::Range<i32>>`, which does not implement the `Copy` trait
LL | let mut count = 0;
LL | for await i in iter {
| -------------------
| | |
| | value moved here
| inside of this loop
...
LL | for await i in iter {
| ^^^^ value used here after move
|
help: consider cloning the value if the performance cost is acceptable
|
LL | for await i in iter.clone() {
| ++++++++
help: borrow this binding in the pattern to avoid moving the value
|
LL | for await i in ref iter {
| +++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.

View File

@ -0,0 +1,32 @@
// run-pass
// edition: 2024
// compile-flags: -Zunstable-options
#![feature(async_iterator, async_iter_from_iter, const_waker, async_for_loop, noop_waker,
gen_blocks)]
use std::future::Future;
async gen fn async_iter() -> i32 {
let iter = core::async_iter::from_iter(0..3);
for await i in iter {
yield i + 1;
}
}
// make sure a simple for await loop works
async fn real_main() {
let mut count = 1;
for await i in async_iter() {
assert_eq!(i, count);
count += 1;
}
assert_eq!(count, 4);
}
fn main() {
let future = real_main();
let waker = std::task::Waker::noop();
let mut cx = &mut core::task::Context::from_waker(&waker);
let mut future = core::pin::pin!(future);
while let core::task::Poll::Pending = future.as_mut().poll(&mut cx) {}
}

View File

@ -0,0 +1,24 @@
// run-pass
// edition: 2021
#![feature(async_iterator, async_iter_from_iter, const_waker, async_for_loop, noop_waker)]
use std::future::Future;
// make sure a simple for await loop works
async fn real_main() {
let iter = core::async_iter::from_iter(0..3);
let mut count = 0;
for await i in iter {
assert_eq!(i, count);
count += 1;
}
assert_eq!(count, 3);
}
fn main() {
let future = real_main();
let waker = std::task::Waker::noop();
let mut cx = &mut core::task::Context::from_waker(&waker);
let mut future = core::pin::pin!(future);
while let core::task::Poll::Pending = future.as_mut().poll(&mut cx) {}
}