Skip to content

Commit 508e058

Browse files
committed
Auto merge of #96376 - scottmcm:do-yeet, r=oli-obk
Add `do yeet` expressions to allow experimentation in nightly Two main goals for this: - Ensure that trait restructuring in #84277 (comment) doesn't accidentally close us off from the possibility of doing this in future, as sketched in https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#possibilities-for-yeet - Experiment with the *existence* of syntax for this, to be able to weight the syntax-vs-library tradeoffs better than we can right now. Notably the syntax (with `do`) and name in this PR are not intended as candidates for stabilization, but they make a good v0 PR for adding this with minimal impact to compiler maintenance or priming one possible name choice over another. r? `@oli-obk` The lang `second` for doing this: rust-lang/lang-team#160 (comment) Tracking issues - Lang, #96373 - Libs-api, #96374
2 parents 637b3f6 + b317ec1 commit 508e058

File tree

33 files changed

+303
-21
lines changed

33 files changed

+303
-21
lines changed

compiler/rustc_ast/src/ast.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,7 @@ impl Expr {
12751275
ExprKind::Paren(..) => ExprPrecedence::Paren,
12761276
ExprKind::Try(..) => ExprPrecedence::Try,
12771277
ExprKind::Yield(..) => ExprPrecedence::Yield,
1278+
ExprKind::Yeet(..) => ExprPrecedence::Yeet,
12781279
ExprKind::Err => ExprPrecedence::Err,
12791280
}
12801281
}
@@ -1462,6 +1463,10 @@ pub enum ExprKind {
14621463
/// A `yield`, with an optional value to be yielded.
14631464
Yield(Option<P<Expr>>),
14641465

1466+
/// A `do yeet` (aka `throw`/`fail`/`bail`/`raise`/whatever),
1467+
/// with an optional value to be returned.
1468+
Yeet(Option<P<Expr>>),
1469+
14651470
/// Placeholder for an expression that wasn't syntactically well formed in some way.
14661471
Err,
14671472
}

compiler/rustc_ast/src/mut_visit.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,9 @@ pub fn noop_visit_expr<T: MutVisitor>(
13941394
ExprKind::Ret(expr) => {
13951395
visit_opt(expr, |expr| vis.visit_expr(expr));
13961396
}
1397+
ExprKind::Yeet(expr) => {
1398+
visit_opt(expr, |expr| vis.visit_expr(expr));
1399+
}
13971400
ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm),
13981401
ExprKind::MacCall(mac) => vis.visit_mac_call(mac),
13991402
ExprKind::Struct(se) => {

compiler/rustc_ast/src/util/parser.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ pub enum ExprPrecedence {
247247
Continue,
248248
Ret,
249249
Yield,
250+
Yeet,
250251

251252
Range,
252253

@@ -299,7 +300,8 @@ impl ExprPrecedence {
299300
ExprPrecedence::Break |
300301
ExprPrecedence::Continue |
301302
ExprPrecedence::Ret |
302-
ExprPrecedence::Yield => PREC_JUMP,
303+
ExprPrecedence::Yield |
304+
ExprPrecedence::Yeet => PREC_JUMP,
303305

304306
// `Range` claims to have higher precedence than `Assign`, but `x .. x = x` fails to
305307
// parse, instead of parsing as `(x .. x) = x`. Giving `Range` a lower precedence

compiler/rustc_ast/src/visit.rs

+3
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,9 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
893893
ExprKind::Ret(ref optional_expression) => {
894894
walk_list!(visitor, visit_expr, optional_expression);
895895
}
896+
ExprKind::Yeet(ref optional_expression) => {
897+
walk_list!(visitor, visit_expr, optional_expression);
898+
}
896899
ExprKind::MacCall(ref mac) => visitor.visit_mac_call(mac),
897900
ExprKind::Paren(ref subexpression) => visitor.visit_expr(subexpression),
898901
ExprKind::InlineAsm(ref asm) => walk_inline_asm(visitor, asm),

compiler/rustc_ast_lowering/src/expr.rs

+39
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
221221
let e = e.as_ref().map(|x| self.lower_expr(x));
222222
hir::ExprKind::Ret(e)
223223
}
224+
ExprKind::Yeet(ref sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
224225
ExprKind::InlineAsm(ref asm) => {
225226
hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm))
226227
}
@@ -1543,6 +1544,44 @@ impl<'hir> LoweringContext<'_, 'hir> {
15431544
)
15441545
}
15451546

