From e094ee5f102dfea8e99137d3818510cd178e8817 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Fri, 25 Mar 2022 23:43:54 -0700 Subject: [PATCH] Add `do yeet` expressions to allow experimentation in nightly Using an obviously-placeholder syntax. An RFC would still be needed before this could have any chance at stabilization, and it might be removed at any point. But I'd really like to have it in nightly at least to ensure it works well with try_trait_v2, especially as we refactor the traits. --- compiler/rustc_ast/src/ast.rs | 5 +++ compiler/rustc_ast/src/mut_visit.rs | 3 ++ compiler/rustc_ast/src/util/parser.rs | 4 +- compiler/rustc_ast/src/visit.rs | 3 ++ compiler/rustc_ast_lowering/src/expr.rs | 39 +++++++++++++++++++ compiler/rustc_ast_lowering/src/item.rs | 2 +- compiler/rustc_ast_passes/src/feature_gate.rs | 8 ++++ .../rustc_ast_pretty/src/pprust/state/expr.rs | 14 ++++++- compiler/rustc_feature/src/active.rs | 2 + compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_parse/src/parser/expr.rs | 21 ++++++++++ compiler/rustc_span/src/hygiene.rs | 2 + compiler/rustc_span/src/symbol.rs | 4 ++ library/core/src/ops/mod.rs | 3 ++ library/core/src/ops/try_trait.rs | 22 +++++++++++ library/core/src/option.rs | 8 ++++ library/core/src/result.rs | 8 ++++ .../src/language-features/yeet-expr.md | 26 +++++++++++++ src/test/pretty/yeet-expr.rs | 12 ++++++ .../feature-gates/feature-gate-yeet_expr.rs | 9 +++++ .../feature-gate-yeet_expr.stderr | 21 ++++++++++ src/test/ui/try-trait/yeet-for-option.rs | 11 ++++++ src/test/ui/try-trait/yeet-for-result.rs | 11 ++++++ 23 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 src/doc/unstable-book/src/language-features/yeet-expr.md create mode 100644 src/test/pretty/yeet-expr.rs create mode 100644 src/test/ui/feature-gates/feature-gate-yeet_expr.rs create mode 100644 src/test/ui/feature-gates/feature-gate-yeet_expr.stderr create mode 100644 src/test/ui/try-trait/yeet-for-option.rs create mode 100644 src/test/ui/try-trait/yeet-for-result.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index cdcd221e811a..1a18d1964c97 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -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, } } @@ -1462,6 +1463,10 @@ pub enum ExprKind { /// A `yield`, with an optional value to be yielded. Yield(Option>), + /// A `do yeet` (aka `throw`/`fail`/`bail`/`raise`/whatever), + /// with an optional value to be returned. + Yeet(Option>), + /// Placeholder for an expression that wasn't syntactically well formed in some way. Err, } diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index d7b1bc6a7f58..4bf3d483f735 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1394,6 +1394,9 @@ pub fn noop_visit_expr( 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) => { diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 742a7d1d2df7..74b7fe9e2495 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -247,6 +247,7 @@ pub enum ExprPrecedence { Continue, Ret, Yield, + Yeet, Range, @@ -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 diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index e08ba73e0ae3..fa26716083f8 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -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), diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 37ae41fabf98..5c3e3be21167 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -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)) } @@ -1543,6 +1544,44 @@ impl<'hir> LoweringContext<'_, 'hir> { ) } + /// Desugar `ExprKind::Yeet` from: `do yeet ` 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)); + 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. // ========================================================================= diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 125acdcc27d9..5a95e5b084ad 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -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()), }; diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 649af48e48ad..add9955b1844 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -619,6 +619,14 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { ast::ExprKind::TryBlock(_) => { gate_feature_post!(&self, try_blocks, e.span, "`try` expression is experimental"); } + ast::ExprKind::Yeet(_) => { + gate_feature_post!( + &self, + yeet_expr, + e.span, + "`do yeet` expression is experimental" + ); + } ast::ExprKind::Block(_, Some(label)) => { gate_feature_post!( &self, diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 9de4cbbee13f..9f44f1b6cc20 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -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), } } @@ -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); diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index f3d4c8ab4384..9159d60463c4 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -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. // !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 9318ebb40b09..b3c22d4ec213 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -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; diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 13e9a5e660fe..6114e7aaa7bd 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -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) { @@ -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> { + 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 @@ -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)) diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 3889639b50f4..447b73fa3c3c 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -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`. @@ -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`", diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e3ce8105a8b4..c1299c94c4bb 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -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::*`. @@ -714,6 +715,7 @@ symbols! { from_residual, from_size_align_unchecked, from_usize, + from_yeet, fsub_fast, fundamental, future, @@ -1534,6 +1536,8 @@ symbols! { x87_reg, xer, xmm_reg, + yeet_desugar_details, + yeet_expr, ymm_reg, zmm_reg, } diff --git a/library/core/src/ops/mod.rs b/library/core/src/ops/mod.rs index 9d1e7e81b0e7..31c1a1d099dc 100644 --- a/library/core/src/ops/mod.rs +++ b/library/core/src/ops/mod.rs @@ -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; diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs index ba369e7f3aaa..3eaee958b69b 100644 --- a/library/core/src/ops/try_trait.rs +++ b/library/core/src/ops/try_trait.rs @@ -330,6 +330,22 @@ pub trait FromResidual::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(yeeted: Y) -> T +where + T: FromResidual>, +{ + 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. /// @@ -395,3 +411,9 @@ impl FromResidual for NeverShortCircuit { impl Residual for NeverShortCircuitResidual { type TryType = NeverShortCircuit; } + +/// Implement `FromResidual>` 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(pub T); diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 91e4708f6a60..f339b076dd7d 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -2287,6 +2287,14 @@ impl const ops::FromResidual for Option { } } +#[unstable(feature = "try_trait_v2_yeet", issue = "96374")] +impl ops::FromResidual> for Option { + #[inline] + fn from_residual(ops::Yeet(()): ops::Yeet<()>) -> Self { + None + } +} + #[unstable(feature = "try_trait_v2_residual", issue = "91285")] impl ops::Residual for Option { type TryType = Option; diff --git a/library/core/src/result.rs b/library/core/src/result.rs index b2b132300a29..5e5f8a5ab954 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -2107,6 +2107,14 @@ impl> const ops::FromResidual> ops::FromResidual> for Result { + #[inline] + fn from_residual(ops::Yeet(e): ops::Yeet) -> Self { + Err(From::from(e)) + } +} + #[unstable(feature = "try_trait_v2_residual", issue = "91285")] impl ops::Residual for Result { type TryType = Result; diff --git a/src/doc/unstable-book/src/language-features/yeet-expr.md b/src/doc/unstable-book/src/language-features/yeet-expr.md new file mode 100644 index 000000000000..bc1ba4c916b6 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/yeet-expr.md @@ -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 { + do yeet 4; +} +assert_eq!(foo(), Err(4)); + +fn bar() -> Option { + do yeet; +} +assert_eq!(bar(), None); +``` diff --git a/src/test/pretty/yeet-expr.rs b/src/test/pretty/yeet-expr.rs new file mode 100644 index 000000000000..c899f11b7240 --- /dev/null +++ b/src/test/pretty/yeet-expr.rs @@ -0,0 +1,12 @@ +// pp-exact +#![feature(yeet_expr)] + +fn yeet_no_expr() -> Option { do yeet } + +fn yeet_no_expr_with_semicolon() -> Option { do yeet; } + +fn yeet_with_expr() -> Result { do yeet 1 + 2 } + +fn yeet_with_expr_with_semicolon() -> Result { do yeet 1 + 2; } + +fn main() {} diff --git a/src/test/ui/feature-gates/feature-gate-yeet_expr.rs b/src/test/ui/feature-gates/feature-gate-yeet_expr.rs new file mode 100644 index 000000000000..978a84cf6e5f --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-yeet_expr.rs @@ -0,0 +1,9 @@ +// compile-flags: --edition 2018 + +pub fn demo() -> Option { + do yeet //~ ERROR `do yeet` expression is experimental +} + +pub fn main() -> Result<(), String> { + do yeet "hello"; //~ ERROR `do yeet` expression is experimental +} diff --git a/src/test/ui/feature-gates/feature-gate-yeet_expr.stderr b/src/test/ui/feature-gates/feature-gate-yeet_expr.stderr new file mode 100644 index 000000000000..8d1b92370fbe --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-yeet_expr.stderr @@ -0,0 +1,21 @@ +error[E0658]: `do yeet` expression is experimental + --> $DIR/feature-gate-yeet_expr.rs:4:5 + | +LL | do yeet + | ^^^^^^^ + | + = note: see issue #96373 for more information + = help: add `#![feature(yeet_expr)]` to the crate attributes to enable + +error[E0658]: `do yeet` expression is experimental + --> $DIR/feature-gate-yeet_expr.rs:8:5 + | +LL | do yeet "hello"; + | ^^^^^^^^^^^^^^^ + | + = note: see issue #96373 for more information + = help: add `#![feature(yeet_expr)]` to the crate attributes to enable + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/try-trait/yeet-for-option.rs b/src/test/ui/try-trait/yeet-for-option.rs new file mode 100644 index 000000000000..753fbc1dee7d --- /dev/null +++ b/src/test/ui/try-trait/yeet-for-option.rs @@ -0,0 +1,11 @@ +// run-pass + +#![feature(yeet_expr)] + +fn always_yeet() -> Option { + do yeet; +} + +fn main() { + assert_eq!(always_yeet(), None); +} diff --git a/src/test/ui/try-trait/yeet-for-result.rs b/src/test/ui/try-trait/yeet-for-result.rs new file mode 100644 index 000000000000..b7b113797cde --- /dev/null +++ b/src/test/ui/try-trait/yeet-for-result.rs @@ -0,0 +1,11 @@ +// run-pass + +#![feature(yeet_expr)] + +fn always_yeet() -> Result { + do yeet "hello"; +} + +fn main() { + assert_eq!(always_yeet(), Err("hello".to_string())); +}