Skip to content

Add support for postfix yield expressions #138435

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ pub enum ExprKind {
Try(P<Expr>),

/// A `yield`, with an optional value to be yielded.
Yield(Option<P<Expr>>),
Yield(YieldKind),

/// A `do yeet` (aka `throw`/`fail`/`bail`/`raise`/whatever),
/// with an optional value to be returned.
Expand Down Expand Up @@ -1903,6 +1903,44 @@ pub enum MatchKind {
Postfix,
}

/// The kind of yield expression
#[derive(Clone, Encodable, Decodable, Debug)]
pub enum YieldKind {
/// yield expr { ... }
Prefix(Option<P<Expr>>),
/// expr.yield { ... }
Postfix(P<Expr>),
}

impl YieldKind {
/// Returns the expression inside the yield expression, if any.
///
/// For postfix yields, this is guaranteed to be `Some`.
pub const fn expr(&self) -> Option<&P<Expr>> {
match self {
YieldKind::Prefix(expr) => expr.as_ref(),
YieldKind::Postfix(expr) => Some(expr),
}
}

/// Returns a mutable reference to the expression being yielded, if any.
pub const fn expr_mut(&mut self) -> Option<&mut P<Expr>> {
match self {
YieldKind::Prefix(expr) => expr.as_mut(),
YieldKind::Postfix(expr) => Some(expr),
}
}

/// Returns true if both yields are prefix or both are postfix.
pub const fn same_kind(&self, other: &Self) -> bool {
match (self, other) {
(YieldKind::Prefix(_), YieldKind::Prefix(_)) => true,
(YieldKind::Postfix(_), YieldKind::Postfix(_)) => true,
_ => false,
}
}
}

/// A literal in a meta item.
#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)]
pub struct MetaItemLit {
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1813,8 +1813,11 @@ pub fn walk_expr<T: MutVisitor>(vis: &mut T, Expr { kind, id, span, attrs, token
ExprKind::Paren(expr) => {
vis.visit_expr(expr);
}
ExprKind::Yield(expr) => {
visit_opt(expr, |expr| vis.visit_expr(expr));
ExprKind::Yield(kind) => {
let expr = kind.expr_mut();
if let Some(expr) = expr {
vis.visit_expr(expr);
}
}
ExprKind::Try(expr) => vis.visit_expr(expr),
ExprKind::TryBlock(body) => vis.visit_block(body),
Expand Down
10 changes: 7 additions & 3 deletions compiler/rustc_ast/src/util/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,14 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
| Range(_, Some(e), _)
| Ret(Some(e))
| Unary(_, e)
| Yield(Some(e))
| Yeet(Some(e))
| Become(e) => {
expr = e;
}
Yield(kind) => match kind.expr() {
Some(e) => expr = e,
None => break None,
},
Closure(closure) => {
expr = &closure.body;
}
Expand Down Expand Up @@ -217,7 +220,6 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
Break(_, None)
| Range(_, None, _)
| Ret(None)
| Yield(None)
| Array(_)
| Call(_, _)
| MethodCall(_)
Expand All @@ -237,7 +239,9 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
| Yeet(None)
| UnsafeBinderCast(..)
| Err(_)
| Dummy => break None,
| Dummy => {
break None;
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1269,8 +1269,8 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) -> V
try_visit!(visitor.visit_ty(container));
walk_list!(visitor, visit_ident, fields.iter());
}
ExprKind::Yield(optional_expression) => {
visit_opt!(visitor, visit_expr, optional_expression);
ExprKind::Yield(kind) => {
visit_opt!(visitor, visit_expr, kind.expr());
}
ExprKind::Try(subexpression) => try_visit!(visitor.visit_expr(subexpression)),
ExprKind::TryBlock(body) => try_visit!(visitor.visit_block(body)),
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
rest,
)
}
ExprKind::Yield(opt_expr) => self.lower_expr_yield(e.span, opt_expr.as_deref()),
ExprKind::Yield(kind) => self.lower_expr_yield(e.span, kind.expr().map(|x| &**x)),
ExprKind::Err(guar) => hir::ExprKind::Err(*guar),

ExprKind::UnsafeBinderCast(kind, expr, ty) => hir::ExprKind::UnsafeBinderCast(
Expand Down
12 changes: 10 additions & 2 deletions compiler/rustc_ast_pretty/src/pprust/state/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_ast::util::literal::escape_byte_str_symbol;
use rustc_ast::util::parser::{self, ExprPrecedence, Fixity};
use rustc_ast::{
self as ast, BlockCheckMode, FormatAlignment, FormatArgPosition, FormatArgsPiece, FormatCount,
FormatDebugHex, FormatSign, FormatTrait, token,
FormatDebugHex, FormatSign, FormatTrait, YieldKind, token,
};

use crate::pp::Breaks::Inconsistent;
Expand Down Expand Up @@ -761,7 +761,7 @@ impl<'a> State<'a> {
self.print_expr(e, FixupContext::default());
self.pclose();
}
ast::ExprKind::Yield(e) => {
ast::ExprKind::Yield(YieldKind::Prefix(e)) => {
self.word("yield");

if let Some(expr) = e {
Expand All @@ -773,6 +773,14 @@ impl<'a> State<'a> {
);
}
}
ast::ExprKind::Yield(YieldKind::Postfix(e)) => {
self.print_expr_cond_paren(
e,
e.precedence() < ExprPrecedence::Unambiguous,
fixup.leftmost_subexpression_with_dot(),
);
self.word(".yield");
}
ast::ExprKind::Try(e) => {
self.print_expr_cond_paren(
e,
Expand Down
12 changes: 11 additions & 1 deletion compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use rustc_ast::{
self as ast, AnonConst, Arm, AttrStyle, AttrVec, BinOp, BinOpKind, BlockCheckMode, CaptureBy,
ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, FnRetTy, Label, MacCall,
MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind,
YieldKind,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::stack::ensure_sufficient_stack;
Expand Down Expand Up @@ -1310,6 +1311,15 @@ impl<'a> Parser<'a> {
return self.parse_match_block(lo, match_span, self_arg, MatchKind::Postfix);
}

// Parse a postfix `yield`.
if self.eat_keyword(exp!(Yield)) {
let yield_span = self.prev_token.span;
self.psess.gated_spans.gate(sym::yield_expr, yield_span);
return Ok(
self.mk_expr(lo.to(yield_span), ExprKind::Yield(YieldKind::Postfix(self_arg)))
);
}

let fn_span_lo = self.token.span;
let mut seg = self.parse_path_segment(PathStyle::Expr, None)?;
self.check_trailing_angle_brackets(&seg, &[exp!(OpenParen)]);
Expand Down Expand Up @@ -1884,7 +1894,7 @@ impl<'a> Parser<'a> {
/// Parse `"yield" expr?`.
fn parse_expr_yield(&mut self) -> PResult<'a, P<Expr>> {
let lo = self.prev_token.span;
let kind = ExprKind::Yield(self.parse_expr_opt()?);
let kind = ExprKind::Yield(YieldKind::Prefix(self.parse_expr_opt()?));
let span = lo.to(self.prev_token.span);
self.psess.gated_spans.gate(sym::yield_expr, span);
let expr = self.mk_expr(span, kind);
Expand Down
5 changes: 3 additions & 2 deletions src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
(Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lt, rt),
(Block(lb, ll), Block(rb, rl)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lb, rb),
(TryBlock(l), TryBlock(r)) => eq_block(l, r),
(Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()),
(Yield(l), Yield(r)) => eq_expr_opt(l.expr(), r.expr()) && l.same_kind(r),
(Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()),
(Break(ll, le), Break(rl, re)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_ref(), re.as_ref()),
(Continue(ll), Continue(rl)) => eq_label(ll.as_ref(), rl.as_ref()),
(Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => {
Expand Down Expand Up @@ -688,7 +689,7 @@ pub fn eq_generics(l: &Generics, r: &Generics) -> bool {

pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
use WherePredicateKind::*;
over(&l.attrs, &r.attrs, eq_attr)
over(&l.attrs, &r.attrs, eq_attr)
&& match (&l.kind, &r.kind) {
(BoundPredicate(l), BoundPredicate(r)) => {
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
Expand Down
10 changes: 9 additions & 1 deletion src/tools/rustfmt/src/chains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ enum ChainItemKind {
StructField(symbol::Ident),
TupleField(symbol::Ident, bool),
Await,
Yield,
Comment(String, CommentPosition),
}

Expand All @@ -203,6 +204,7 @@ impl ChainItemKind {
| ChainItemKind::StructField(..)
| ChainItemKind::TupleField(..)
| ChainItemKind::Await
| ChainItemKind::Yield
| ChainItemKind::Comment(..) => false,
}
}
Expand Down Expand Up @@ -257,6 +259,10 @@ impl ChainItemKind {
let span = mk_sp(nested.span.hi(), expr.span.hi());
(ChainItemKind::Await, span)
}
ast::ExprKind::Yield(ast::YieldKind::Postfix(ref nested)) => {
let span = mk_sp(nested.span.hi(), expr.span.hi());
(ChainItemKind::Yield, span)
}
_ => {
return (
ChainItemKind::Parent {
Expand Down Expand Up @@ -306,6 +312,7 @@ impl Rewrite for ChainItem {
rewrite_ident(context, ident)
),
ChainItemKind::Await => ".await".to_owned(),
ChainItemKind::Yield => ".yield".to_owned(),
ChainItemKind::Comment(ref comment, _) => {
rewrite_comment(comment, false, shape, context.config)?
}
Expand Down Expand Up @@ -508,7 +515,8 @@ impl Chain {
}),
ast::ExprKind::Field(ref subexpr, _)
| ast::ExprKind::Try(ref subexpr)
| ast::ExprKind::Await(ref subexpr, _) => Some(SubExpr {
| ast::ExprKind::Await(ref subexpr, _)
| ast::ExprKind::Yield(ast::YieldKind::Postfix(ref subexpr)) => Some(SubExpr {
expr: Self::convert_try(subexpr, context),
is_method_call_receiver: false,
}),
Expand Down
5 changes: 3 additions & 2 deletions src/tools/rustfmt/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ pub(crate) fn format_expr(
Ok(format!("break{id_str}"))
}
}
ast::ExprKind::Yield(ref opt_expr) => {
ast::ExprKind::Yield(ast::YieldKind::Prefix(ref opt_expr)) => {
if let Some(ref expr) = *opt_expr {
rewrite_unary_prefix(context, "yield ", &**expr, shape)
} else {
Expand All @@ -243,7 +243,8 @@ pub(crate) fn format_expr(
ast::ExprKind::Try(..)
| ast::ExprKind::Field(..)
| ast::ExprKind::MethodCall(..)
| ast::ExprKind::Await(_, _) => rewrite_chain(expr, context, shape),
| ast::ExprKind::Await(_, _)
| ast::ExprKind::Yield(ast::YieldKind::Postfix(_)) => rewrite_chain(expr, context, shape),
ast::ExprKind::MacCall(ref mac) => {
rewrite_macro(mac, None, context, shape, MacroPosition::Expression).or_else(|_| {
wrap_str(
Expand Down
8 changes: 5 additions & 3 deletions src/tools/rustfmt/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rustc_ast::ast::{
self, Attribute, MetaItem, MetaItemInner, MetaItemKind, NodeId, Path, Visibility,
VisibilityKind,
};
use rustc_ast::ptr;
use rustc_ast::{YieldKind, ptr};
use rustc_ast_pretty::pprust;
use rustc_span::{BytePos, LocalExpnId, Span, Symbol, SyntaxContext, sym, symbol};
use unicode_width::UnicodeWidthStr;
Expand Down Expand Up @@ -485,7 +485,9 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
| ast::ExprKind::Index(_, ref expr, _)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Yield(Some(ref expr)) => is_block_expr(context, expr, repr),
| ast::ExprKind::Yield(YieldKind::Prefix(Some(ref expr))) => {
is_block_expr(context, expr, repr)
}
ast::ExprKind::Closure(ref closure) => is_block_expr(context, &closure.body, repr),
// This can only be a string lit
ast::ExprKind::Lit(_) => {
Expand Down Expand Up @@ -515,7 +517,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
| ast::ExprKind::Tup(..)
| ast::ExprKind::Use(..)
| ast::ExprKind::Type(..)
| ast::ExprKind::Yield(None)
| ast::ExprKind::Yield(..)
| ast::ExprKind::Underscore => false,
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/tools/rustfmt/tests/target/postfix-yield.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This demonstrates a proposed alternate or additional option of having yield in postfix position.
//@ edition: 2024

#![feature(gen_blocks, coroutines, coroutine_trait, yield_expr)]

use std::ops::{Coroutine, CoroutineState};
use std::pin::pin;

fn main() {
let mut coro = pin!(
#[coroutine]
|_: i32| {
let x = 1.yield;
(x + 2).await;
}
);
}
15 changes: 15 additions & 0 deletions tests/pretty/postfix-yield.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This demonstrates a proposed alternate or additional option of having yield in postfix position.
//@ edition: 2024
//@ pp-exact

#![feature(gen_blocks, coroutines, coroutine_trait, yield_expr)]

use std::ops::{Coroutine, CoroutineState};
use std::pin::pin;

fn main() {
let mut gn = gen { yield 1; 2.yield; (1 + 2).yield; };

let mut coro =
pin!(#[coroutine] |_: i32| { let x = 1.yield; (x + 2).yield; });
}
34 changes: 34 additions & 0 deletions tests/ui/coroutine/postfix-yield.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This demonstrates a proposed alternate or additional option of having yield in postfix position.

//@ run-pass
//@ edition: 2024

#![feature(gen_blocks, coroutines, coroutine_trait, yield_expr)]

use std::ops::{Coroutine, CoroutineState};
use std::pin::pin;

fn main() {
// generators (i.e. yield doesn't return anything useful)
let mut gn = gen {
yield 1;
2.yield;
};

assert_eq!(gn.next(), Some(1));
assert_eq!(gn.next(), Some(2));
assert_eq!(gn.next(), None);

//coroutines (i.e. yield returns something useful)
let mut coro = pin!(
#[coroutine]
|_: i32| {
let x = 1.yield;
(x + 2).yield;
}
);

assert_eq!(coro.as_mut().resume(0), CoroutineState::Yielded(1));
assert_eq!(coro.as_mut().resume(2), CoroutineState::Yielded(4));
assert_eq!(coro.as_mut().resume(3), CoroutineState::Complete(()));
}
Loading