1547+
/// Desugar `ExprKind::Yeet` from: `do yeet <expr>` into:
1548+
/// ```rust
1549+
/// // If there is an enclosing `try {...}`:
1550+
/// break 'catch_target FromResidual::from_residual(Yeet(residual)),
1551+
/// // Otherwise:
1552+
/// return FromResidual::from_residual(Yeet(residual)),
1553+
/// ```
1554+
/// But to simplify this, there's a `from_yeet` lang item function which
1555+
/// handles the combined `FromResidual::from_residual(Yeet(residual))`.
1556+
fn lower_expr_yeet(&mut self, span: Span, sub_expr: Option<&Expr>) -> hir::ExprKind<'hir> {
1557+
// The expression (if present) or `()` otherwise.
1558+
let (yeeted_span, yeeted_expr) = if let Some(sub_expr) = sub_expr {
1559+
(sub_expr.span, self.lower_expr(sub_expr))
1560+
} else {
1561+
(self.mark_span_with_reason(DesugaringKind::YeetExpr, span, None), self.expr_unit(span))
1562+
};
1563+
1564+
let unstable_span = self.mark_span_with_reason(
1565+
DesugaringKind::YeetExpr,
1566+
span,
1567+
self.allow_try_trait.clone(),
1568+
);
1569+
1570+
let from_yeet_expr = self.wrap_in_try_constructor(
1571+
hir::LangItem::TryTraitFromYeet,
1572+
unstable_span,
1573+
yeeted_expr,
1574+
yeeted_span,
1575+
);
1576+
1577+
if let Some(catch_node) = self.catch_scope {
1578+
let target_id = Ok(self.lower_node_id(catch_node));
1579+
hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr))
1580+
} else {
1581+
hir::ExprKind::Ret(Some(from_yeet_expr))
1582+
}
1583+
}
1584+
15461585
// =========================================================================
15471586
// Helper methods for building HIR.
15481587
// =========================================================================

compiler/rustc_ast_lowering/src/item.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> {
8585
task_context: None,
8686
current_item: None,
8787
captured_lifetimes: None,
88-
allow_try_trait: Some([sym::try_trait_v2][..].into()),
88+
allow_try_trait: Some([sym::try_trait_v2, sym::yeet_desugar_details][..].into()),
8989
allow_gen_future: Some([sym::gen_future][..].into()),
9090
allow_into_future: Some([sym::into_future][..].into()),
9191
};

compiler/rustc_ast_passes/src/feature_gate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
783783
gate_all!(inline_const, "inline-const is experimental");
784784
gate_all!(inline_const_pat, "inline-const in pattern position is experimental");
785785
gate_all!(associated_const_equality, "associated const equality is incomplete");
786+
gate_all!(yeet_expr, "`do yeet` expression is experimental");
786787

