Skip to content

Add do yeet expressions to allow experimentation in nightly #96376

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 5 commits into from
May 1, 2022
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
5 changes: 5 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,7 @@ impl Expr {
ExprKind::Paren(..) => ExprPrecedence::Paren,
ExprKind::Try(..) => ExprPrecedence::Try,
ExprKind::Yield(..) => ExprPrecedence::Yield,
ExprKind::Yeet(..) => ExprPrecedence::Yeet,
ExprKind::Err => ExprPrecedence::Err,
}
}
Expand Down Expand Up @@ -1462,6 +1463,10 @@ pub enum ExprKind {
/// A `yield`, with an optional value to be yielded.
Yield(Option<P<Expr>>),

/// A `do yeet` (aka `throw`/`fail`/`bail`/`raise`/whatever),
/// with an optional value to be returned.
Yeet(Option<P<Expr>>),

/// Placeholder for an expression that wasn't syntactically well formed in some way.
Err,
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,9 @@ pub fn noop_visit_expr<T: MutVisitor>(
ExprKind::Ret(expr) => {
visit_opt(expr, |expr| vis.visit_expr(expr));
}
ExprKind::Yeet(expr) => {
visit_opt(expr, |expr| vis.visit_expr(expr));
}
ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm),
ExprKind::MacCall(mac) => vis.visit_mac_call(mac),
ExprKind::Struct(se) => {
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_ast/src/util/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ pub enum ExprPrecedence {
Continue,
Ret,
Yield,
Yeet,

Range,

Expand Down Expand Up @@ -299,7 +300,8 @@ impl ExprPrecedence {
ExprPrecedence::Break |
ExprPrecedence::Continue |
ExprPrecedence::Ret |
ExprPrecedence::Yield => PREC_JUMP,
ExprPrecedence::Yield |
ExprPrecedence::Yeet => PREC_JUMP,

// `Range` claims to have higher precedence than `Assign`, but `x .. x = x` fails to
// parse, instead of parsing as `(x .. x) = x`. Giving `Range` a lower precedence
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,9 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
ExprKind::Ret(ref optional_expression) => {
walk_list!(visitor, visit_expr, optional_expression);
}
ExprKind::Yeet(ref optional_expression) => {
walk_list!(visitor, visit_expr, optional_expression);
}
ExprKind::MacCall(ref mac) => visitor.visit_mac_call(mac),
ExprKind::Paren(ref subexpression) => visitor.visit_expr(subexpression),
ExprKind::InlineAsm(ref asm) => walk_inline_asm(visitor, asm),
Expand Down
39 changes: 39 additions & 0 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
let e = e.as_ref().map(|x| self.lower_expr(x));
hir::ExprKind::Ret(e)
}
ExprKind::Yeet(ref sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
ExprKind::InlineAsm(ref asm) => {
hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm))
}
Expand Down Expand Up @@ -1543,6 +1544,44 @@ impl<'hir> LoweringContext<'_, 'hir> {
)
}

/// Desugar `ExprKind::Yeet` from: `do yeet <expr>` into:
/// ```rust
/// // If there is an enclosing `try {...}`:
/// break 'catch_target FromResidual::from_residual(Yeet(residual)),
/// // Otherwise:
/// return FromResidual::from_residual(Yeet(residual)),
/// ```
/// But to simplify this, there's a `from_yeet` lang item function which
/// handles the combined `FromResidual::from_residual(Yeet(residual))`.
fn lower_expr_yeet(&mut self, span: Span, sub_expr: Option<&Expr>) -> hir::ExprKind<'hir> {
// The expression (if present) or `()` otherwise.
let (yeeted_span, yeeted_expr) = if let Some(sub_expr) = sub_expr {
(sub_expr.span, self.lower_expr(sub_expr))
} else {
(self.mark_span_with_reason(DesugaringKind::YeetExpr, span, None), self.expr_unit(span))
};

let unstable_span = self.mark_span_with_reason(
DesugaringKind::YeetExpr,
span,
self.allow_try_trait.clone(),
);

let from_yeet_expr = self.wrap_in_try_constructor(
hir::LangItem::TryTraitFromYeet,
unstable_span,
yeeted_expr,
yeeted_span,
);

if let Some(catch_node) = self.catch_scope {
let target_id = Ok(self.lower_node_id(catch_node));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preexisting, but I wish we didn't have to lower the node id twice.

hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr))
} else {
hir::ExprKind::Ret(Some(from_yeet_expr))
}
}

// =========================================================================
// Helper methods for building HIR.
// =========================================================================
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> {
task_context: None,
current_item: None,
captured_lifetimes: None,
allow_try_trait: Some([sym::try_trait_v2][..].into()),
allow_try_trait: Some([sym::try_trait_v2, sym::yeet_desugar_details][..].into()),
allow_gen_future: Some([sym::gen_future][..].into()),
allow_into_future: Some([sym::into_future][..].into()),
};
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
gate_all!(inline_const, "inline-const is experimental");
gate_all!(inline_const_pat, "inline-const in pattern position is experimental");
gate_all!(associated_const_equality, "associated const equality is incomplete");
gate_all!(yeet_expr, "`do yeet` expression is experimental");

