diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 4360fbeb9bbc1..a398fd80119be 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1295,6 +1295,7 @@ impl Expr {
ExprKind::Yield(..) => ExprPrecedence::Yield,
ExprKind::Yeet(..) => ExprPrecedence::Yeet,
ExprKind::FormatArgs(..) => ExprPrecedence::FormatArgs,
+ ExprKind::Become(..) => ExprPrecedence::Become,
ExprKind::Err => ExprPrecedence::Err,
}
}
@@ -1515,6 +1516,11 @@ pub enum ExprKind {
/// with an optional value to be returned.
Yeet(Option
>),
+ /// A tail call return, with the value to be returned.
+ ///
+ /// While `.0` must be a function call, we check this later, after parsing.
+ Become(P),
+
/// Bytes included via `include_bytes!`
/// Added for optimization purposes to avoid the need to escape
/// large binary blobs - should always behave like [`ExprKind::Lit`]
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 66b94d12a32c6..53a9c9a046ea0 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -1457,6 +1457,7 @@ pub fn noop_visit_expr(
ExprKind::Yeet(expr) => {
visit_opt(expr, |expr| vis.visit_expr(expr));
}
+ ExprKind::Become(expr) => vis.visit_expr(expr),
ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm),
ExprKind::FormatArgs(fmt) => vis.visit_format_args(fmt),
ExprKind::OffsetOf(container, fields) => {
diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs
index 35afd5423721d..096077e09bf76 100644
--- a/compiler/rustc_ast/src/util/parser.rs
+++ b/compiler/rustc_ast/src/util/parser.rs
@@ -245,6 +245,7 @@ pub enum ExprPrecedence {
Ret,
Yield,
Yeet,
+ Become,
Range,
@@ -298,7 +299,8 @@ impl ExprPrecedence {
| ExprPrecedence::Continue
| ExprPrecedence::Ret
| ExprPrecedence::Yield
- | ExprPrecedence::Yeet => PREC_JUMP,
+ | ExprPrecedence::Yeet
+ | ExprPrecedence::Become => 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 275692ad5dda7..d9de5b8e197db 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -908,6 +908,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
ExprKind::Yeet(optional_expression) => {
walk_list!(visitor, visit_expr, optional_expression);
}
+ ExprKind::Become(expr) => visitor.visit_expr(expr),
ExprKind::MacCall(mac) => visitor.visit_mac_call(mac),
ExprKind::Paren(subexpression) => visitor.visit_expr(subexpression),
ExprKind::InlineAsm(asm) => visitor.visit_inline_asm(asm),
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index f52797c4f3f1d..225714a13615f 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -275,6 +275,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ExprKind::Ret(e)
}
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
+ ExprKind::Become(sub_expr) => {
+ let sub_expr = self.lower_expr(sub_expr);
+
+ // FIXME(explicit_tail_calls): Use `hir::ExprKind::Become` once we implemented it
+ hir::ExprKind::Ret(Some(sub_expr))
+ }
ExprKind::InlineAsm(asm) => {
hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm))
}
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 2125349909ef1..b0dbc2c23403e 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -555,6 +555,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
gate_all!(dyn_star, "`dyn*` trait objects are experimental");
gate_all!(const_closures, "const closures are experimental");
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
+ gate_all!(explicit_tail_calls, "`become` expression is experimental");
if !visitor.features.negative_bounds {
for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() {
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 87c32ffce1214..609920180a2fa 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -537,6 +537,11 @@ impl<'a> State<'a> {
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
}
}
+ ast::ExprKind::Become(result) => {
+ self.word("become");
+ self.word(" ");
+ self.print_expr_maybe_paren(result, parser::PREC_JUMP);
+ }
ast::ExprKind::InlineAsm(a) => {
// FIXME: This should have its own syntax, distinct from a macro invocation.
self.word("asm!");
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
index b619e80e15f3c..902c1e40c7541 100644
--- a/compiler/rustc_builtin_macros/src/assert/context.rs
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -320,6 +320,7 @@ impl<'cx, 'a> Context<'cx, 'a> {
| ExprKind::Underscore
| ExprKind::While(_, _, _)
| ExprKind::Yeet(_)
+ | ExprKind::Become(_)
| ExprKind::Yield(_) => {}
}
}
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 4c53f9d8369fd..20a33e7fb36e3 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -395,6 +395,8 @@ declare_features! (
(active, exclusive_range_pattern, "1.11.0", Some(37854), None),
/// Allows exhaustive pattern matching on types that contain uninhabited types.
(active, exhaustive_patterns, "1.13.0", Some(51085), None),
+ /// Allows explicit tail calls via `become` expression.
+ (incomplete, explicit_tail_calls, "CURRENT_RUSTC_VERSION", Some(112788), None),
/// Allows using `efiapi`, `sysv64` and `win64` as calling convention
/// for functions with varargs.
(active, extended_varargs_abi_support, "1.65.0", Some(100189), None),
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index f00bc54589a7f..88c6cc1ae7072 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1430,6 +1430,8 @@ impl<'a> Parser<'a> {
self.parse_expr_yield()
} else if self.is_do_yeet() {
self.parse_expr_yeet()
+ } else if self.eat_keyword(kw::Become) {
+ self.parse_expr_become()
} else if self.check_keyword(kw::Let) {
self.parse_expr_let()
} else if self.eat_keyword(kw::Underscore) {
@@ -1746,6 +1748,16 @@ impl<'a> Parser<'a> {
self.maybe_recover_from_bad_qpath(expr)
}
+ /// Parse `"become" expr`, with `"become"` token already eaten.
+ fn parse_expr_become(&mut self) -> PResult<'a, P> {
+ let lo = self.prev_token.span;
+ let kind = ExprKind::Become(self.parse_expr()?);
+ let span = lo.to(self.prev_token.span);
+ self.sess.gated_spans.gate(sym::explicit_tail_calls, span);
+ let expr = self.mk_expr(span, kind);
+ self.maybe_recover_from_bad_qpath(expr)
+ }
+
/// 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
diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs
index dc5e454074ded..90809270118d0 100644
--- a/compiler/rustc_passes/src/hir_stats.rs
+++ b/compiler/rustc_passes/src/hir_stats.rs
@@ -569,7 +569,8 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign,
AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
- InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err
+ InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet,
+ Become, IncludedBytes, Err
]
);
ast_visit::walk_expr(self, e)
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index bd39cbf17cec1..3282a82d9774b 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -686,6 +686,7 @@ symbols! {
expf32,
expf64,
explicit_generic_args_with_impl_trait,
+ explicit_tail_calls,
export_name,
expr,
extended_key_value_attributes,
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index f477524eec5cc..a87d58110b0c1 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -211,6 +211,7 @@ impl<'a> Sugg<'a> {
| ast::ExprKind::Path(..)
| ast::ExprKind::Repeat(..)
| ast::ExprKind::Ret(..)
+ | ast::ExprKind::Become(..)
| ast::ExprKind::Yeet(..)
| ast::ExprKind::FormatArgs(..)
| ast::ExprKind::Struct(..)
diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs
index 5dc628adb0c6f..44aaddd8cb27f 100644
--- a/src/tools/rustfmt/src/expr.rs
+++ b/src/tools/rustfmt/src/expr.rs
@@ -232,6 +232,7 @@ pub(crate) fn format_expr(
ast::ExprKind::Ret(Some(ref expr)) => {
rewrite_unary_prefix(context, "return ", &**expr, shape)
}
+ ast::ExprKind::Become(ref expr) => rewrite_unary_prefix(context, "become ", &**expr, shape),
ast::ExprKind::Yeet(None) => Some("do yeet".to_owned()),
ast::ExprKind::Yeet(Some(ref expr)) => {
rewrite_unary_prefix(context, "do yeet ", &**expr, shape)
diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs
index ca1716574071b..890a05b8c8259 100644
--- a/src/tools/rustfmt/src/utils.rs
+++ b/src/tools/rustfmt/src/utils.rs
@@ -505,6 +505,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
| ast::ExprKind::Range(..)
| ast::ExprKind::Repeat(..)
| ast::ExprKind::Ret(..)
+ | ast::ExprKind::Become(..)
| ast::ExprKind::Yeet(..)
| ast::ExprKind::Tup(..)
| ast::ExprKind::Type(..)
diff --git a/tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs
new file mode 100644
index 0000000000000..856a7f393289a
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs
@@ -0,0 +1,9 @@
+pub fn you() -> T {
+ become bottom(); //~ error: `become` expression is experimental
+}
+
+pub fn bottom() -> T {
+ become you(); //~ error: `become` expression is experimental
+}
+
+fn main() {}
diff --git a/tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr
new file mode 100644
index 0000000000000..b58da19c1745f
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr
@@ -0,0 +1,21 @@
+error[E0658]: `become` expression is experimental
+ --> $DIR/feature-gate-explicit_tail_calls.rs:2:5
+ |
+LL | become bottom();
+ | ^^^^^^^^^^^^^^^
+ |
+ = note: see issue #112788 for more information
+ = help: add `#![feature(explicit_tail_calls)]` to the crate attributes to enable
+
+error[E0658]: `become` expression is experimental
+ --> $DIR/feature-gate-explicit_tail_calls.rs:6:5
+ |
+LL | become you();
+ | ^^^^^^^^^^^^
+ |
+ = note: see issue #112788 for more information
+ = help: add `#![feature(explicit_tail_calls)]` to the crate attributes to enable
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0658`.