787788
// All uses of `gate_all!` below this point were added in #65742,
788789
// and subsequently disabled (with the non-early gating readded).

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ impl<'a> State<'a> {
6464
// parses as the erroneous construct `if (return {})`, not `if (return) {}`.
6565
pub(super) fn cond_needs_par(expr: &ast::Expr) -> bool {
6666
match expr.kind {
67-
ast::ExprKind::Break(..) | ast::ExprKind::Closure(..) | ast::ExprKind::Ret(..) => true,
67+
ast::ExprKind::Break(..)
68+
| ast::ExprKind::Closure(..)
69+
| ast::ExprKind::Ret(..)
70+
| ast::ExprKind::Yeet(..) => true,
6871
_ => parser::contains_exterior_struct_lit(expr),
6972
}
7073
}
@@ -502,6 +505,15 @@ impl<'a> State<'a> {
502505
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
503506
}
504507
}
508+
ast::ExprKind::Yeet(ref result) => {
509+
self.word("do");
510+
self.word(" ");
511+
self.word("yeet");
512+
if let Some(ref expr) = *result {
513+
self.word(" ");
514+
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
515+
}
516+
}
505517
ast::ExprKind::InlineAsm(ref a) => {
506518
self.word("asm!");
507519
self.print_inline_asm(a);

compiler/rustc_feature/src/active.rs

+2
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,8 @@ declare_features! (
544544
(active, used_with_arg, "1.60.0", Some(93798), None),
545545
/// Allows `extern "wasm" fn`
546546
(active, wasm_abi, "1.53.0", Some(83788), None),
547+
/// Allows `do yeet` expressions
548+
(active, yeet_expr, "1.62.0", Some(96373), None),
547549
// !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!!
548550
// Features are listed in alphabetical order. Tidy will fail if you don't keep it this way.
549551
// !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!!

compiler/rustc_hir/src/lang_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ language_item_table! {
293293
TryTraitFromResidual, sym::from_residual, from_residual_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
294294
TryTraitFromOutput, sym::from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
295295
TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
296+
TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None;
296297

297298
PollReady, sym::Ready, poll_ready_variant, Target::Variant, GenericRequirement::None;
298299
PollPending, sym::Pending, poll_pending_variant, Target::Variant, GenericRequirement::None;

compiler/rustc_parse/src/parser/expr.rs

+21
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,8 @@ impl<'a> Parser<'a> {
13741374
self.parse_break_expr(attrs)
13751375
} else if self.eat_keyword(kw::Yield) {
13761376
self.parse_yield_expr(attrs)
1377+
} else if self.is_do_yeet() {
1378+
self.parse_yeet_expr(attrs)
13771379
} else if self.eat_keyword(kw::Let) {
13781380
self.parse_let_expr(attrs)
13791381
} else if self.eat_keyword(kw::Underscore) {
@@ -1605,6 +1607,21 @@ impl<'a> Parser<'a> {
16051607
self.maybe_recover_from_bad_qpath(expr, true)
16061608
}
16071609

1610+
/// Parse `"do" "yeet" expr?`.
1611+
fn parse_yeet_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
1612+
let lo = self.token.span;
1613+
1614+
self.bump(); // `do`
1615+
self.bump(); // `yeet`
1616+
1617+
let kind = ExprKind::Yeet(self.parse_expr_opt()?);
1618+
1619+
let span = lo.to(self.prev_token.span);
1620+
self.sess.gated_spans.gate(sym::yeet_expr, span);
1621+
let expr = self.mk_expr(span, kind, attrs);
1622+
self.maybe_recover_from_bad_qpath(expr, true)
1623+
}
1624+
16081625
/// Parse `"break" (('label (:? expr)?) | expr?)` with `"break"` token already eaten.
16091626
/// If the label is followed immediately by a `:` token, the label and `:` are
16101627
/// parsed as part of the expression (i.e. a labeled loop). The language team has
@@ -2676,6 +2693,10 @@ impl<'a> Parser<'a> {
26762693
&& !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
26772694
}
26782695

2696+
fn is_do_yeet(&self) -> bool {
2697+
self.token.is_keyword(kw::Do) && self.is_keyword_ahead(1, &[kw::Yeet])
2698+
}
2699+
26792700
fn is_try_block(&self) -> bool {
26802701
self.token.is_keyword(kw::Try)
26812702
&& self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace))

compiler/rustc_span/src/hygiene.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,7 @@ pub enum DesugaringKind {
11321132
CondTemporary,
11331133
QuestionMark,
11341134
TryBlock,
1135+
YeetExpr,
11351136
/// Desugaring of an `impl Trait` in return type position
11361137
/// to an `type Foo = impl Trait;` and replacing the
11371138
/// `impl Trait` with `Foo`.
@@ -1152,6 +1153,7 @@ impl DesugaringKind {
11521153
DesugaringKind::Await => "`await` expression",
11531154
DesugaringKind::QuestionMark => "operator `?`",
11541155
DesugaringKind::TryBlock => "`try` block",
1156+
DesugaringKind::YeetExpr => "`do yeet` expression",
11551157
DesugaringKind::OpaqueTy => "`impl Trait`",
11561158
DesugaringKind::ForLoop => "`for` loop",
11571159
DesugaringKind::LetElse => "`let...else`",

compiler/rustc_span/src/symbol.rs

+4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ symbols! {
101101
MacroRules: "macro_rules",
102102
Raw: "raw",
103103
Union: "union",
104+
Yeet: "yeet",
104105
}
105106

106107
// Pre-interned symbols that can be referred to with `rustc_span::sym::*`.
@@ -714,6 +715,7 @@ symbols! {
714715
from_residual,
715716
from_size_align_unchecked,
716717
from_usize,
718+
from_yeet,
717719
fsub_fast,
718720
fundamental,
719721
future,
@@ -1534,6 +1536,8 @@ symbols! {
15341536
x87_reg,
15351537
xer,
15361538
xmm_reg,
1539+
yeet_desugar_details,
1540+
yeet_expr,
15371541
ymm_reg,
15381542
zmm_reg,
15391543
}

library/core/src/ops/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ pub use self::range::OneSidedRange;
187187
#[unstable(feature = "try_trait_v2", issue = "84277")]
188188
pub use self::try_trait::{FromResidual, Try};
189189

190+
#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
191+
pub use self::try_trait::Yeet;
192+
190193
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
191194
pub use self::try_trait::Residual;
192195

library/core/src/ops/try_trait.rs

+22
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,22 @@ pub trait FromResidual<R = <Self as Try>::Residual> {
330330
fn from_residual(residual: R) -> Self;
331331
}
332332

333+
#[cfg(not(bootstrap))]
334+
#[unstable(
335+
feature = "yeet_desugar_details",
336+
issue = "none",
337+
reason = "just here to simplify the desugaring; will never be stabilized"
338+
)]
339+
#[inline]
340+
#[track_caller] // because `Result::from_residual` has it
341+
#[lang = "from_yeet"]
342+
pub fn from_yeet<T, Y>(yeeted: Y) -> T
343+
where
344+
T: FromResidual<Yeet<Y>>,
345+
{
346+
FromResidual::from_residual(Yeet(yeeted))
347+
}
348+
333349
/// Allows retrieving the canonical type implementing [`Try`] that has this type
334350
/// as its residual and allows it to hold an `O` as its output.
335351
///
@@ -395,3 +411,9 @@ impl<T> FromResidual for NeverShortCircuit<T> {
395411
impl<T> Residual<T> for NeverShortCircuitResidual {
396412
type TryType = NeverShortCircuit<T>;
397413
}
414+
415+
/// Implement `FromResidual<Yeet<T>>` on your type to enable
416+
/// `do yeet expr` syntax in functions returning your type.
417+
#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
418+
#[derive(Debug)]
419+
pub struct Yeet<T>(pub T);

library/core/src/option.rs

+8
Original file line numberDiff line numberDiff line change
@@ -2287,6 +2287,14 @@ impl<T> const ops::FromResidual for Option<T> {
22872287
}
22882288
}
22892289

2290+
#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
2291+
impl<T> ops::FromResidual<ops::Yeet<()>> for Option<T> {
2292+
#[inline]
2293+
fn from_residual(ops::Yeet(()): ops::Yeet<()>) -> Self {
2294+
None
2295+
}
2296+
}
2297+
22902298
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
22912299
impl<T> ops::Residual<T> for Option<convert::Infallible> {
22922300
type TryType = Option<T>;

library/core/src/result.rs

+8
Original file line numberDiff line numberDiff line change
@@ -2107,6 +2107,14 @@ impl<T, E, F: ~const From<E>> const ops::FromResidual<Result<convert::Infallible
21072107
}
21082108
}
21092109

2110+
#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
2111+
impl<T, E, F: From<E>> ops::FromResidual<ops::Yeet<E>> for Result<T, F> {
2112+
#[inline]
2113+
fn from_residual(ops::Yeet(e): ops::Yeet<E>) -> Self {
2114+
Err(From::from(e))
2115+
}
2116+
}
2117+
21102118
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
21112119
impl<T, E> ops::Residual<T> for Result<convert::Infallible, E> {
21122120
type TryType = Result<T, E>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `yeet_expr`
2+
3+
The tracking issue for this feature is: [#96373]
4+
5+
[#96373]: https://github.com/rust-lang/rust/issues/96373
6+
7+
------------------------
8+
9+
The `yeet_expr` feature adds support for `do yeet` expressions,
10+
which can be used to early-exit from a function or `try` block.
11+
12+
These are highly experimental, thus the placeholder syntax.
13+
14+
```rust,edition2021
15+
#![feature(yeet_expr)]
16+
17+
fn foo() -> Result<String, i32> {
18+
do yeet 4;
19+
}
20+
assert_eq!(foo(), Err(4));
21+
22+
fn bar() -> Option<String> {
23+
do yeet;
24+
}
25+
assert_eq!(bar(), None);
26+
```

src/test/pretty/yeet-expr.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// pp-exact
2+
#![feature(yeet_expr)]
3+
4+
fn yeet_no_expr() -> Option<String> { do yeet }
5+
6+
fn yeet_no_expr_with_semicolon() -> Option<String> { do yeet; }
7+
8+
fn yeet_with_expr() -> Result<String, i32> { do yeet 1 + 2 }
9+
10+
fn yeet_with_expr_with_semicolon() -> Result<String, i32> { do yeet 1 + 2; }
11+
12+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// compile-flags: --edition 2021
2+
3+
pub fn demo() -> Option<i32> {
4+
#[cfg(nope)]
5+
{
6+
do yeet //~ ERROR `do yeet` expression is experimental
7+
}
8+
9+
Some(1)
10+
}
11+
12+
#[cfg(nope)]
13+
pub fn alternative() -> Result<(), String> {
14+
do yeet "hello"; //~ ERROR `do yeet` expression is experimental
15+
}
16+
17+
fn main() {
18+
demo();
19+
}

0 commit comments

Comments
 (0)