Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b3c5caf

Browse files
authoredMar 20, 2025
Rollup merge of rust-lang#138435 - eholk:prefix-yield, r=oli-obk
Add support for postfix yield expressions We've been having a discussion about whether we want postfix yield, or want to stick with prefix yield, or have both. I figured it's easy enough to support both for now and let us play around with them while the feature is still experimental. This PR treats `yield x` and `x.yield` as semantically equivalent. There was a suggestion to make `yield x` have a `()` type (so it only works in coroutines with `Resume = ()`. I think that'd be worth trying, either in a later PR, or before this one merges, depending on people's opinions. rust-lang#43122
2 parents d8e44b7 + 2bd7f73 commit b3c5caf

File tree

14 files changed

+161
-20
lines changed

14 files changed

+161
-20
lines changed
 

‎compiler/rustc_ast/src/ast.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -1657,7 +1657,7 @@ pub enum ExprKind {
16571657
Try(P<Expr>),
16581658

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

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

1906+
/// The kind of yield expression
1907+
#[derive(Clone, Encodable, Decodable, Debug)]
1908+
pub enum YieldKind {
1909+
/// yield expr { ... }
1910+
Prefix(Option<P<Expr>>),
1911+
/// expr.yield { ... }
1912+
Postfix(P<Expr>),
1913+
}
1914+
1915+
impl YieldKind {
1916+
/// Returns the expression inside the yield expression, if any.
1917+
///
1918+
/// For postfix yields, this is guaranteed to be `Some`.
1919+
pub const fn expr(&self) -> Option<&P<Expr>> {
1920+
match self {
1921+
YieldKind::Prefix(expr) => expr.as_ref(),
1922+
YieldKind::Postfix(expr) => Some(expr),
1923+
}
1924+
}
1925+
1926+
/// Returns a mutable reference to the expression being yielded, if any.
1927+
pub const fn expr_mut(&mut self) -> Option<&mut P<Expr>> {
1928+
match self {
1929+
YieldKind::Prefix(expr) => expr.as_mut(),
1930+
YieldKind::Postfix(expr) => Some(expr),
1931+
}
1932+
}
1933+
1934+
/// Returns true if both yields are prefix or both are postfix.
1935+
pub const fn same_kind(&self, other: &Self) -> bool {
1936+
match (self, other) {
1937+
(YieldKind::Prefix(_), YieldKind::Prefix(_)) => true,
1938+
(YieldKind::Postfix(_), YieldKind::Postfix(_)) => true,
1939+
_ => false,
1940+
}
1941+
}
1942+
}
1943+
19061944
/// A literal in a meta item.
19071945
#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)]
19081946
pub struct MetaItemLit {

‎compiler/rustc_ast/src/mut_visit.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1813,8 +1813,11 @@ pub fn walk_expr<T: MutVisitor>(vis: &mut T, Expr { kind, id, span, attrs, token
18131813
ExprKind::Paren(expr) => {
18141814
vis.visit_expr(expr);
18151815
}
1816-
ExprKind::Yield(expr) => {
1817-
visit_opt(expr, |expr| vis.visit_expr(expr));
1816+
ExprKind::Yield(kind) => {
1817+
let expr = kind.expr_mut();
1818+
if let Some(expr) = expr {
1819+
vis.visit_expr(expr);
1820+
}
18181821
}
18191822
ExprKind::Try(expr) => vis.visit_expr(expr),
18201823
ExprKind::TryBlock(body) => vis.visit_block(body),

‎compiler/rustc_ast/src/util/classify.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,14 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
182182
| Range(_, Some(e), _)
183183
| Ret(Some(e))
184184
| Unary(_, e)
185-
| Yield(Some(e))
186185
| Yeet(Some(e))
187186
| Become(e) => {
188187
expr = e;
189188
}
189+
Yield(kind) => match kind.expr() {
190+
Some(e) => expr = e,
191+
None => break None,
192+
},
190193
Closure(closure) => {
191194
expr = &closure.body;
192195
}
@@ -217,7 +220,6 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
217220
Break(_, None)
218221
| Range(_, None, _)
219222
| Ret(None)
220-
| Yield(None)
221223
| Array(_)
222224
| Call(_, _)
223225
| MethodCall(_)
@@ -237,7 +239,9 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
237239
| Yeet(None)
238240
| UnsafeBinderCast(..)
239241
| Err(_)
240-
| Dummy => break None,
242+
| Dummy => {
243+
break None;
244+
}
241245
}
242246
}
243247
}

‎compiler/rustc_ast/src/visit.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1269,8 +1269,8 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) -> V
12691269
try_visit!(visitor.visit_ty(container));
12701270
walk_list!(visitor, visit_ident, fields.iter());
12711271
}
1272-
ExprKind::Yield(optional_expression) => {
1273-
visit_opt!(visitor, visit_expr, optional_expression);
1272+
ExprKind::Yield(kind) => {
1273+
visit_opt!(visitor, visit_expr, kind.expr());
12741274
}
12751275
ExprKind::Try(subexpression) => try_visit!(visitor.visit_expr(subexpression)),
12761276
ExprKind::TryBlock(body) => try_visit!(visitor.visit_block(body)),

‎compiler/rustc_ast_lowering/src/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
351351
rest,
352352
)
353353
}
354-
ExprKind::Yield(opt_expr) => self.lower_expr_yield(e.span, opt_expr.as_deref()),
354+
ExprKind::Yield(kind) => self.lower_expr_yield(e.span, kind.expr().map(|x| &**x)),
355355
ExprKind::Err(guar) => hir::ExprKind::Err(*guar),
356356