// All uses of `gate_all!` below this point were added in #65742,
// and subsequently disabled (with the non-early gating readded).
Expand Down
14 changes: 13 additions & 1 deletion compiler/rustc_ast_pretty/src/pprust/state/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ impl<'a> State<'a> {
// parses as the erroneous construct `if (return {})`, not `if (return) {}`.
pub(super) fn cond_needs_par(expr: &ast::Expr) -> bool {
match expr.kind {
ast::ExprKind::Break(..) | ast::ExprKind::Closure(..) | ast::ExprKind::Ret(..) => true,
ast::ExprKind::Break(..)
| ast::ExprKind::Closure(..)
| ast::ExprKind::Ret(..)
| ast::ExprKind::Yeet(..) => true,
_ => parser::contains_exterior_struct_lit(expr),
}
}
Expand Down Expand Up @@ -502,6 +505,15 @@ impl<'a> State<'a> {
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
}
}
ast::ExprKind::Yeet(ref result) => {
self.word("do");
self.word(" ");
self.word("yeet");
if let Some(ref expr) = *result {
self.word(" ");
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
}
}
ast::ExprKind::InlineAsm(ref a) => {
self.word("asm!");
self.print_inline_asm(a);
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,8 @@ declare_features! (
(active, used_with_arg, "1.60.0", Some(93798), None),
/// Allows `extern "wasm" fn`
(active, wasm_abi, "1.53.0", Some(83788), None),
/// Allows `do yeet` expressions
(active, yeet_expr, "1.62.0", Some(96373), None),
// !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!!
// Features are listed in alphabetical order. Tidy will fail if you don't keep it this way.
// !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!!
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ language_item_table! {
TryTraitFromResidual, sym::from_residual, from_residual_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
TryTraitFromOutput, sym::from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None;

PollReady, sym::Ready, poll_ready_variant, Target::Variant, GenericRequirement::None;
PollPending, sym::Pending, poll_pending_variant, Target::Variant, GenericRequirement::None;
Expand Down
21 changes: 21 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,8 @@ impl<'a> Parser<'a> {
self.parse_break_expr(attrs)
} else if self.eat_keyword(kw::Yield) {
self.parse_yield_expr(attrs)
} else if self.is_do_yeet() {
self.parse_yeet_expr(attrs)
} else if self.eat_keyword(kw::Let) {
self.parse_let_expr(attrs)
} else if self.eat_keyword(kw::Underscore) {
Expand Down Expand Up @@ -1605,6 +1607,21 @@ impl<'a> Parser<'a> {
self.maybe_recover_from_bad_qpath(expr, true)
}

/// Parse `"do" "yeet" expr?`.
fn parse_yeet_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
let lo = self.token.span;

self.bump(); // `do`
self.bump(); // `yeet`

let kind = ExprKind::Yeet(self.parse_expr_opt()?);

let span = lo.to(self.prev_token.span);
self.sess.gated_spans.gate(sym::yeet_expr, span);
let expr = self.mk_expr(span, kind, attrs);
self.maybe_recover_from_bad_qpath(expr, true)
}

/// Parse `"break" (('label (:? expr)?) | expr?)` with `"break"` token already eaten.
/// If the label is followed immediately by a `:` token, the label and `:` are
/// parsed as part of the expression (i.e. a labeled loop). The language team has
Expand Down Expand Up @@ -2676,6 +2693,10 @@ impl<'a> Parser<'a> {
&& !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
}

fn is_do_yeet(&self) -> bool {
self.token.is_keyword(kw::Do) && self.is_keyword_ahead(1, &[kw::Yeet])
}

fn is_try_block(&self) -> bool {
self.token.is_keyword(kw::Try)
&& self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace))
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,7 @@ pub enum DesugaringKind {
CondTemporary,
QuestionMark,
TryBlock,
YeetExpr,
/// Desugaring of an `impl Trait` in return type position
/// to an `type Foo = impl Trait;` and replacing the
/// `impl Trait` with `Foo`.
Expand All @@ -1152,6 +1153,7 @@ impl DesugaringKind {
DesugaringKind::Await => "`await` expression",
DesugaringKind::QuestionMark => "operator `?`",
DesugaringKind::TryBlock => "`try` block",
DesugaringKind::YeetExpr => "`do yeet` expression",
DesugaringKind::OpaqueTy => "`impl Trait`",
DesugaringKind::ForLoop => "`for` loop",
DesugaringKind::LetElse => "`let...else`",
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ symbols! {
MacroRules: "macro_rules",
Raw: "raw",
Union: "union",
Yeet: "yeet",
}

// Pre-interned symbols that can be referred to with `rustc_span::sym::*`.
Expand Down Expand Up @@ -714,6 +715,7 @@ symbols! {
from_residual,
from_size_align_unchecked,
from_usize,
from_yeet,
fsub_fast,
fundamental,
future,
Expand Down Expand Up @@ -1534,6 +1536,8 @@ symbols! {
x87_reg,
xer,
xmm_reg,
yeet_desugar_details,
yeet_expr,
ymm_reg,
zmm_reg,
}
Expand Down
3 changes: 3 additions & 0 deletions library/core/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ pub use self::range::OneSidedRange;
#[unstable(feature = "try_trait_v2", issue = "84277")]
pub use self::try_trait::{FromResidual, Try};

#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
pub use self::try_trait::Yeet;

#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
pub use self::try_trait::Residual;

Expand Down
22 changes: 22 additions & 0 deletions library/core/src/ops/try_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,22 @@ pub trait FromResidual<R = <Self as Try>::Residual> {
fn from_residual(residual: R) -> Self;
}

#[cfg(not(bootstrap))]
#[unstable(
feature = "yeet_desugar_details",
issue = "none",
reason = "just here to simplify the desugaring; will never be stabilized"
)]
#[inline]
#[track_caller] // because `Result::from_residual` has it
#[lang = "from_yeet"]
pub fn from_yeet<T, Y>(yeeted: Y) -> T
where
T: FromResidual<Yeet<Y>>,
{
FromResidual::from_residual(Yeet(yeeted))
}

/// Allows retrieving the canonical type implementing [`Try`] that has this type
/// as its residual and allows it to hold an `O` as its output.
///
Expand Down Expand Up @@ -395,3 +411,9 @@ impl<T> FromResidual for NeverShortCircuit<T> {
impl<T> Residual<T> for NeverShortCircuitResidual {
type TryType = NeverShortCircuit<T>;
}

/// Implement `FromResidual<Yeet<T>>` on your type to enable
/// `do yeet expr` syntax in functions returning your type.
#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
#[derive(Debug)]
pub struct Yeet<T>(pub T);
8 changes: 8 additions & 0 deletions library/core/src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2287,6 +2287,14 @@ impl<T> const ops::FromResidual for Option<T> {
}
}

#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
impl<T> ops::FromResidual<ops::Yeet<()>> for Option<T> {
#[inline]
fn from_residual(ops::Yeet(()): ops::Yeet<()>) -> Self {
None
}
}

#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
impl<T> ops::Residual<T> for Option<convert::Infallible> {
type TryType = Option<T>;
Expand Down
8 changes: 8 additions & 0 deletions library/core/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,14 @@ impl<T, E, F: ~const From<E>> const ops::FromResidual<Result<convert::Infallible
}
}

#[unstable(feature = "try_trait_v2_yeet", issue = "96374")]
impl<T, E, F: From<E>> ops::FromResidual<ops::Yeet<E>> for Result<T, F> {
#[inline]
fn from_residual(ops::Yeet(e): ops::Yeet<E>) -> Self {
Err(From::from(e))
}
}

#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
impl<T, E> ops::Residual<T> for Result<convert::Infallible, E> {
type TryType = Result<T, E>;
Expand Down
26 changes: 26 additions & 0 deletions src/doc/unstable-book/src/language-features/yeet-expr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# `yeet_expr`

The tracking issue for this feature is: [#96373]

[#96373]: https://github.com/rust-lang/rust/issues/96373

------------------------

The `yeet_expr` feature adds support for `do yeet` expressions,
which can be used to early-exit from a function or `try` block.

These are highly experimental, thus the placeholder syntax.

```rust,edition2021
#![feature(yeet_expr)]

fn foo() -> Result<String, i32> {
do yeet 4;
}
assert_eq!(foo(), Err(4));

fn bar() -> Option<String> {
do yeet;
}
assert_eq!(bar(), None);
```
12 changes: 12 additions & 0 deletions src/test/pretty/yeet-expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// pp-exact
#![feature(yeet_expr)]

fn yeet_no_expr() -> Option<String> { do yeet }

fn yeet_no_expr_with_semicolon() -> Option<String> { do yeet; }

fn yeet_with_expr() -> Result<String, i32> { do yeet 1 + 2 }

fn yeet_with_expr_with_semicolon() -> Result<String, i32> { do yeet 1 + 2; }

fn main() {}
19 changes: 19 additions & 0 deletions src/test/ui/feature-gates/feature-gate-yeet_expr-in-cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// compile-flags: --edition 2021

pub fn demo() -> Option<i32> {
#[cfg(nope)]
{
do yeet //~ ERROR `do yeet` expression is experimental
}

Some(1)
}

#[cfg(nope)]
pub fn alternative() -> Result<(), String> {
do yeet "hello"; //~ ERROR `do yeet` expression is experimental
}

fn main() {
demo();
}
Loading