357357
ExprKind::UnsafeBinderCast(kind, expr, ty) => hir::ExprKind::UnsafeBinderCast(

‎compiler/rustc_ast_pretty/src/pprust/state/expr.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_ast::util::literal::escape_byte_str_symbol;
88
use rustc_ast::util::parser::{self, ExprPrecedence, Fixity};
99
use rustc_ast::{
1010
self as ast, BlockCheckMode, FormatAlignment, FormatArgPosition, FormatArgsPiece, FormatCount,
11-
FormatDebugHex, FormatSign, FormatTrait, token,
11+
FormatDebugHex, FormatSign, FormatTrait, YieldKind, token,
1212
};
1313

1414
use crate::pp::Breaks::Inconsistent;
@@ -761,7 +761,7 @@ impl<'a> State<'a> {
761761
self.print_expr(e, FixupContext::default());
762762
self.pclose();
763763
}
764-
ast::ExprKind::Yield(e) => {
764+
ast::ExprKind::Yield(YieldKind::Prefix(e)) => {
765765
self.word("yield");
766766

767767
if let Some(expr) = e {
@@ -773,6 +773,14 @@ impl<'a> State<'a> {
773773
);
774774
}
775775
}
776+
ast::ExprKind::Yield(YieldKind::Postfix(e)) => {
777+
self.print_expr_cond_paren(
778+
e,
779+
e.precedence() < ExprPrecedence::Unambiguous,
780+
fixup.leftmost_subexpression_with_dot(),
781+
);
782+
self.word(".yield");
783+
}
776784
ast::ExprKind::Try(e) => {
777785
self.print_expr_cond_paren(
778786
e,

‎compiler/rustc_parse/src/parser/expr.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_ast::{
1717
self as ast, AnonConst, Arm, AttrStyle, AttrVec, BinOp, BinOpKind, BlockCheckMode, CaptureBy,
1818
ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, FnRetTy, Label, MacCall,
1919
MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind,
20+
YieldKind,
2021
};
2122
use rustc_ast_pretty::pprust;
2223
use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -1310,6 +1311,15 @@ impl<'a> Parser<'a> {
13101311
return self.parse_match_block(lo, match_span, self_arg, MatchKind::Postfix);
13111312
}
13121313

1314+
// Parse a postfix `yield`.
1315+
if self.eat_keyword(exp!(Yield)) {
1316+
let yield_span = self.prev_token.span;
1317+
self.psess.gated_spans.gate(sym::yield_expr, yield_span);
1318+
return Ok(
1319+
self.mk_expr(lo.to(yield_span), ExprKind::Yield(YieldKind::Postfix(self_arg)))
1320+
);
1321+
}
1322+
13131323
let fn_span_lo = self.token.span;
13141324
let mut seg = self.parse_path_segment(PathStyle::Expr, None)?;
13151325
self.check_trailing_angle_brackets(&seg, &[exp!(OpenParen)]);
@@ -1884,7 +1894,7 @@ impl<'a> Parser<'a> {
18841894
/// Parse `"yield" expr?`.
18851895
fn parse_expr_yield(&mut self) -> PResult<'a, P<Expr>> {
18861896
let lo = self.prev_token.span;
1887-
let kind = ExprKind::Yield(self.parse_expr_opt()?);
1897+
let kind = ExprKind::Yield(YieldKind::Prefix(self.parse_expr_opt()?));
18881898
let span = lo.to(self.prev_token.span);
18891899
self.psess.gated_spans.gate(sym::yield_expr, span);
18901900
let expr = self.mk_expr(span, kind);

‎src/tools/clippy/clippy_utils/src/ast_utils/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
201201
(Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lt, rt),
202202
(Block(lb, ll), Block(rb, rl)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lb, rb),
203203
(TryBlock(l), TryBlock(r)) => eq_block(l, r),
204-
(Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()),
204+
(Yield(l), Yield(r)) => eq_expr_opt(l.expr(), r.expr()) && l.same_kind(r),
205+
(Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()),
205206
(Break(ll, le), Break(rl, re)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_ref(), re.as_ref()),
206207
(Continue(ll), Continue(rl)) => eq_label(ll.as_ref(), rl.as_ref()),
207208
(Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => {
@@ -688,7 +689,7 @@ pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
688689

689690
pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
690691
use WherePredicateKind::*;
691-
over(&l.attrs, &r.attrs, eq_attr)
692+
over(&l.attrs, &r.attrs, eq_attr)
692693
&& match (&l.kind, &r.kind) {
693694
(BoundPredicate(l), BoundPredicate(r)) => {
694695
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {

‎src/tools/rustfmt/src/chains.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ enum ChainItemKind {
192192
StructField(symbol::Ident),
193193
TupleField(symbol::Ident, bool),
194194
Await,
195+
Yield,
195196
Comment(String, CommentPosition),
196197
}
197198

@@ -203,6 +204,7 @@ impl ChainItemKind {
203204
| ChainItemKind::StructField(..)
204205
| ChainItemKind::TupleField(..)
205206
| ChainItemKind::Await
207+
| ChainItemKind::Yield
206208
| ChainItemKind::Comment(..) => false,
207209
}
208210
}
@@ -257,6 +259,10 @@ impl ChainItemKind {
257259
let span = mk_sp(nested.span.hi(), expr.span.hi());
258260
(ChainItemKind::Await, span)
259261
}
262+
ast::ExprKind::Yield(ast::YieldKind::Postfix(ref nested)) => {
263+
let span = mk_sp(nested.span.hi(), expr.span.hi());
264+
(ChainItemKind::Yield, span)
265+
}
260266
_ => {
261267
return (
262268
ChainItemKind::Parent {
@@ -306,6 +312,7 @@ impl Rewrite for ChainItem {
306312
rewrite_ident(context, ident)
307313
),
308314
ChainItemKind::Await => ".await".to_owned(),
315+
ChainItemKind::Yield => ".yield".to_owned(),
309316
ChainItemKind::Comment(ref comment, _) => {
310317
rewrite_comment(comment, false, shape, context.config)?
311318
}
@@ -508,7 +515,8 @@ impl Chain {
508515
}),
509516
ast::ExprKind::Field(ref subexpr, _)
510517
| ast::ExprKind::Try(ref subexpr)
511-
| ast::ExprKind::Await(ref subexpr, _) => Some(SubExpr {
518+
| ast::ExprKind::Await(ref subexpr, _)
519+
| ast::ExprKind::Yield(ast::YieldKind::Postfix(ref subexpr)) => Some(SubExpr {
512520
expr: Self::convert_try(subexpr, context),
513521
is_method_call_receiver: false,
514522
}),

‎src/tools/rustfmt/src/expr.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ pub(crate) fn format_expr(
221221
Ok(format!("break{id_str}"))
222222
}
223223
}
224-
ast::ExprKind::Yield(ref opt_expr) => {
224+
ast::ExprKind::Yield(ast::YieldKind::Prefix(ref opt_expr)) => {
225225
if let Some(ref expr) = *opt_expr {
226226
rewrite_unary_prefix(context, "yield ", &**expr, shape)
227227
} else {
@@ -243,7 +243,8 @@ pub(crate) fn format_expr(
243243
ast::ExprKind::Try(..)
244244
| ast::ExprKind::Field(..)
245245
| ast::ExprKind::MethodCall(..)
246-
| ast::ExprKind::Await(_, _) => rewrite_chain(expr, context, shape),
246+
| ast::ExprKind::Await(_, _)
247+
| ast::ExprKind::Yield(ast::YieldKind::Postfix(_)) => rewrite_chain(expr, context, shape),
247248
ast::ExprKind::MacCall(ref mac) => {
248249
rewrite_macro(mac, None, context, shape, MacroPosition::Expression).or_else(|_| {
249250
wrap_str(

‎src/tools/rustfmt/src/utils.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_ast::ast::{
44
self, Attribute, MetaItem, MetaItemInner, MetaItemKind, NodeId, Path, Visibility,
55
VisibilityKind,
66
};
7-
use rustc_ast::ptr;
7+
use rustc_ast::{YieldKind, ptr};
88
use rustc_ast_pretty::pprust;
99
use rustc_span::{BytePos, LocalExpnId, Span, Symbol, SyntaxContext, sym, symbol};
1010
use unicode_width::UnicodeWidthStr;
@@ -485,7 +485,9 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
485485
| ast::ExprKind::Index(_, ref expr, _)
486486
| ast::ExprKind::Unary(_, ref expr)
487487
| ast::ExprKind::Try(ref expr)
488-
| ast::ExprKind::Yield(Some(ref expr)) => is_block_expr(context, expr, repr),
488+
| ast::ExprKind::Yield(YieldKind::Prefix(Some(ref expr))) => {
489+
is_block_expr(context, expr, repr)
490+
}
489491
ast::ExprKind::Closure(ref closure) => is_block_expr(context, &closure.body, repr),
490492
// This can only be a string lit
491493
ast::ExprKind::Lit(_) => {
@@ -515,7 +517,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
515517
| ast::ExprKind::Tup(..)
516518
| ast::ExprKind::Use(..)
517519
| ast::ExprKind::Type(..)
518-
| ast::ExprKind::Yield(None)
520+
| ast::ExprKind::Yield(..)
519521
| ast::ExprKind::Underscore => false,
520522
}
521523
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This demonstrates a proposed alternate or additional option of having yield in postfix position.
2+
//@ edition: 2024
3+
4+
#![feature(gen_blocks, coroutines, coroutine_trait, yield_expr)]
5+
6+
use std::ops::{Coroutine, CoroutineState};
7+
use std::pin::pin;
8+
9+
fn main() {
10+
let mut coro = pin!(
11+
#[coroutine]
12+
|_: i32| {
13+
let x = 1.yield;
14+
(x + 2).await;
15+
}
16+
);
17+
}

‎tests/pretty/postfix-yield.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This demonstrates a proposed alternate or additional option of having yield in postfix position.
2+
//@ edition: 2024
3+
//@ pp-exact
4+
5+
#![feature(gen_blocks, coroutines, coroutine_trait, yield_expr)]
6+
7+
use std::ops::{Coroutine, CoroutineState};
8+
use std::pin::pin;
9+
10+
fn main() {
11+
let mut gn = gen { yield 1; 2.yield; (1 + 2).yield; };
12+
13+
let mut coro =
14+
pin!(#[coroutine] |_: i32| { let x = 1.yield; (x + 2).yield; });
15+
}

‎tests/ui/coroutine/postfix-yield.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// This demonstrates a proposed alternate or additional option of having yield in postfix position.
2+
3+
//@ run-pass
4+
//@ edition: 2024
5+
6+
#![feature(gen_blocks, coroutines, coroutine_trait, yield_expr)]
7+
8+
use std::ops::{Coroutine, CoroutineState};
9+
use std::pin::pin;
10+
11+
fn main() {
12+
// generators (i.e. yield doesn't return anything useful)
13+
let mut gn = gen {
14+
yield 1;
15+
2.yield;
16+
};
17+
18+
assert_eq!(gn.next(), Some(1));
19+
assert_eq!(gn.next(), Some(2));
20+
assert_eq!(gn.next(), None);
21+
22+
//coroutines (i.e. yield returns something useful)
23+
let mut coro = pin!(
24+
#[coroutine]
25+
|_: i32| {
26+
let x = 1.yield;
27+
(x + 2).yield;
28+
}
29+
);
30+
31+
assert_eq!(coro.as_mut().resume(0), CoroutineState::Yielded(1));
32+
assert_eq!(coro.as_mut().resume(2), CoroutineState::Yielded(4));
33+
assert_eq!(coro.as_mut().resume(3), CoroutineState::Complete(()));
34+
}

0 commit comments

Comments
 (0)
Failed to load comments.