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..29972dd76eb0f 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -275,6 +275,10 @@ 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);
+ hir::ExprKind::Become(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_borrowck/src/invalidation.rs b/compiler/rustc_borrowck/src/invalidation.rs
index b2ff25ecb96f4..0ff5c623e8601 100644
--- a/compiler/rustc_borrowck/src/invalidation.rs
+++ b/compiler/rustc_borrowck/src/invalidation.rs
@@ -137,6 +137,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
}
self.mutate_place(location, *destination, Deep);
}
+ TerminatorKind::TailCall { func, args, .. } => {
+ self.consume_operand(location, func);
+ for arg in args {
+ self.consume_operand(location, arg);
+ }
+ }
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
self.consume_operand(location, cond);
use rustc_middle::mir::AssertKind;
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 99a988f2c629e..9ba97041c8add 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -719,6 +719,12 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
}
self.mutate_place(loc, (*destination, span), Deep, flow_state);
}
+ TerminatorKind::TailCall { func, args, .. } => {
+ self.consume_operand(loc, (func, span), flow_state);
+ for arg in args {
+ self.consume_operand(loc, (arg, span), flow_state);
+ }
+ }
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
self.consume_operand(loc, (cond, span), flow_state);
use rustc_middle::mir::AssertKind;
@@ -803,7 +809,11 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
}
}
- TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
+ // FIXME(explicit_tail_calls): do we need to do something similar before the tail call?
+ TerminatorKind::Resume
+ | TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
+ | TerminatorKind::GeneratorDrop => {
// Returning from the function implicitly kills storage for all locals and statics.
// Often, the storage will already have been killed by an explicit
// StorageDead, but we don't always emit those (notably on unwind paths),
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 6697e1aff7dd0..b26946e96583f 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -1370,7 +1370,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
}
// FIXME: check the values
}
- TerminatorKind::Call { func, args, destination, from_hir_call, target, .. } => {
+ TerminatorKind::Call { func, args, .. }
+ | TerminatorKind::TailCall { func, args, .. } => {
+ let from_hir_call = matches!(
+ &term.kind,
+ TerminatorKind::Call { from_hir_call: true, .. }
+ // tail calls don't support overloaded operators
+ | TerminatorKind::TailCall { .. }
+ );
+
self.check_operand(func, term_location);
for arg in args {
self.check_operand(arg, term_location);
@@ -1426,7 +1434,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
ConstraintCategory::Boring,
);
let sig = self.normalize(sig, term_location);
- self.check_call_dest(body, term, &sig, *destination, *target, term_location);
+
+ if let TerminatorKind::Call { destination, target, .. } = term.kind {
+ self.check_call_dest(body, term, &sig, destination, target, term_location);
+ }
// The ordinary liveness rules will ensure that all
// regions in the type of the callee are live here. We
@@ -1444,7 +1455,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
.add_element(region_vid, term_location);
}
- self.check_call_inputs(body, term, &sig, args, term_location, *from_hir_call);
+ self.check_call_inputs(body, term, &sig, args, term_location, from_hir_call);
}
TerminatorKind::Assert { cond, msg, .. } => {
self.check_operand(cond, term_location);
@@ -1637,6 +1648,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
span_mirbug!(self, block_data, "return on cleanup block")
}
}
+ TerminatorKind::TailCall { .. } => {
+ if is_cleanup {
+ span_mirbug!(self, block_data, "tailcall on cleanup block")
+ }
+ }
TerminatorKind::GeneratorDrop { .. } => {
if is_cleanup {
span_mirbug!(self, block_data, "generator_drop in cleanup block")
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_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs
index fcfa0b862d4b5..22ddc6d9d4353 100644
--- a/compiler/rustc_codegen_cranelift/src/base.rs
+++ b/compiler/rustc_codegen_cranelift/src/base.rs
@@ -433,6 +433,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
)
});
}
+ // FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls
+ TerminatorKind::TailCall { fn_span, .. } => span_bug!(
+ *fn_span,
+ "tail calls are not yet supported in `rustc_codegen_cranelift` backend"
+ ),
TerminatorKind::InlineAsm {
template,
operands,
diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs
index 77af561a58724..dea61355e2adc 100644
--- a/compiler/rustc_codegen_cranelift/src/constant.rs
+++ b/compiler/rustc_codegen_cranelift/src/constant.rs
@@ -566,6 +566,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
{
return None;
}
+ TerminatorKind::TailCall { .. } => return None,
TerminatorKind::Call { .. } => {}
}
}
diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs
index f9ea0f004564b..46e328a626d2e 100644
--- a/compiler/rustc_codegen_gcc/src/builder.rs
+++ b/compiler/rustc_codegen_gcc/src/builder.rs
@@ -1378,6 +1378,18 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
call
}
+ fn tail_call(
+ &mut self,
+ _typ: Type<'gcc>,
+ _fn_attrs: Option<&CodegenFnAttrs>,
+ _fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+ _func: RValue<'gcc>,
+ _: &[RValue<'gcc>],
+ _: Option<&Funclet>
+ ) {
+ bug!("tail calls are not yet supported in `rustc_codegen_gcc` backend")
+ }
+
fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> {
// FIXME(antoyo): this does not zero-extend.
if value.get_type().is_bool() && dest_typ.is_i8(&self.cx) {
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index b4aa001547c4c..f67901c8ff159 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -14,6 +14,7 @@ use rustc_codegen_ssa::traits::*;
use rustc_codegen_ssa::MemFlags;
use rustc_data_structures::small_c_str::SmallCStr;
use rustc_hir::def_id::DefId;
+use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::ty::layout::{
FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, TyAndLayout,
@@ -1217,6 +1218,29 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
call
}
+ fn tail_call(
+ &mut self,
+ llty: Self::Type,
+ fn_attrs: Option<&CodegenFnAttrs>,
+ fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+ llfn: Self::Value,
+ args: &[Self::Value],
+ funclet: Option<&Self::Funclet>,
+ ) {
+ let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet);
+
+ // Depending on the pass mode we neet to generate different return instructions for llvm
+ match &fn_abi.ret.mode {
+ abi::call::PassMode::Ignore | abi::call::PassMode::Indirect { .. } => self.ret_void(),
+ abi::call::PassMode::Direct(_) | abi::call::PassMode::Pair { .. } => self.ret(call),
+ mode @ abi::call::PassMode::Cast(..) => {
+ bug!("Encountered `PassMode::{mode:?}` during codegen")
+ }
+ }
+
+ unsafe { llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail) };
+ }
+
fn zext(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
unsafe { llvm::LLVMBuildZExt(self.llbuilder, val, dest_ty, UNNAMED) }
}
diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
index 6ef3418cc5f77..32ddc228b8d7c 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
@@ -585,6 +585,17 @@ pub enum ThreadLocalMode {
LocalExec,
}
+/// LLVMRustTailCallKind
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub enum TailCallKind {
+ None,
+ Tail,
+ MustTail,
+ NoTail,
+ Last,
+}
+
/// LLVMRustChecksumKind
#[derive(Copy, Clone)]
#[repr(C)]
@@ -1195,6 +1206,7 @@ extern "C" {
NameLen: size_t,
) -> Option<&Value>;
pub fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool);
+ pub fn LLVMRustSetTailCallKind(CallInst: &Value, TKC: TailCallKind);
// Operations on attributes
pub fn LLVMRustCreateAttrNoValue(C: &Context, attr: AttributeKind) -> &Attribute;
diff --git a/compiler/rustc_codegen_ssa/src/mir/analyze.rs b/compiler/rustc_codegen_ssa/src/mir/analyze.rs
index 22c1f05974ddd..b377baba4d939 100644
--- a/compiler/rustc_codegen_ssa/src/mir/analyze.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/analyze.rs
@@ -292,6 +292,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec { /* nothing to do */ }
TerminatorKind::Call { unwind, .. }
| TerminatorKind::InlineAsm { unwind, .. }
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index e0cb26d3ba866..3fc860592930f 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -239,6 +239,31 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
}
}
+ /// **Tail** call `fn_ptr` of `fn_abi` with the arguments `llargs`.
+ fn do_tail_call>(
+ &self,
+ fx: &mut FunctionCx<'a, 'tcx, Bx>,
+ bx: &mut Bx,
+ fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>,
+ fn_ptr: Bx::Value,
+ llargs: &[Bx::Value],
+ copied_constant_arguments: &[PlaceRef<'tcx, ::Value>],
+ ) {
+ let fn_ty = bx.fn_decl_backend_type(&fn_abi);
+
+ let fn_attrs = if bx.tcx().def_kind(fx.instance.def_id()).has_codegen_attrs() {
+ Some(bx.tcx().codegen_fn_attrs(fx.instance.def_id()))
+ } else {
+ None
+ };
+
+ bx.tail_call(fn_ty, fn_attrs, fn_abi, fn_ptr, &llargs, self.funclet(fx));
+
+ for tmp in copied_constant_arguments {
+ bx.lifetime_end(tmp.llval, tmp.layout.size);
+ }
+ }
+
/// Generates inline assembly with optional `destination` and `unwind`.
fn do_inlineasm>(
&self,
@@ -1077,6 +1102,242 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
)
}
+ fn codegen_tail_call_terminator(
+ &mut self,
+ helper: TerminatorCodegenHelper<'tcx>,
+ bx: &mut Bx,
+ terminator: &mir::Terminator<'tcx>,
+ func: &mir::Operand<'tcx>,
+ args: &[mir::Operand<'tcx>],
+ fn_span: Span,
+ ) {
+ let source_info = terminator.source_info;
+ let span = source_info.span;
+
+ // Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar.
+ let callee = self.codegen_operand(bx, func);
+
+ let (instance, mut llfn) = match *callee.layout.ty.kind() {
+ ty::FnDef(def_id, substs) => (
+ Some(
+ ty::Instance::expect_resolve(
+ bx.tcx(),
+ ty::ParamEnv::reveal_all(),
+ def_id,
+ substs,
+ )
+ .polymorphize(bx.tcx()),
+ ),
+ None,
+ ),
+ ty::FnPtr(_) => (None, Some(callee.immediate())),
+ _ => bug!("{} is not callable", callee.layout.ty),
+ };
+ let def = instance.map(|i| i.def);
+
+ if let Some(ty::InstanceDef::DropGlue(..)) = def {
+ bug!("tail-calling drop glue should not be possible");
+ }
+
+ // FIXME(eddyb) avoid computing this if possible, when `instance` is
+ // available - right now `sig` is only needed for getting the `abi`
+ // and figuring out how many extra args were passed to a C-variadic `fn`.
+ let sig = callee.layout.ty.fn_sig(bx.tcx());
+ let abi = sig.abi();
+
+ if let Some(ty::InstanceDef::Intrinsic(def_id)) = def {
+ span_bug!(
+ fn_span,
+ "Attempting to tail-call `{}` intrinsic",
+ bx.tcx().item_name(def_id)
+ );
+ };
+
+ let extra_args = &args[sig.inputs().skip_binder().len()..];
+ let extra_args = bx.tcx().mk_type_list_from_iter(extra_args.iter().map(|op_arg| {
+ let op_ty = op_arg.ty(self.mir, bx.tcx());
+ self.monomorphize(op_ty)
+ }));
+
+ let fn_abi = match instance {
+ Some(instance) => bx.fn_abi_of_instance(instance, extra_args),
+ None => bx.fn_abi_of_fn_ptr(sig, extra_args),
+ };
+
+ // The arguments we'll be passing. Plus one to account for outptr, if used.
+ let arg_count = fn_abi.args.len() + fn_abi.ret.is_indirect() as usize;
+ let mut llargs = Vec::with_capacity(arg_count);
+
+ if fn_abi.ret.is_indirect() {
+ let LocalRef::Place(place) = self.locals[mir::RETURN_PLACE]
+ else { bug!() };
+
+ llargs.push(place.llval);
+ }
+
+ // Split the rust-call tupled arguments off.
+ let (first_args, untuple) = if abi == Abi::RustCall && !args.is_empty() {
+ let (tup, args) = args.split_last().unwrap();
+ (args, Some(tup))
+ } else {
+ (args, None)
+ };
+
+ // FIXME(explicit_tail_calls): refactor this into a separate function, deduplicate with `Call`
+ let mut copied_constant_arguments = vec![];
+ 'make_args: for (i, arg) in first_args.iter().enumerate() {
+ let mut op = self.codegen_operand(bx, arg);
+
+ if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) {
+ match op.val {
+ Pair(data_ptr, meta) => {
+ // In the case of Rc, we need to explicitly pass a
+ // *mut RcBox with a Scalar (not ScalarPair) ABI. This is a hack
+ // that is understood elsewhere in the compiler as a method on
+ // `dyn Trait`.
+ // To get a `*mut RcBox`, we just keep unwrapping newtypes until
+ // we get a value of a built-in pointer type.
+ //
+ // This is also relevant for `Pin<&mut Self>`, where we need to peel the `Pin`.
+ 'descend_newtypes: while !op.layout.ty.is_unsafe_ptr()
+ && !op.layout.ty.is_ref()
+ {
+ for i in 0..op.layout.fields.count() {
+ let field = op.extract_field(bx, i);
+ if !field.layout.is_zst() {
+ // we found the one non-zero-sized field that is allowed
+ // now find *its* non-zero-sized field, or stop if it's a
+ // pointer
+ op = field;
+ continue 'descend_newtypes;
+ }
+ }
+
+ span_bug!(span, "receiver has no non-zero-sized fields {:?}", op);
+ }
+
+ // now that we have `*dyn Trait` or `&dyn Trait`, split it up into its
+ // data pointer and vtable. Look up the method in the vtable, and pass
+ // the data pointer as the first argument
+ llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
+ bx,
+ meta,
+ op.layout.ty,
+ &fn_abi,
+ ));
+ llargs.push(data_ptr);
+ continue 'make_args;
+ }
+ Ref(data_ptr, Some(meta), _) => {
+ // by-value dynamic dispatch
+ llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
+ bx,
+ meta,
+ op.layout.ty,
+ &fn_abi,
+ ));
+ llargs.push(data_ptr);
+ continue;
+ }
+ Immediate(_) => {
+ // See comment above explaining why we peel these newtypes
+ 'descend_newtypes: while !op.layout.ty.is_unsafe_ptr()
+ && !op.layout.ty.is_ref()
+ {
+ for i in 0..op.layout.fields.count() {
+ let field = op.extract_field(bx, i);
+ if !field.layout.is_zst() {
+ // we found the one non-zero-sized field that is allowed
+ // now find *its* non-zero-sized field, or stop if it's a
+ // pointer
+ op = field;
+ continue 'descend_newtypes;
+ }
+ }
+
+ span_bug!(span, "receiver has no non-zero-sized fields {:?}", op);
+ }
+
+ // Make sure that we've actually unwrapped the rcvr down
+ // to a pointer or ref to `dyn* Trait`.
+ if !op.layout.ty.builtin_deref(true).unwrap().ty.is_dyn_star() {
+ span_bug!(span, "can't codegen a virtual call on {:#?}", op);
+ }
+ let place = op.deref(bx.cx());
+ let data_ptr = place.project_field(bx, 0);
+ let meta_ptr = place.project_field(bx, 1);
+ let meta = bx.load_operand(meta_ptr);
+ llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
+ bx,
+ meta.immediate(),
+ op.layout.ty,
+ &fn_abi,
+ ));
+ llargs.push(data_ptr.llval);
+ continue;
+ }
+ _ => {
+ span_bug!(span, "can't codegen a virtual call on {:#?}", op);
+ }
+ }
+ }
+
+ // The callee needs to own the argument memory if we pass it
+ // by-ref, so make a local copy of non-immediate constants.
+ match (arg, op.val) {
+ (&mir::Operand::Copy(_), Ref(_, None, _))
+ | (&mir::Operand::Constant(_), Ref(_, None, _)) => {
+ let tmp = PlaceRef::alloca(bx, op.layout);
+ bx.lifetime_start(tmp.llval, tmp.layout.size);
+ op.val.store(bx, tmp);
+ op.val = Ref(tmp.llval, None, tmp.align);
+ copied_constant_arguments.push(tmp);
+ }
+ _ => {}
+ }
+
+ self.codegen_argument(bx, op, &mut llargs, &fn_abi.args[i]);
+ }
+ let num_untupled = untuple.map(|tup| {
+ self.codegen_arguments_untupled(bx, tup, &mut llargs, &fn_abi.args[first_args.len()..])
+ });
+
+ let needs_location =
+ instance.map_or(false, |i| i.def.requires_caller_location(self.cx.tcx()));
+ if needs_location {
+ let mir_args = if let Some(num_untupled) = num_untupled {
+ first_args.len() + num_untupled
+ } else {
+ args.len()
+ };
+ assert_eq!(
+ fn_abi.args.len(),
+ mir_args + 1,
+ "#[track_caller] fn's must have 1 more argument in their ABI than in their MIR: {:?} {:?} {:?}",
+ instance,
+ fn_span,
+ fn_abi,
+ );
+ let location =
+ self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info });
+ debug!(
+ "codegen_tail_call_terminator({:?}): location={:?} (fn_span {:?})",
+ terminator, location, fn_span
+ );
+
+ let last_arg = fn_abi.args.last().unwrap();
+ self.codegen_argument(bx, location, &mut llargs, last_arg);
+ }
+
+ let fn_ptr = match (instance, llfn) {
+ (Some(instance), None) => bx.get_fn_addr(instance),
+ (_, Some(llfn)) => llfn,
+ _ => span_bug!(span, "no instance or llfn for tail-call"),
+ };
+
+ helper.do_tail_call(self, bx, fn_abi, fn_ptr, &llargs, &copied_constant_arguments);
+ }
+
fn codegen_asm_terminator(
&mut self,
helper: TerminatorCodegenHelper<'tcx>,
@@ -1294,6 +1555,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
fn_span,
mergeable_succ(),
),
+
+ mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => {
+ self.codegen_tail_call_terminator(helper, bx, terminator, func, args, fn_span);
+ MergingSucc::False
+ }
+
mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } => {
bug!("generator ops in codegen")
}
diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs
index 853c6934c2c24..df060469d05f1 100644
--- a/compiler/rustc_codegen_ssa/src/traits/builder.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs
@@ -330,6 +330,17 @@ pub trait BuilderMethods<'a, 'tcx>:
args: &[Self::Value],
funclet: Option<&Self::Funclet>,
) -> Self::Value;
+
+ fn tail_call(
+ &mut self,
+ llty: Self::Type,
+ fn_attrs: Option<&CodegenFnAttrs>,
+ fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+ llfn: Self::Value,
+ args: &[Self::Value],
+ funclet: Option<&Self::Funclet>,
+ );
+
fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
fn do_not_inline(&mut self, llret: Self::Value);
diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs
index 7269ff8d53cdf..3520934967477 100644
--- a/compiler/rustc_const_eval/src/interpret/terminator.rs
+++ b/compiler/rustc_const_eval/src/interpret/terminator.rs
@@ -115,6 +115,75 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
+ TailCall { ref func, ref args, fn_span: _ } => {
+ // FIXME: a lot of code here is duplicated with normal calls, can we refactor this?
+ let old_frame_idx = self.frame_idx();
+ let func = self.eval_operand(func, None)?;
+ let args = self.eval_operands(args)?;
+
+ let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx);
+ let fn_sig =
+ self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder);
+ let extra_args = &args[fn_sig.inputs().len()..];
+ let extra_args =
+ self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout.ty));
+
+ let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() {
+ ty::FnPtr(_sig) => {
+ let fn_ptr = self.read_pointer(&func)?;
+ let fn_val = self.get_ptr_fn(fn_ptr)?;
+ (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false)
+ }
+ ty::FnDef(def_id, substs) => {
+ let instance = self.resolve(def_id, substs)?;
+ (
+ FnVal::Instance(instance),
+ self.fn_abi_of_instance(instance, extra_args)?,
+ instance.def.requires_caller_location(*self.tcx),
+ )
+ }
+ _ => span_bug!(
+ terminator.source_info.span,
+ "invalid callee of type {:?}",
+ func.layout.ty
+ ),
+ };
+
+ // FIXME(explicit_tail_calls): maybe we need the drop here?...
+
+ // This is the "canonical" implementation of tails calls,
+ // a pop of the current stack frame, followed by a normal call
+ // which pushes a new stack frame, with the return address from
+ // the popped stack frame.
+ //
+ // Note that we can't use `pop_stack_frame` as it "executes"
+ // the goto to the return block, but we don't want to,
+ // only the tail called function should return to the current
+ // return block.
+ let Some(prev_frame) = self.stack_mut().pop()
+ else { span_bug!(terminator.source_info.span, "empty stack while evaluating this tail call") };
+
+ let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block
+ else { span_bug!(terminator.source_info.span, "tail call with the root stack frame") };
+
+ self.eval_fn_call(
+ fn_val,
+ (fn_sig.abi, fn_abi),
+ &args,
+ with_caller_location,
+ &prev_frame.return_place,
+ ret,
+ unwind,
+ )?;
+
+ if self.frame_idx() != old_frame_idx {
+ span_bug!(
+ terminator.source_info.span,
+ "evaluating this tail call pushed a new stack frame"
+ );
+ }
+ }
+
Drop { place, target, unwind, replace: _ } => {
let frame = self.frame();
let ty = place.ty(&frame.body.local_decls, *self.tcx).ty;
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index 57d939747aab3..e3ef4a3e11521 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -129,6 +129,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
ccx: &'mir ConstCx<'mir, 'tcx>,
tainted_by_errors: Option,
) -> ConstQualifs {
+ // FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything
+
// Find the `Return` terminator if one exists.
//
// If no `Return` terminator exists, this MIR is divergent. Just return the conservative
@@ -702,7 +704,15 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
self.super_terminator(terminator, location);
match &terminator.kind {
- TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => {
+ TerminatorKind::Call { func, args, fn_span, .. }
+ | TerminatorKind::TailCall { func, args, fn_span, .. } => {
+ let from_hir_call = matches!(
+ &terminator.kind,
+ TerminatorKind::Call { from_hir_call: true, .. }
+ // tail calls don't support overloaded operators
+ | TerminatorKind::TailCall { .. }
+ );
+
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
let caller = self.def_id();
@@ -755,7 +765,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ from_hir_call,
feature: Some(sym::const_trait_impl),
});
return;
@@ -788,7 +798,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ from_hir_call,
feature: None,
});
@@ -814,7 +824,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ from_hir_call,
feature: None,
});
return;
@@ -857,7 +867,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ from_hir_call,
feature: None,
});
return;
@@ -917,7 +927,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ from_hir_call,
feature: None,
});
return;
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs
index 1f1640fd80ae6..8508c8e24f826 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs
@@ -106,6 +106,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
mir::TerminatorKind::Terminate
| mir::TerminatorKind::Call { .. }
+ | mir::TerminatorKind::TailCall { .. }
| mir::TerminatorKind::Assert { .. }
| mir::TerminatorKind::FalseEdge { .. }
| mir::TerminatorKind::FalseUnwind { .. }
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index 3c350e25ba6ec..744aba0a368be 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -976,7 +976,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.check_edge(location, *target, EdgeKind::Normal);
self.check_unwind_edge(location, *unwind);
}
- TerminatorKind::Call { func, args, destination, target, unwind, .. } => {
+
+ TerminatorKind::Call { func, args, .. }
+ | TerminatorKind::TailCall { func, args, .. } => {
let func_ty = func.ty(&self.body.local_decls, self.tcx);
match func_ty.kind() {
ty::FnPtr(..) | ty::FnDef(..) => {}
@@ -985,16 +987,21 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
format!("encountered non-callable type {} in `Call` terminator", func_ty),
),
}
- if let Some(target) = target {
- self.check_edge(location, *target, EdgeKind::Normal);
+
+ if let TerminatorKind::Call { target, unwind, .. } = terminator.kind {
+ if let Some(target) = target {
+ self.check_edge(location, target, EdgeKind::Normal);
+ }
+ self.check_unwind_edge(location, unwind);
}
- self.check_unwind_edge(location, *unwind);
// The call destination place and Operand::Move place used as an argument might be
// passed by a reference to the callee. Consequently they must be non-overlapping.
// Currently this simply checks for duplicate places.
self.place_cache.clear();
- self.place_cache.push(destination.as_ref());
+ if let TerminatorKind::Call { destination, .. } = terminator.kind {
+ self.place_cache.push(destination.as_ref());
+ }
for arg in args {
if let Operand::Move(place) = arg {
self.place_cache.push(place.as_ref());
@@ -1008,7 +1015,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.fail(
location,
format!(
- "encountered overlapping memory in `Call` terminator: {:?}",
+ "encountered overlapping memory in `{}` terminator: {:?}",
+ terminator.kind.name(),
terminator.kind,
),
);
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 4c53f9d8369fd..af7cf8660be74 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -154,6 +154,8 @@ declare_features! (
(active, compiler_builtins, "1.13.0", None, None),
/// Allows writing custom MIR
(active, custom_mir, "1.65.0", None, None),
+ /// Allows `become` expression aka explicit tail calls (internal because no TI yet).
+ (active, explicit_tail_calls, "CURRENT_RUSTC_VERSION", None, None),
/// Outputs useful `assert!` messages
(active, generic_assert, "1.63.0", None, None),
/// Allows using the `rust-intrinsic`'s "ABI".
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 70fc66947df99..df495d5c42eaa 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1719,6 +1719,7 @@ impl Expr<'_> {
ExprKind::Break(..) => ExprPrecedence::Break,
ExprKind::Continue(..) => ExprPrecedence::Continue,
ExprKind::Ret(..) => ExprPrecedence::Ret,
+ ExprKind::Become(..) => ExprPrecedence::Become,
ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm,
ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf,
ExprKind::Struct(..) => ExprPrecedence::Struct,
@@ -1776,6 +1777,7 @@ impl Expr<'_> {
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
+ | ExprKind::Become(..)
| ExprKind::Let(..)
| ExprKind::Loop(..)
| ExprKind::Assign(..)
@@ -1866,6 +1868,7 @@ impl Expr<'_> {
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
+ | ExprKind::Become(..)
| ExprKind::Let(..)
| ExprKind::Loop(..)
| ExprKind::Assign(..)
@@ -2025,6 +2028,8 @@ pub enum ExprKind<'hir> {
Continue(Destination),
/// A `return`, with an optional value to be returned.
Ret(Option<&'hir Expr<'hir>>),
+ /// A `become`, with the value to be returned.
+ Become(&'hir Expr<'hir>),
/// Inline assembly (from `asm!`), with its outputs and inputs.
InlineAsm(&'hir InlineAsm<'hir>),
diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs
index f84c814bd9278..1886a91bda838 100644
--- a/compiler/rustc_hir/src/intravisit.rs
+++ b/compiler/rustc_hir/src/intravisit.rs
@@ -791,6 +791,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
ExprKind::Ret(ref optional_expression) => {
walk_list!(visitor, visit_expr, optional_expression);
}
+ ExprKind::Become(ref expr) => visitor.visit_expr(expr),
ExprKind::InlineAsm(ref asm) => {
visitor.visit_inline_asm(asm, expression.hir_id);
}
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index ced46fe426c47..a699cd6c942eb 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -1554,6 +1554,11 @@ impl<'a> State<'a> {
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
}
}
+ hir::ExprKind::Become(result) => {
+ self.word("become");
+ self.word(" ");
+ self.print_expr_maybe_paren(result, parser::PREC_JUMP);
+ }
hir::ExprKind::InlineAsm(asm) => {
self.word("asm!");
self.print_inline_asm(asm);
diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl
index c1c58db57648c..82a9f1cb5cec1 100644
--- a/compiler/rustc_hir_typeck/messages.ftl
+++ b/compiler/rustc_hir_typeck/messages.ftl
@@ -78,8 +78,8 @@ hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang
hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters
hir_typeck_return_stmt_outside_of_fn_body =
- return statement outside of function body
- .encl_body_label = the return is part of this body...
+ `{$statement_kind}` statement outside of function body
+ .encl_body_label = the `{$statement_kind}` is part of this body...
.encl_fn_label = ...not the enclosing function body
hir_typeck_struct_expr_non_exhaustive =
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index 6b4168d89446f..bb4143cf9f626 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -31,6 +31,8 @@ pub struct ReturnStmtOutsideOfFnBody {
pub encl_body_span: Option,
#[label(hir_typeck_encl_fn_label)]
pub encl_fn_span: Option,
+ // "return" or "become"
+ pub statement_kind: String,
}
#[derive(Diagnostic)]
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 3f6847be91b65..0334b88bcad1b 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -324,6 +324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr),
+ ExprKind::Become(call) => self.check_expr_become(call, expr),
ExprKind::Let(let_expr) => self.check_expr_let(let_expr),
ExprKind::Loop(body, _, source, _) => {
self.check_expr_loop(body, source, expected, expr)
@@ -735,47 +736,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
if self.ret_coercion.is_none() {
- let mut err = ReturnStmtOutsideOfFnBody {
- span: expr.span,
- encl_body_span: None,
- encl_fn_span: None,
- };
-
- let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id);
-
- if let Some(hir::Node::Item(hir::Item {
- kind: hir::ItemKind::Fn(..),
- span: encl_fn_span,
- ..
- }))
- | Some(hir::Node::TraitItem(hir::TraitItem {
- kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)),
- span: encl_fn_span,
- ..
- }))
- | Some(hir::Node::ImplItem(hir::ImplItem {
- kind: hir::ImplItemKind::Fn(..),
- span: encl_fn_span,
- ..
- })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id)
- {
- // We are inside a function body, so reporting "return statement
- // outside of function body" needs an explanation.
-
- let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id);
-
- // If this didn't hold, we would not have to report an error in
- // the first place.
- assert_ne!(encl_item_id.def_id, encl_body_owner_id);
-
- let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id);
- let encl_body = self.tcx.hir().body(encl_body_id);
-
- err.encl_body_span = Some(encl_body.value.span);
- err.encl_fn_span = Some(*encl_fn_span);
- }
-
- self.tcx.sess.emit_err(err);
+ self.emit_return_outside_of_fn_body(expr, "return");
if let Some(e) = expr_opt {
// We still have to type-check `e` (issue #86188), but calling
@@ -815,6 +776,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx.types.never
}
+ fn check_expr_become(
+ &self,
+ call: &'tcx hir::Expr<'tcx>,
+ expr: &'tcx hir::Expr<'tcx>,
+ ) -> Ty<'tcx> {
+ match &self.ret_coercion {
+ Some(ret_coercion) => {
+ let ret_ty = ret_coercion.borrow().expected_ty();
+ let call_expr_ty = self.check_expr_with_hint(call, ret_ty);
+
+ // N.B. don't coerce here, as tail calls can't support most/all coercions
+ // FIXME(explicit_tail_calls): add a diagnostic note that `become` doesn't allow coercions
+ self.demand_suptype(expr.span, ret_ty, call_expr_ty);
+ }
+ None => {
+ self.emit_return_outside_of_fn_body(expr, "become");
+
+ // Fallback to simply type checking `call` without hint/demanding the right types.
+ // Best effort to highlight more errors.
+ self.check_expr(call);
+ }
+ }
+
+ self.tcx.types.never
+ }
+
+ /// Check an expression that _is being returned_.
+ /// For example, this is called with `return_expr: $expr` when `return $expr`
+ /// is encountered.
+ ///
+ /// Note that this function must only be called in function bodies.
+ ///
/// `explicit_return` is `true` if we're checking an explicit `return expr`,
/// and `false` if we're checking a trailing expression.
pub(super) fn check_return_expr(
@@ -831,10 +824,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let mut span = return_expr.span;
// Use the span of the trailing expression for our cause,
// not the span of the entire function
- if !explicit_return {
- if let ExprKind::Block(body, _) = return_expr.kind && let Some(last_expr) = body.expr {
+ if !explicit_return
+ && let ExprKind::Block(body, _) = return_expr.kind
+ && let Some(last_expr) = body.expr
+ {
span = last_expr.span;
- }
}
ret_coercion.borrow_mut().coerce(
self,
@@ -854,6 +848,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
+ /// Emit an error because `return` or `become` is used outside of a function body.
+ ///
+ /// `expr` is the `return` (`become`) "statement", `kind` is the kind of the return
+ /// either "return" or "become"
+ ///
+ /// FIXME(explicit_tail_calls): come up with a better way than passing strings lol
+ fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: &'static str) {
+ let mut err = ReturnStmtOutsideOfFnBody {
+ span: expr.span,
+ encl_body_span: None,
+ encl_fn_span: None,
+ statement_kind: kind.to_string(),
+ };
+
+ let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id);
+
+ if let Some(hir::Node::Item(hir::Item {
+ kind: hir::ItemKind::Fn(..),
+ span: encl_fn_span,
+ ..
+ }))
+ | Some(hir::Node::TraitItem(hir::TraitItem {
+ kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)),
+ span: encl_fn_span,
+ ..
+ }))
+ | Some(hir::Node::ImplItem(hir::ImplItem {
+ kind: hir::ImplItemKind::Fn(..),
+ span: encl_fn_span,
+ ..
+ })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id)
+ {
+ // We are inside a function body, so reporting "return statement
+ // outside of function body" needs an explanation.
+
+ let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id);
+
+ // If this didn't hold, we would not have to report an error in
+ // the first place.
+ assert_ne!(encl_item_id.def_id, encl_body_owner_id);
+
+ let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id);
+ let encl_body = self.tcx.hir().body(encl_body_id);
+
+ err.encl_body_span = Some(encl_body.value.span);
+ err.encl_fn_span = Some(*encl_fn_span);
+ }
+
+ self.tcx.sess.emit_err(err);
+ }
+
fn point_at_return_for_opaque_ty_error(
&self,
errors: &mut Vec>,
diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
index e14e8ac2ce000..57e4f0fcc9c39 100644
--- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
@@ -326,6 +326,10 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
}
}
+ hir::ExprKind::Become(call) => {
+ self.consume_expr(call);
+ }
+
hir::ExprKind::Assign(lhs, rhs, _) => {
self.mutate_expr(lhs);
self.consume_expr(rhs);
diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
index 786a8c28f998b..8fa29b5b87394 100644
--- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
+++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
@@ -214,6 +214,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
+ | ExprKind::Become(..)
| ExprKind::InlineAsm(..)
| ExprKind::OffsetOf(..)
| ExprKind::Struct(..)
@@ -451,6 +452,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
}
}
+ // FIXME(explicit_tail_calls): we should record the "drop everything that is not passed by-value" here, I think
+ ExprKind::Become(_call) => intravisit::walk_expr(self, expr),
+
ExprKind::Call(f, args) => {
self.visit_expr(f);
for arg in args {
diff --git a/compiler/rustc_hir_typeck/src/mem_categorization.rs b/compiler/rustc_hir_typeck/src/mem_categorization.rs
index 78171e0b20e8c..6c8589493cb01 100644
--- a/compiler/rustc_hir_typeck/src/mem_categorization.rs
+++ b/compiler/rustc_hir_typeck/src/mem_categorization.rs
@@ -361,6 +361,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {
| hir::ExprKind::AssignOp(..)
| hir::ExprKind::Closure { .. }
| hir::ExprKind::Ret(..)
+ | hir::ExprKind::Become(..)
| hir::ExprKind::Unary(..)
| hir::ExprKind::Yield(..)
| hir::ExprKind::MethodCall(..)
diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
index ea04899ab6872..caaf34acd1bc5 100644
--- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
@@ -116,6 +116,33 @@ extern "C" LLVMValueRef LLVMRustGetNamedValue(LLVMModuleRef M, const char *Name,
return wrap(unwrap(M)->getNamedValue(StringRef(Name, NameLen)));
}
+enum class LLVMRustTailCallKind {
+ None,
+ Tail,
+ MustTail,
+ NoTail,
+ Last,
+};
+
+static CallInst::TailCallKind fromRust(LLVMRustTailCallKind Kind) {
+ switch (Kind) {
+ case LLVMRustTailCallKind::None:
+ return CallInst::TailCallKind::TCK_None;
+ case LLVMRustTailCallKind::Tail:
+ return CallInst::TailCallKind::TCK_Tail;
+ case LLVMRustTailCallKind::MustTail:
+ return CallInst::TailCallKind::TCK_MustTail;
+ case LLVMRustTailCallKind::Last:
+ return CallInst::TailCallKind::TCK_NoTail;
+ default:
+ report_fatal_error("bad CallInst::TailCallKind.");
+ }
+}
+
+extern "C" void LLVMRustSetTailCallKind(LLVMValueRef Call, LLVMRustTailCallKind TCK) {
+ unwrap(Call)->setTailCallKind(fromRust(TCK));
+}
+
extern "C" LLVMValueRef LLVMRustGetOrInsertFunction(LLVMModuleRef M,
const char *Name,
size_t NameLen,
@@ -1672,6 +1699,7 @@ extern "C" void LLVMRustSetDSOLocal(LLVMValueRef Global, bool is_dso_local) {
unwrap(Global)->setDSOLocal(is_dso_local);
}
+
struct LLVMRustModuleBuffer {
std::string data;
};
diff --git a/compiler/rustc_middle/src/mir/spanview.rs b/compiler/rustc_middle/src/mir/spanview.rs
index 2165403da2671..faed54098be4d 100644
--- a/compiler/rustc_middle/src/mir/spanview.rs
+++ b/compiler/rustc_middle/src/mir/spanview.rs
@@ -269,6 +269,7 @@ pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
Call { .. } => "Call",
Assert { .. } => "Assert",
Yield { .. } => "Yield",
+ TailCall { .. } => "TailCall",
GeneratorDrop => "GeneratorDrop",
FalseEdge { .. } => "FalseEdge",
FalseUnwind { .. } => "FalseUnwind",
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index 1a65f74f4fe22..6b09c533bebfc 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -646,6 +646,36 @@ pub enum TerminatorKind<'tcx> {
fn_span: Span,
},
+ /// Tail call.
+ ///
+ /// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats.
+ /// Semantically tail calls consists of two actions:
+ /// - pop of the current stack frame
+ /// - a call to the `func`, with the return address of the **current** caller
+ /// - so that a `return` inside `func` returns to the caller of the caller
+ /// of the function that is currently being executed
+ ///
+ /// Note that in difference with [`Call`] this is missing
+ /// - `destination` (because it's always the return place)
+ /// - `target` (because it's always taken from the current stack frame)
+ /// - `unwind` (because it's always taken from the current stack frame)
+ ///
+ /// [`Call`]: TerminatorKind::Call
+ /// [`Return`]: TerminatorKind::Return
+ TailCall {
+ /// The function that’s being called.
+ func: Operand<'tcx>,
+ /// Arguments the function is called with.
+ /// These are owned by the callee, which is free to modify them.
+ /// This allows the memory occupied by "by-value" arguments to be
+ /// reused across function calls without duplicating the contents.
+ args: Vec>,
+ // FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it?
+ /// This `Span` is the span of the function, without the dot and receiver
+ /// (e.g. `foo(a, b)` in `x.foo(a, b)`
+ fn_span: Span,
+ },
+
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
@@ -771,6 +801,7 @@ impl TerminatorKind<'_> {
TerminatorKind::Unreachable => "Unreachable",
TerminatorKind::Drop { .. } => "Drop",
TerminatorKind::Call { .. } => "Call",
+ TerminatorKind::TailCall { .. } => "TailCall",
TerminatorKind::Assert { .. } => "Assert",
TerminatorKind::Yield { .. } => "Yield",
TerminatorKind::GeneratorDrop => "GeneratorDrop",
diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs
index 561ef371b0904..ce34266c721aa 100644
--- a/compiler/rustc_middle/src/mir/terminator.rs
+++ b/compiler/rustc_middle/src/mir/terminator.rs
@@ -158,6 +158,7 @@ impl<'tcx> TerminatorKind<'tcx> {
| Terminate
| GeneratorDrop
| Return
+ | TailCall { .. }
| Unreachable
| Call { target: None, unwind: _, .. }
| InlineAsm { destination: None, unwind: _, .. } => {
@@ -200,6 +201,7 @@ impl<'tcx> TerminatorKind<'tcx> {
| Terminate
| GeneratorDrop
| Return
+ | TailCall { .. }
| Unreachable
| Call { target: None, unwind: _, .. }
| InlineAsm { destination: None, unwind: _, .. } => None.into_iter().chain(&mut []),
@@ -216,6 +218,7 @@ impl<'tcx> TerminatorKind<'tcx> {
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::GeneratorDrop
| TerminatorKind::Yield { .. }
@@ -235,6 +238,7 @@ impl<'tcx> TerminatorKind<'tcx> {
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::GeneratorDrop
| TerminatorKind::Yield { .. }
@@ -325,6 +329,16 @@ impl<'tcx> TerminatorKind<'tcx> {
}
write!(fmt, ")")
}
+ TailCall { func, args, .. } => {
+ write!(fmt, "tailcall {func:?}(")?;
+ for (index, arg) in args.iter().enumerate() {
+ if index > 0 {
+ write!(fmt, ", ")?;
+ }
+ write!(fmt, "{:?}", arg)?;
+ }
+ write!(fmt, ")")
+ }
Assert { cond, expected, msg, .. } => {
write!(fmt, "assert(")?;
if !expected {
@@ -389,7 +403,7 @@ impl<'tcx> TerminatorKind<'tcx> {
pub fn fmt_successor_labels(&self) -> Vec> {
use self::TerminatorKind::*;
match *self {
- Return | Resume | Terminate | Unreachable | GeneratorDrop => vec![],
+ Return | TailCall { .. } | Resume | Terminate | Unreachable | GeneratorDrop => vec![],
Goto { .. } => vec!["".into()],
SwitchInt { ref targets, .. } => targets
.values
diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs
index 8d44e929afde3..1dcfb22fcd90d 100644
--- a/compiler/rustc_middle/src/mir/visit.rs
+++ b/compiler/rustc_middle/src/mir/visit.rs
@@ -533,6 +533,17 @@ macro_rules! make_mir_visitor {
);
}
+ TerminatorKind::TailCall {
+ func,
+ args,
+ fn_span: _,
+ } => {
+ self.visit_operand(func, location);
+ for arg in args {
+ self.visit_operand(arg, location);
+ }
+ },
+
TerminatorKind::Assert {
cond,
expected: _,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index a6c8d41e925f1..87885770ed1dd 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -864,6 +864,12 @@ rustc_queries! {
cache_on_disk_if { true }
}
+ /// Checks well-formedness of tail calls (`become f()`).
+ query thir_check_tail_calls(key: LocalDefId) -> Result<(), rustc_errors::ErrorGuaranteed> {
+ desc { |tcx| "tail-call-checking `{}`", tcx.def_path_str(key) }
+ cache_on_disk_if { true }
+ }
+
/// Returns the types assumed to be well formed while "inside" of the given item.
///
/// Note that we've liberated the late bound regions of function signatures, so
diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index 813e109c41e14..0f3ad6d0151d1 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -410,6 +410,10 @@ pub enum ExprKind<'tcx> {
Return {
value: Option,
},
+ /// A `become` expression.
+ Become {
+ value: ExprId,
+ },
/// An inline `const` block, e.g. `const {}`.
ConstBlock {
did: DefId,
diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs
index 5c7ec31cf93d3..9cc07677e0e36 100644
--- a/compiler/rustc_middle/src/thir/visit.rs
+++ b/compiler/rustc_middle/src/thir/visit.rs
@@ -100,6 +100,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp
visitor.visit_expr(&visitor.thir()[value])
}
}
+ Become { value } => visitor.visit_expr(&visitor.thir()[value]),
ConstBlock { did: _, substs: _ } => {}
Repeat { value, count: _ } => {
visitor.visit_expr(&visitor.thir()[value]);
diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs
index 7ec57add66b59..e56eea085e2e9 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_place.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs
@@ -549,6 +549,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
| ExprKind::Break { .. }
| ExprKind::Continue { .. }
| ExprKind::Return { .. }
+ | ExprKind::Become { .. }
| ExprKind::Literal { .. }
| ExprKind::NamedConst { .. }
| ExprKind::NonHirLiteral { .. }
diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
index 3742d640e3b58..27b1390dc4e4e 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
@@ -532,6 +532,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
| ExprKind::Break { .. }
| ExprKind::Continue { .. }
| ExprKind::Return { .. }
+ | ExprKind::Become { .. }
| ExprKind::InlineAsm { .. }
| ExprKind::PlaceTypeAscription { .. }
| ExprKind::ValueTypeAscription { .. } => {
diff --git a/compiler/rustc_mir_build/src/build/expr/category.rs b/compiler/rustc_mir_build/src/build/expr/category.rs
index d9aa461c19d40..2fe9cac637808 100644
--- a/compiler/rustc_mir_build/src/build/expr/category.rs
+++ b/compiler/rustc_mir_build/src/build/expr/category.rs
@@ -82,7 +82,8 @@ impl Category {
| ExprKind::Block { .. }
| ExprKind::Break { .. }
| ExprKind::Continue { .. }
- | ExprKind::Return { .. } =>
+ | ExprKind::Return { .. }
+ | ExprKind::Become { .. } =>
// FIXME(#27840) these probably want their own
// category, like "nonterminating"
{
diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs
index 29ff916d2cc9c..1e53643c6925e 100644
--- a/compiler/rustc_mir_build/src/build/expr/into.rs
+++ b/compiler/rustc_mir_build/src/build/expr/into.rs
@@ -489,7 +489,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block.unit()
}
- ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Return { .. } => {
+ ExprKind::Continue { .. }
+ | ExprKind::Break { .. }
+ | ExprKind::Return { .. }
+ | ExprKind::Become { .. } => {
unpack!(block = this.stmt_expr(block, expr, None));
// No assign, as these have type `!`.
block.unit()
diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs
index ea5aeb67d8576..981e7159e0c63 100644
--- a/compiler/rustc_mir_build/src/build/expr/stmt.rs
+++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs
@@ -99,6 +99,39 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
BreakableTarget::Return,
source_info,
),
+ ExprKind::Become { value } => {
+ let v = &this.thir[value];
+ let ExprKind::Scope { region_scope, lint_level, value, .. } = v.kind
+ else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") };
+ let v = &this.thir[value];
+ let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind
+ else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") };
+
+ this.in_scope((region_scope, source_info), lint_level, |this| {
+ let fun = unpack!(block = this.as_local_operand(block, &this.thir[fun]));
+ let args: Vec<_> = args
+ .into_iter()
+ .copied()
+ .map(|arg| {
+ unpack!(block = this.as_local_call_operand(block, &this.thir[arg]))
+ })
+ .collect();
+
+ this.record_operands_moved(&args);
+
+ debug!("expr_into_dest: fn_span={:?}", fn_span);
+
+ unpack!(block = this.break_for_tail_call(block));
+
+ this.cfg.terminate(
+ block,
+ source_info,
+ TerminatorKind::TailCall { func: fun, args, fn_span },
+ );
+
+ this.cfg.start_new_block().unit()
+ })
+ }
_ => {
assert!(
statement_scope.is_some(),
diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs
index 7f0c1d53f729a..1956183a388b5 100644
--- a/compiler/rustc_mir_build/src/build/mod.rs
+++ b/compiler/rustc_mir_build/src/build/mod.rs
@@ -45,6 +45,10 @@ fn mir_build(tcx: TyCtxt<'_>, def: LocalDefId) -> Body<'_> {
return construct_error(tcx, def, e);
}
+ if let Err(err) = tcx.thir_check_tail_calls(def) {
+ return construct_error(tcx, def, err);
+ }
+
let body = match tcx.thir_body(def) {
Err(error_reported) => construct_error(tcx, def, error_reported),
Ok((thir, expr)) => {
diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs
index 7c0fbc6f81c94..9ab78416acb53 100644
--- a/compiler/rustc_mir_build/src/build/scope.rs
+++ b/compiler/rustc_mir_build/src/build/scope.rs
@@ -721,6 +721,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.cfg.terminate(block, source_info, TerminatorKind::Resume);
}
+ /// Sets up the drops for explict tail calls.
+ ///
+ /// Unlike other kinds of early exits, tail calls do not go through the drop tree.
+ /// Instead, all scheduled drops are immediately added to the CFG.
+ pub(crate) fn break_for_tail_call(&mut self, mut block: BasicBlock) -> BlockAnd<()> {
+ // the innermost scope contains only the destructors for the tail call arguments
+ // we only want to drop these in case of a panic, so we skip it
+ for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
+ for drop in scope.drops.iter().rev() {
+ match drop.kind {
+ DropKind::Value => {
+ let target = self.cfg.start_new_block();
+ let terminator = TerminatorKind::Drop {
+ target,
+ // The caller will handle this if needed.
+ unwind: UnwindAction::Terminate,
+ place: drop.local.into(),
+ replace: false,
+ };
+ self.cfg.terminate(block, drop.source_info, terminator);
+ block = target;
+ }
+ DropKind::Storage => {
+ let stmt = Statement {
+ source_info: drop.source_info,
+ kind: StatementKind::StorageDead(drop.local),
+ };
+ self.cfg.push(block, stmt);
+ }
+ }
+ }
+ }
+
+ block.unit()
+ }
+
// Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue`
// statement.
fn add_dummy_assignment(&mut self, span: Span, block: BasicBlock, source_info: SourceInfo) {
@@ -1459,6 +1495,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind {
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Yield { .. }
| TerminatorKind::GeneratorDrop
diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs
new file mode 100644
index 0000000000000..bde25353f2b7d
--- /dev/null
+++ b/compiler/rustc_mir_build/src/check_tail_calls.rs
@@ -0,0 +1,382 @@
+use rustc_errors::Applicability;
+use rustc_hir::def::DefKind;
+use rustc_hir::LangItem;
+use rustc_middle::thir::visit::{self, Visitor};
+use rustc_middle::thir::ExprId;
+use rustc_middle::thir::{BodyTy, Expr, ExprKind, Thir};
+use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
+use rustc_span::def_id::DefId;
+use rustc_span::def_id::LocalDefId;
+use rustc_span::{ErrorGuaranteed, Span};
+use rustc_target::spec::abi::Abi;
+
+pub fn thir_check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> {
+ let (thir, expr) = tcx.thir_body(def)?;
+ let thir = &thir.borrow();
+
+ // If `thir` is empty, a type error occurred, skip this body.
+ if thir.exprs.is_empty() {
+ return Ok(());
+ }
+
+ let is_closure = matches!(tcx.def_kind(def), DefKind::Closure);
+ let caller_ty = tcx.type_of(def).skip_binder();
+
+ let mut visitor = TailCallCkVisitor {
+ tcx,
+ thir,
+ found_errors: Ok(()),
+ param_env: tcx.param_env(def),
+ is_closure,
+ caller_ty,
+ };
+
+ visitor.visit_expr(&thir[expr]);
+
+ visitor.found_errors
+}
+
+struct TailCallCkVisitor<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ thir: &'a Thir<'tcx>,
+ param_env: ParamEnv<'tcx>,
+ /// Whatever the currently checked body is one of a closure
+ is_closure: bool,
+ /// The result of the checks, `Err(_)` if there was a problem with some
+ /// tail call, `Ok(())` if all of them were fine.
+ found_errors: Result<(), ErrorGuaranteed>,
+ /// Type of the caller function.
+ caller_ty: Ty<'tcx>,
+}
+
+impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
+ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
+ if self.is_closure {
+ self.report_in_closure(expr);
+ return;
+ }
+
+ let BodyTy::Fn(caller_sig) = self.thir.body_type
+ else { span_bug!(call.span, "`become` outside of functions should have been disallowed by hit_typeck") };
+
+ let ExprKind::Scope { value, .. } = call.kind
+ else { span_bug!(call.span, "expected scope, found: {call:?}") };
+ let value = &self.thir[value];
+
+ if matches!(
+ value.kind,
+ ExprKind::Binary { .. }
+ | ExprKind::Unary { .. }
+ | ExprKind::AssignOp { .. }
+ | ExprKind::Index { .. }
+ ) {
+ self.report_builtin_op(call, expr);
+ return;
+ }
+
+ let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind
+ else {
+ self.report_non_call(value, expr);
+ return
+ };
+
+ if !from_hir_call {
+ self.report_op(ty, args, fn_span, expr);
+ }
+
+ // Closures in thir look something akin to
+ // `for<'a> extern "rust-call" fn(&'a [closure@...], ()) -> <[closure@...] as FnOnce<()>>::Output {<[closure@...] as Fn<()>>::call}`
+ // So we have to check for them in this weird way...
+ if let &ty::FnDef(did, substs) = ty.kind() {
+ let parent = self.tcx.parent(did);
+ let fn_ = self.tcx.require_lang_item(LangItem::Fn, Some(expr.span));
+ let fn_once = self.tcx.require_lang_item(LangItem::FnOnce, Some(expr.span));
+ let fn_mut = self.tcx.require_lang_item(LangItem::FnMut, Some(expr.span));
+ if [fn_, fn_once, fn_mut].contains(&parent) {
+ if substs.first().and_then(|arg| arg.as_type()).is_some_and(|t| t.is_closure()) {
+ self.report_calling_closure(
+ &self.thir[fun],
+ substs[1].as_type().unwrap(),
+ expr,
+ );
+
+ // Tail calling is likely to cause unrelated errors (ABI, argument mismatches)
+ return;
+ }
+ };
+ }
+
+ // Erase regions since tail calls don't care about lifetimes
+ let callee_sig =
+ self.tcx.normalize_erasing_late_bound_regions(self.param_env, ty.fn_sig(self.tcx));
+
+ if caller_sig.abi != callee_sig.abi {
+ self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi);
+ }
+
+ if caller_sig.inputs_and_output != callee_sig.inputs_and_output {
+ if caller_sig.inputs() != callee_sig.inputs() {
+ self.report_arguments_mismatch(expr.span, caller_sig, callee_sig);
+ }
+
+ if caller_sig.output() != callee_sig.output() {
+ span_bug!(expr.span, "hir typeck should have checked the return type already");
+ }
+ }
+
+ {
+ let caller_needs_location = self.needs_location(self.caller_ty);
+ let callee_needs_location = self.needs_location(ty);
+
+ if caller_needs_location != callee_needs_location {
+ self.report_track_caller_mismatch(expr.span, caller_needs_location);
+ }
+ }
+
+ if caller_sig.c_variadic {
+ self.report_c_variadic_caller(expr.span);
+ }
+
+ if callee_sig.c_variadic {
+ self.report_c_variadic_callee(expr.span);
+ }
+ }
+
+ /// Returns true if function of type `ty` needs location argument
+ /// (i.e. if a function is marked as `#[track_caller]`)
+ fn needs_location(&self, ty: Ty<'tcx>) -> bool {
+ if let &ty::FnDef(did, substs) = ty.kind() {
+ let instance =
+ ty::Instance::expect_resolve(self.tcx, ty::ParamEnv::reveal_all(), did, substs)
+ .polymorphize(self.tcx);
+
+ instance.def.requires_caller_location(self.tcx)
+ } else {
+ false
+ }
+ }
+
+ fn report_in_closure(&mut self, expr: &Expr<'_>) {
+ let err = self.tcx.sess.span_err(expr.span, "`become` is not allowed in closures");
+ self.found_errors = Err(err);
+ }
+
+ fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) {
+ let err = self
+ .tcx
+ .sess
+ .struct_span_err(value.span, "`become` does not support operators")
+ .note("using `become` on a builtin operator is not useful")
+ .span_suggestion(
+ value.span.until(expr.span),
+ "try using `return` instead",
+ "return ",
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ self.found_errors = Err(err);
+ }
+
+ fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) {
+ let mut err = self.tcx.sess.struct_span_err(fn_span, "`become` does not support operators");
+
+ if let &ty::FnDef(did, _substs) = fun_ty.kind()
+ && let parent = self.tcx.parent(did)
+ && matches!(self.tcx.def_kind(parent), DefKind::Trait)
+ && let Some(method) = op_trait_as_method_name(self.tcx, parent)
+ {
+ match args {
+ &[arg] => {
+ let arg = &self.thir[arg];
+
+ err.multipart_suggestion(
+ "try using the method directly",
+ vec![
+ (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()),
+ (arg.span.shrink_to_hi(), format!(").{method}()")),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ }
+ &[lhs, rhs] => {
+ let lhs = &self.thir[lhs];
+ let rhs = &self.thir[rhs];
+
+ err.multipart_suggestion(
+ "try using the method directly",
+ vec![
+ (lhs.span.shrink_to_lo(), format!("(")),
+ (lhs.span.between(rhs.span), format!(").{method}(")),
+ (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ }
+ _ => span_bug!(expr.span, "operator with more than 2 args? {args:?}"),
+ }
+ }
+
+ self.found_errors = Err(err.emit());
+ }
+
+ fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) {
+ let err = self
+ .tcx
+ .sess
+ .struct_span_err(value.span, "`become` requires a function call")
+ .span_note(value.span, "not a function call")
+ .span_suggestion(
+ value.span.until(expr.span),
+ "try using `return` instead",
+ "return ",
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+ self.found_errors = Err(err);
+ }
+
+ fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) {
+ let underscored_args = match tupled_args.kind() {
+ ty::Tuple(tys) if tys.is_empty() => "".to_owned(),
+ ty::Tuple(tys) => std::iter::repeat("_, ").take(tys.len() - 1).chain(["_"]).collect(),
+ _ => "_".to_owned(),
+ };
+
+ let err = self
+ .tcx
+ .sess
+ .struct_span_err(expr.span, "tail calling closures directly is not allowed")
+ .multipart_suggestion(
+ "try casting the closure to a function pointer type",
+ vec![
+ (fun.span.shrink_to_lo(), "(".to_owned()),
+ (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")),
+ ],
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+ self.found_errors = Err(err);
+ }
+
+ fn report_abi_mismatch(&mut self, sp: Span, caller_abi: Abi, callee_abi: Abi) {
+ let err = self
+ .tcx
+ .sess
+ .struct_span_err(sp, "mismatched function ABIs")
+ .note("`become` requires caller and callee to have the same ABI")
+ .note(format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`"))
+ .emit();
+ self.found_errors = Err(err);
+ }
+
+ fn report_arguments_mismatch(
+ &mut self,
+ sp: Span,
+ caller_sig: ty::FnSig<'_>,
+ callee_sig: ty::FnSig<'_>,
+ ) {
+ let err = self
+ .tcx
+ .sess
+ .struct_span_err(sp, "mismatched signatures")
+ .note("`become` requires caller and callee to have matching signatures")
+ .note(format!("caller signature: `{caller_sig}`"))
+ .note(format!("callee signature: `{callee_sig}`"))
+ .emit();
+ self.found_errors = Err(err);
+ }
+
+ fn report_track_caller_mismatch(&mut self, sp: Span, caller_needs_location: bool) {
+ let err = match caller_needs_location {
+ true => self
+ .tcx
+ .sess
+ .struct_span_err(
+ sp,
+ "a function marked with `#[track_caller]` cannot tail-call one that is not",
+ )
+ .emit(),
+ false => self
+ .tcx
+ .sess
+ .struct_span_err(
+ sp,
+ "a function mot marked with `#[track_caller]` cannot tail-call one that is",
+ )
+ .emit(),
+ };
+
+ self.found_errors = Err(err);
+ }
+
+ fn report_c_variadic_caller(&mut self, sp: Span) {
+ let err = self
+ .tcx
+ .sess
+ // FIXME(explicit_tail_calls): highlight the `...`
+ .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions")
+ .emit();
+
+ self.found_errors = Err(err);
+ }
+
+ fn report_c_variadic_callee(&mut self, sp: Span) {
+ let err = self
+ .tcx
+ .sess
+ // FIXME(explicit_tail_calls): highlight the function or something...
+ .struct_span_err(sp, "c-variadic functions can't be tail-called")
+ .emit();
+
+ self.found_errors = Err(err);
+ }
+}
+
+impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> {
+ fn thir(&self) -> &'a Thir<'tcx> {
+ &self.thir
+ }
+
+ fn visit_expr(&mut self, expr: &Expr<'tcx>) {
+ if let ExprKind::Become { value } = expr.kind {
+ let call = &self.thir[value];
+ self.check_tail_call(call, expr);
+ }
+
+ visit::walk_expr(self, expr);
+ }
+}
+
+fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> {
+ let trait_did = Some(trait_did);
+ let items = tcx.lang_items();
+ let m = match () {
+ _ if trait_did == items.get(LangItem::Add) => "add",
+ _ if trait_did == items.get(LangItem::Sub) => "sub",
+ _ if trait_did == items.get(LangItem::Mul) => "mul",
+ _ if trait_did == items.get(LangItem::Div) => "div",
+ _ if trait_did == items.get(LangItem::Rem) => "rem",
+ _ if trait_did == items.get(LangItem::Neg) => "neg",
+ _ if trait_did == items.get(LangItem::Not) => "not",
+ _ if trait_did == items.get(LangItem::BitXor) => "bitxor",
+ _ if trait_did == items.get(LangItem::BitAnd) => "bitand",
+ _ if trait_did == items.get(LangItem::BitOr) => "bitor",
+ _ if trait_did == items.get(LangItem::Shl) => "shl",
+ _ if trait_did == items.get(LangItem::Shr) => "shr",
+ _ if trait_did == items.get(LangItem::AddAssign) => "add_assign",
+ _ if trait_did == items.get(LangItem::SubAssign) => "sub_assign",
+ _ if trait_did == items.get(LangItem::MulAssign) => "mul_assign",
+ _ if trait_did == items.get(LangItem::DivAssign) => "div_assign",
+ _ if trait_did == items.get(LangItem::RemAssign) => "rem_assign",
+ _ if trait_did == items.get(LangItem::BitXorAssign) => "bitxor_assign",
+ _ if trait_did == items.get(LangItem::BitAndAssign) => "bitand_assign",
+ _ if trait_did == items.get(LangItem::BitOrAssign) => "bitor_assign",
+ _ if trait_did == items.get(LangItem::ShlAssign) => "shl_assign",
+ _ if trait_did == items.get(LangItem::ShrAssign) => "shr_assign",
+ _ if trait_did == items.get(LangItem::Index) => "index",
+ _ if trait_did == items.get(LangItem::IndexMut) => "index_mut",
+ _ => return None,
+ };
+
+ Some(m)
+}
diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs
index 0506f2bf2385c..7227ec3d207ed 100644
--- a/compiler/rustc_mir_build/src/check_unsafety.rs
+++ b/compiler/rustc_mir_build/src/check_unsafety.rs
@@ -316,6 +316,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
| ExprKind::Closure { .. }
| ExprKind::Continue { .. }
| ExprKind::Return { .. }
+ | ExprKind::Become { .. }
| ExprKind::Yield { .. }
| ExprKind::Loop { .. }
| ExprKind::Let { .. }
diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs
index 0eaab9b57036c..5a8e757754757 100644
--- a/compiler/rustc_mir_build/src/lib.rs
+++ b/compiler/rustc_mir_build/src/lib.rs
@@ -17,6 +17,7 @@ extern crate tracing;
extern crate rustc_middle;
mod build;
+mod check_tail_calls;
mod check_unsafety;
mod errors;
mod lints;
@@ -34,6 +35,7 @@ pub fn provide(providers: &mut Providers) {
providers.lit_to_const = thir::constant::lit_to_const;
providers.mir_built = build::mir_built;
providers.thir_check_unsafety = check_unsafety::thir_check_unsafety;
+ providers.thir_check_tail_calls = check_tail_calls::thir_check_tail_calls;
providers.thir_body = thir::cx::thir_body;
providers.thir_tree = thir::print::thir_tree;
providers.thir_flat = thir::print::thir_flat;
diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs
index 8e41957af0eba..5af7b17daf829 100644
--- a/compiler/rustc_mir_build/src/lints.rs
+++ b/compiler/rustc_mir_build/src/lints.rs
@@ -112,6 +112,8 @@ impl<'mir, 'tcx> TriColorVisitor> for Search<'mir, 'tcx> {
| TerminatorKind::GeneratorDrop
| TerminatorKind::Resume
| TerminatorKind::Return
+ // FIXME(explicit_tail_calls) Is this right??
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index b20495d602e59..3d72bfd825ee7 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -694,7 +694,8 @@ impl<'tcx> Cx<'tcx> {
ExprKind::Repeat { value: self.mirror_expr(v), count: *count }
}
- hir::ExprKind::Ret(ref v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) },
+ hir::ExprKind::Ret(v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) },
+ hir::ExprKind::Become(call) => ExprKind::Become { value: self.mirror_expr(call) },
hir::ExprKind::Break(dest, ref value) => match dest.target_id {
Ok(target_id) => ExprKind::Break {
label: region::Scope { id: target_id.local_id, data: region::ScopeData::Node },
diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs
index b2f2a64e29c8c..d07ca8773a5f0 100644
--- a/compiler/rustc_mir_build/src/thir/print.rs
+++ b/compiler/rustc_mir_build/src/thir/print.rs
@@ -421,6 +421,12 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> {
print_indented!(self, "}", depth_lvl);
}
+ Become { value } => {
+ print_indented!(self, "Return {", depth_lvl);
+ print_indented!(self, "value:", depth_lvl + 1);
+ self.print_expr(*value, depth_lvl + 2);
+ print_indented!(self, "}", depth_lvl);
+ }
ConstBlock { did, substs } => {
print_indented!(self, "ConstBlock {", depth_lvl);
print_indented!(self, format!("did: {:?}", did), depth_lvl + 1);
diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs
index 0c379288a0962..8bfef37364549 100644
--- a/compiler/rustc_mir_dataflow/src/framework/direction.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs
@@ -527,6 +527,10 @@ impl Direction for Forward {
}
}
+ TailCall { .. } => {
+ // FIXME(explicit_tail_calls): is there anything sensible to do here?
+ }
+
InlineAsm {
template: _,
ref operands,
diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs
index e5e9c1507ba8b..40b8dbb212f24 100644
--- a/compiler/rustc_mir_dataflow/src/framework/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs
@@ -453,7 +453,7 @@ where
/// building up a `GenKillSet` and then throwing it away.
pub trait GenKill {
/// Inserts `elem` into the state vector.
- fn gen(&mut self, elem: T);
+ fn gen(&mut self, der: T);
/// Removes `elem` from the state vector.
fn kill(&mut self, elem: T);
diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
index b88ed32b687f6..e70e7ca58f69b 100644
--- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
@@ -136,6 +136,7 @@ where
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } => {}
diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
index 666c8d50a8a8c..51449496c1d59 100644
--- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
@@ -281,6 +281,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
| TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {}
}
@@ -318,6 +319,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
| TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {}
}
diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
index 096bc0acfccb9..28d2b594c8f4f 100644
--- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
+++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
@@ -411,6 +411,12 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly);
}
}
+ TerminatorKind::TailCall { ref func, ref args, .. } => {
+ self.gather_operand(func);
+ for arg in args {
+ self.gather_operand(arg);
+ }
+ }
TerminatorKind::InlineAsm {
template: _,
ref operands,
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 5693e5a4a712a..1eeaa9548f393 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -258,6 +258,9 @@ pub trait ValueAnalysis<'tcx> {
// They would have an effect, but are not allowed in this phase.
bug!("encountered disallowed terminator");
}
+ TerminatorKind::TailCall { .. } => {
+ // FIXME(explicit_tail_calls): determine if we need to do something here (probably not)
+ }
TerminatorKind::Goto { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Resume
diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs
index 70812761e88d6..38c3a55e22395 100644
--- a/compiler/rustc_mir_transform/src/check_unsafety.rs
+++ b/compiler/rustc_mir_transform/src/check_unsafety.rs
@@ -66,7 +66,7 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
// safe (at least as emitted during MIR construction)
}
- TerminatorKind::Call { ref func, .. } => {
+ TerminatorKind::Call { ref func, .. } | TerminatorKind::TailCall { ref func, .. } => {
let func_ty = func.ty(self.body, self.tcx);
let func_id =
if let ty::FnDef(func_id, _) = func_ty.kind() { Some(func_id) } else { None };
diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs
index 1d43dbda0aae4..4e7b102818b4b 100644
--- a/compiler/rustc_mir_transform/src/const_prop.rs
+++ b/compiler/rustc_mir_transform/src/const_prop.rs
@@ -950,11 +950,12 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::InlineAsm { .. } => {}
+
// Every argument in our function calls have already been propagated in `visit_operand`.
//
// NOTE: because LLVM codegen gives slight performance regressions with it, so this is
// gated on `mir_opt_level=3`.
- TerminatorKind::Call { .. } => {}
+ TerminatorKind::Call { .. } | TerminatorKind::TailCall { .. } => {}
}
}
diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs
index 759650fe4db3a..79ca5ddc89c47 100644
--- a/compiler/rustc_mir_transform/src/const_prop_lint.rs
+++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs
@@ -681,6 +681,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Drop { .. }
| TerminatorKind::Yield { .. }
diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs
index ea1223fbca642..94f31b46c6aaf 100644
--- a/compiler/rustc_mir_transform/src/coverage/graph.rs
+++ b/compiler/rustc_mir_transform/src/coverage/graph.rs
@@ -123,6 +123,7 @@ impl CoverageGraph {
match term.kind {
TerminatorKind::Return { .. }
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Terminate
| TerminatorKind::Yield { .. }
| TerminatorKind::SwitchInt { .. } => {
diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs
index d27200419e2cb..f353b7da1a56a 100644
--- a/compiler/rustc_mir_transform/src/coverage/spans.rs
+++ b/compiler/rustc_mir_transform/src/coverage/spans.rs
@@ -841,7 +841,7 @@ pub(super) fn filtered_statement_span(statement: &Statement<'_>) -> Option
/// If the MIR `Terminator` has a span contributive to computing coverage spans,
/// return it; otherwise return `None`.
pub(super) fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option {
- match terminator.kind {
+ match &terminator.kind {
// These terminators have spans that don't positively contribute to computing a reasonable
// span of actually executed source code. (For example, SwitchInt terminators extracted from
// an `if condition { block }` has a span that includes the executed block, if true,
@@ -856,7 +856,8 @@ pub(super) fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option None,
// Call `func` operand can have a more specific span when part of a chain of calls
- | TerminatorKind::Call { ref func, .. } => {
+ TerminatorKind::Call { func, .. }
+ | TerminatorKind::TailCall { func, .. } => {
let mut span = terminator.source_info.span;
if let mir::Operand::Constant(box constant) = func {
if constant.span.lo() > span.lo() {
diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs
index 78758e2db28ab..56b603e2fd6c4 100644
--- a/compiler/rustc_mir_transform/src/dest_prop.rs
+++ b/compiler/rustc_mir_transform/src/dest_prop.rs
@@ -618,6 +618,12 @@ impl WriteInfo {
self.add_operand(arg);
}
}
+ TerminatorKind::TailCall { func, args, .. } => {
+ self.add_operand(func);
+ for arg in args {
+ self.add_operand(arg);
+ }
+ }
TerminatorKind::InlineAsm { operands, .. } => {
for asm_operand in operands {
match asm_operand {
diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs
index fe3f8ed047a70..b72209c1b74b5 100644
--- a/compiler/rustc_mir_transform/src/generator.rs
+++ b/compiler/rustc_mir_transform/src/generator.rs
@@ -1217,6 +1217,8 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
// These may unwind.
TerminatorKind::Drop { .. }
| TerminatorKind::Call { .. }
+ // FIXME(explicit_tail_calls): can tail calls unwind actually?..
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Assert { .. } => return true,
}
@@ -1716,6 +1718,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Return
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Drop { .. }
| TerminatorKind::Assert { .. }
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index 5487b5987e001..758095de36f33 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -1078,6 +1078,9 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> {
*target = self.map_block(*target);
*unwind = self.map_unwind(*unwind);
}
+ TerminatorKind::TailCall { .. } => {
+ // FIXME(explicit_tail_calls): figure out how exactly tail calls are inlined
+ }
TerminatorKind::Call { ref mut target, ref mut unwind, .. } => {
if let Some(ref mut tgt) = *target {
*tgt = self.map_block(*tgt);
diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
index 4941c9edce305..7188bdd6576b3 100644
--- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
+++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs
@@ -75,6 +75,7 @@ impl RemoveNoopLandingPads {
| TerminatorKind::Terminate
| TerminatorKind::Unreachable
| TerminatorKind::Call { .. }
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::InlineAsm { .. } => false,
diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs
index f35a5fb42768a..b47830d8ae81c 100644
--- a/compiler/rustc_mir_transform/src/separate_const_switch.rs
+++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs
@@ -109,6 +109,7 @@ pub fn separate_const_switch(body: &mut Body<'_>) -> usize {
TerminatorKind::Resume
| TerminatorKind::Drop { .. }
| TerminatorKind::Call { .. }
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::Assert { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Yield { .. }
@@ -172,6 +173,7 @@ pub fn separate_const_switch(body: &mut Body<'_>) -> usize {
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::Call { .. }
+ | TerminatorKind::TailCall { .. }
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Yield { .. } => {
span_bug!(
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index f4ee7b7587587..ba104fbf2f83b 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -744,7 +744,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
let tcx = self.tcx;
match terminator.kind {
- mir::TerminatorKind::Call { ref func, .. } => {
+ mir::TerminatorKind::Call { ref func, .. }
+ | mir::TerminatorKind::TailCall { ref func, .. } => {
let callee_ty = func.ty(self.body, tcx);
let callee_ty = self.monomorphize(callee_ty);
visit_fn_use(self.tcx, callee_ty, true, source, &mut self.output)
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index cea2a71c98821..296e3484b43b5 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..6c748147abe07 100644
--- a/compiler/rustc_passes/src/hir_stats.rs
+++ b/compiler/rustc_passes/src/hir_stats.rs
@@ -302,8 +302,8 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
[
ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type,
DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index,
- Path, AddrOf, Break, Continue, Ret, InlineAsm, OffsetOf, Struct, Repeat, Yield,
- Err
+ Path, AddrOf, Break, Continue, Ret, Become, InlineAsm, OffsetOf, Struct, Repeat,
+ Yield, Err
]
);
hir_visit::walk_expr(self, e)
@@ -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_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index 63b1578d43fd7..18249b10c7dbd 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -463,6 +463,8 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
| hir::ExprKind::Lit(_)
| hir::ExprKind::ConstBlock(..)
| hir::ExprKind::Ret(..)
+ // FIXME(explicit_tail_calls): this may or may not be wrong
+ | hir::ExprKind::Become(..)
| hir::ExprKind::Block(..)
| hir::ExprKind::Assign(..)
| hir::ExprKind::AssignOp(..)
@@ -967,6 +969,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln)
}
+ hir::ExprKind::Become(ref e) => {
+ // Ignore succ and subst exit_ln.
+ self.propagate_through_expr(e, self.exit_ln)
+ }
+
hir::ExprKind::Break(label, ref opt_expr) => {
// Find which label this break jumps to
let target = match label.target_id {
@@ -1408,6 +1415,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
| hir::ExprKind::DropTemps(..)
| hir::ExprKind::Unary(..)
| hir::ExprKind::Ret(..)
+ | hir::ExprKind::Become(..)
| hir::ExprKind::Break(..)
| hir::ExprKind::Continue(..)
| hir::ExprKind::Lit(_)
diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs
index a849d61edfeaa..769b389009b7e 100644
--- a/compiler/rustc_passes/src/naked_functions.rs
+++ b/compiler/rustc_passes/src/naked_functions.rs
@@ -204,6 +204,7 @@ impl<'tcx> CheckInlineAssembly<'tcx> {
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::OffsetOf(..)
+ | ExprKind::Become(..)
| ExprKind::Struct(..)
| ExprKind::Repeat(..)
| ExprKind::Yield(..) => {
diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs
index 6bd030b13d1ce..cbb3239d063d9 100644
--- a/compiler/rustc_smir/src/rustc_smir/mod.rs
+++ b/compiler/rustc_smir/src/rustc_smir/mod.rs
@@ -372,6 +372,7 @@ impl<'tcx> Stable for mir::Terminator<'tcx> {
unwind: unwind.stable(),
}
}
+ TailCall { .. } => todo!(),
Yield { .. } | GeneratorDrop | FalseEdge { .. } | FalseUnwind { .. } => unreachable!(),
}
}
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index c5ce2575fff06..8b0a7659cbdde 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -687,6 +687,7 @@ symbols! {
expf32,
expf64,
explicit_generic_args_with_impl_trait,
+ explicit_tail_calls,
export_name,
expr,
extended_key_value_attributes,
diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs
index ce77df0df5dcf..cb5029ec04502 100644
--- a/compiler/rustc_ty_utils/src/consts.rs
+++ b/compiler/rustc_ty_utils/src/consts.rs
@@ -240,7 +240,8 @@ fn recurse_build<'tcx>(
ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
error(GenericConstantTooComplexSub::AssignNotSupported(node.span))?
}
- ExprKind::Closure { .. } | ExprKind::Return { .. } => {
+ // FIXME(explicit_tail_calls): maybe get `become` a new error
+ ExprKind::Closure { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } => {
error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))?
}
// let expressions imply control flow
@@ -336,6 +337,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> {
| thir::ExprKind::Break { .. }
| thir::ExprKind::Continue { .. }
| thir::ExprKind::Return { .. }
+ | thir::ExprKind::Become { .. }
| thir::ExprKind::Array { .. }
| thir::ExprKind::Tuple { .. }
| thir::ExprKind::Adt(_)
diff --git a/library/std/src/keyword_docs.rs b/library/std/src/keyword_docs.rs
index eb46f4e54bb67..27dfda7480ee3 100644
--- a/library/std/src/keyword_docs.rs
+++ b/library/std/src/keyword_docs.rs
@@ -1228,6 +1228,69 @@ mod ref_keyword {}
/// ```
mod return_keyword {}
+#[doc(keyword = "become")]
+//
+/// Perform a tail-call of a function.
+///
+/// A `become` transfers the execution flow to a function in such a way, that
+/// returning from the callee returns to the caller of the current function:
+///
+/// ```
+/// #![feature(explicit_tail_calls)]
+///
+/// fn a() -> u32 {
+/// become b();
+/// }
+///
+/// fn b() -> u32 {
+/// return 2; // this return directly returns to the main ---+
+/// } // |
+/// // |
+/// fn main() { // |
+/// let res = a(); // <--------------------------------------+
+/// assert_eq!(res, 2);
+/// }
+/// ```
+///
+/// This is an optimization that allows function calls to not exhaust the stack.
+/// This is most useful for (mutually) recursive algorithms, but may be used in
+/// other cases too.
+///
+/// It is guaranteed that the call will not cause unbounded stack growth if it
+/// is part of a recursive cycle in the call graph.
+///
+/// For example note that the functions `halt` and `halt_loop` below are
+/// identical, they both do nothing, forever. However `stack_overflow` is
+/// different from them, even though it is written almost identically to
+/// `halt`, `stack_overflow` exhausts the stack and so causes a stack
+/// overflow, instead of running forever.
+///
+///
+/// ```
+/// #![feature(explicit_tail_calls)]
+///
+/// # #[allow(unreachable_code)]
+/// fn halt() -> ! {
+/// become halt()
+/// }
+///
+/// fn halt_loop() -> ! {
+/// loop {}
+/// }
+///
+/// # #[allow(unconditional_recursion)]
+/// fn stack_overflow() -> ! {
+/// stack_overflow() // implicit return
+/// }
+/// ```
+///
+/// Note that from the algorithmic standpoint loops and tail-calls are
+/// interchangeable, you can always rewrite a loop to use tail-calls
+/// instead and vice versa. They are, however, very different in the code
+/// structure, so sometimes one approach can make more sense that the other.
+#[cfg(not(bootstrap))]
+mod become_keyword {}
+
#[doc(keyword = "self")]
//
/// The receiver of a method, or the current module.
diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
index 5f1fdf00be8c3..10b5e1edf9250 100644
--- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
@@ -206,6 +206,12 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H
NeverLoopResult::AlwaysBreak,
)
}),
+ ExprKind::Become(e) => {
+ combine_seq(
+ never_loop_expr(e, ignore_ids, main_loop_id),
+ NeverLoopResult::AlwaysBreak,
+ )
+ }
ExprKind::InlineAsm(asm) => asm
.operands
.iter()
diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
index 7945275393c04..93ef07d36aea7 100644
--- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
@@ -329,6 +329,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
ExprKind::Field(..) |
ExprKind::Index(..) |
ExprKind::Ret(..) |
+ ExprKind::Become(..) |
ExprKind::Repeat(..) |
ExprKind::Yield(..) => walk_expr(self, ex),
ExprKind::AddrOf(_, _, _) |
diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs
index 3c2bf5abab2b5..6b51974d739af 100644
--- a/src/tools/clippy/clippy_lints/src/utils/author.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/author.rs
@@ -559,6 +559,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
kind!("Ret({value})");
value.if_some(|e| self.expr(e));
},
+ ExprKind::Become(value) => {
+ bind!(self, value);
+ kind!("Become({value})");
+ self.expr(value);
+ },
ExprKind::InlineAsm(_) => {
kind!("InlineAsm(_)");
out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
index 3df40942e7b5a..0d34b8c6bf67f 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -152,6 +152,10 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
self.eagerness |= NoChange;
return;
},
+ ExprKind::Become(..) => {
+ self.eagerness = ForceNoChange;
+ return;
+ }
ExprKind::Path(ref path) => {
if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) {
self.eagerness = ForceNoChange;
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index a49246a783272..3e646233d539d 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -845,6 +845,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(e);
}
},
+ ExprKind::Become(f) => {
+ self.hash_expr(f);
+ }
ExprKind::Path(ref qpath) => {
self.hash_qpath(qpath);
},
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index c0d2c835d63d4..66b21c062ca73 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -314,15 +314,8 @@ fn check_terminator<'tcx>(
Err((span, "const fn generators are unstable".into()))
},
- TerminatorKind::Call {
- func,
- args,
- from_hir_call: _,
- destination: _,
- target: _,
- unwind: _,
- fn_span: _,
- } => {
+ TerminatorKind::Call { func, args, .. }
+ | TerminatorKind::TailCall { func, args, .. } => {
let fn_ty = func.ty(body, tcx);
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
if !is_const_fn(tcx, fn_def_id, msrv) {
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index f477524eec5cc..b38b9553558c8 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -147,6 +147,7 @@ impl<'a> Sugg<'a> {
| hir::ExprKind::Path(..)
| hir::ExprKind::Repeat(..)
| hir::ExprKind::Ret(..)
+ | hir::ExprKind::Become(..)
| hir::ExprKind::Struct(..)
| hir::ExprKind::Tup(..)
| hir::ExprKind::Err(_) => Sugg::NonParen(get_snippet(expr.span)),
@@ -211,6 +212,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/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs
index 5dcd71cef127e..8dafa723afa00 100644
--- a/src/tools/clippy/clippy_utils/src/visitors.rs
+++ b/src/tools/clippy/clippy_utils/src/visitors.rs
@@ -651,6 +651,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
// Either drops temporaries, jumps out of the current expression, or has no sub expression.
ExprKind::DropTemps(_)
| ExprKind::Ret(_)
+ | ExprKind::Become(_)
| ExprKind::Break(..)
| ExprKind::Yield(..)
| ExprKind::Block(..)
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/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 55bf38110a6d5..3ed587caadc6b 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
const ENTRY_LIMIT: usize = 900;
// FIXME: The following limits should be reduced eventually.
const ISSUES_ENTRY_LIMIT: usize = 1896;
-const ROOT_ENTRY_LIMIT: usize = 870;
+const ROOT_ENTRY_LIMIT: usize = 871;
const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
"rs", // test source files
diff --git a/tests/codegen/explicit-tail-calls.rs b/tests/codegen/explicit-tail-calls.rs
new file mode 100644
index 0000000000000..e1eb5e1bcf4c8
--- /dev/null
+++ b/tests/codegen/explicit-tail-calls.rs
@@ -0,0 +1,107 @@
+// compile-flags: -C no-prepopulate-passes
+// min-llvm-version: 15.0 (for opaque pointers)
+#![crate_type = "lib"]
+#![feature(explicit_tail_calls)]
+#![feature(c_variadic)]
+
+/// Something that is likely to be passed indirectly
+#[repr(C)]
+pub struct IndirectProbably {
+ _0: u8,
+ _1: u32,
+ _2: u64,
+ _3: u8,
+ _4: [u32; 8],
+ _5: u8,
+ _6: u128,
+}
+
+
+#[no_mangle]
+// CHECK-LABEL: @simple_f(
+pub fn simple_f() -> u32 {
+ // CHECK: %0 = musttail call noundef i32 @simple_g()
+ // CHECK: ret i32 %0
+ become simple_g();
+}
+
+#[no_mangle]
+fn simple_g() -> u32 {
+ 0
+}
+
+
+#[no_mangle]
+// CHECK-LABEL: @unit_f(
+fn unit_f() {
+ // CHECK: musttail call void @unit_g()
+ // CHECK: ret void
+ become unit_g();
+}
+
+#[no_mangle]
+fn unit_g() {}
+
+
+#[no_mangle]
+// CHECK-LABEL: @indirect_f(
+fn indirect_f() -> IndirectProbably {
+ // CHECK: musttail call void @indirect_g(ptr noalias nocapture noundef sret(%IndirectProbably) dereferenceable(80) %0)
+ // CHECK: ret void
+ become indirect_g();
+}
+
+#[no_mangle]
+fn indirect_g() -> IndirectProbably {
+ todo!()
+}
+
+
+#[no_mangle]
+// CHECK-LABEL: @pair_f(
+pub fn pair_f() -> (u32, u8) {
+ // CHECK: %0 = musttail call { i32, i8 } @pair_g()
+ // CHECK: ret { i32, i8 } %0
+ become pair_g()
+}
+
+#[no_mangle]
+fn pair_g() -> (u32, u8) {
+ (1, 2)
+}
+
+
+#[no_mangle]
+// CHECK-LABEL: @extern_c_f(i32 noundef %x)
+pub extern "C" fn extern_c_f(x: u32) -> u8 {
+ unsafe {
+ // CHECK: %0 = musttail call noundef i8 @extern_c_g(i32 noundef %x)
+ // CHECK: ret i8 %0
+ become extern_c_g(x);
+ }
+}
+
+extern "C" {
+ fn extern_c_g(x: u32) -> u8;
+}
+
+
+#[no_mangle]
+/// Does `src + dst` in a recursive way
+// CHECK-LABEL: @flow(
+fn flow(src: u64, dst: u64) -> u64 {
+ match src {
+ 0 => dst,
+ // CHECK: %1 = musttail call noundef i64 @flow(
+ // CHECK: ret i64 %1
+ _ => become flow(src - 1, dst + 1),
+ }
+}
+
+#[no_mangle]
+// CHECK-LABEL: @halt(
+pub fn halt() -> ! {
+ // CHECK: musttail call void @halt()
+ // CHECK: ret void
+ become halt();
+}
diff --git a/tests/ui/error-codes/E0572.stderr b/tests/ui/error-codes/E0572.stderr
index 36619f8dee4cc..2d749580e240b 100644
--- a/tests/ui/error-codes/E0572.stderr
+++ b/tests/ui/error-codes/E0572.stderr
@@ -1,4 +1,4 @@
-error[E0572]: return statement outside of function body
+error[E0572]: `return` statement outside of function body
--> $DIR/E0572.rs:1:18
|
LL | const FOO: u32 = return 0;
diff --git a/tests/ui/explicit-tail-calls/become-c-variadic.rs b/tests/ui/explicit-tail-calls/become-c-variadic.rs
new file mode 100644
index 0000000000000..659c3906976ef
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-c-variadic.rs
@@ -0,0 +1,20 @@
+#![feature(explicit_tail_calls)]
+#![feature(c_variadic)]
+
+pub unsafe extern "C" fn c_variadic_f(x: u8, mut args: ...) {
+ become c_variadic_g(x)
+ //~^ error: tail-calls are not allowed in c-variadic functions
+ //~| error: c-variadic functions can't be tail-called
+}
+
+pub unsafe extern "C" fn c_variadic_g(x: u8, mut args: ...) {
+ become normal(x)
+ //~^ error: tail-calls are not allowed in c-variadic functions
+}
+
+unsafe extern "C" fn normal(x: u8) {
+ become c_variadic_f(x)
+ //~^ error: c-variadic functions can't be tail-called
+}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/become-c-variadic.stderr b/tests/ui/explicit-tail-calls/become-c-variadic.stderr
new file mode 100644
index 0000000000000..ce1fb9170961f
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-c-variadic.stderr
@@ -0,0 +1,26 @@
+error: tail-calls are not allowed in c-variadic functions
+ --> $DIR/become-c-variadic.rs:5:5
+ |
+LL | become c_variadic_g(x)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: c-variadic functions can't be tail-called
+ --> $DIR/become-c-variadic.rs:5:5
+ |
+LL | become c_variadic_g(x)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: tail-calls are not allowed in c-variadic functions
+ --> $DIR/become-c-variadic.rs:11:5
+ |
+LL | become normal(x)
+ | ^^^^^^^^^^^^^^^^
+
+error: c-variadic functions can't be tail-called
+ --> $DIR/become-c-variadic.rs:16:5
+ |
+LL | become c_variadic_f(x)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/become-macro.rs b/tests/ui/explicit-tail-calls/become-macro.rs
new file mode 100644
index 0000000000000..79498bbbecced
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-macro.rs
@@ -0,0 +1,12 @@
+// run-pass
+#![feature(explicit_tail_calls, decl_macro)]
+
+macro call($f:expr $(, $args:expr)* $(,)?) {
+ ($f)($($args),*)
+}
+
+fn main() {
+ become call!(f);
+}
+
+fn f() {}
diff --git a/tests/ui/explicit-tail-calls/become-operator.fixed b/tests/ui/explicit-tail-calls/become-operator.fixed
new file mode 100644
index 0000000000000..c93d45884d095
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-operator.fixed
@@ -0,0 +1,41 @@
+// run-rustfix
+#![feature(explicit_tail_calls)]
+#![allow(unused)]
+use std::num::Wrapping;
+use std::ops::{Not, Add, BitXorAssign};
+
+// built-ins and overloaded operators are handled differently
+
+fn f(a: u64, b: u64) -> u64 {
+ return a + b; //~ error: `become` does not support operators
+}
+
+fn g(a: String, b: &str) -> String {
+ become (a).add(b); //~ error: `become` does not support operators
+}
+
+fn h(x: u64) -> u64 {
+ return !x; //~ error: `become` does not support operators
+}
+
+fn i_do_not_know_any_more_letters(x: Wrapping) -> Wrapping {
+ become (x).not(); //~ error: `become` does not support operators
+}
+
+fn builtin_index(x: &[u8], i: usize) -> u8 {
+ return x[i] //~ error: `become` does not support operators
+}
+
+// FIXME(explicit_tail_calls): overloaded index is represented like `[&]*x.index(i)`,
+// and so need additional handling
+
+fn a(a: &mut u8, _: u8) {
+ return *a ^= 1; //~ error: `become` does not support operators
+}
+
+fn b(b: &mut Wrapping, _: u8) {
+ become (*b).bitxor_assign(1); //~ error: `become` does not support operators
+}
+
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/become-operator.rs b/tests/ui/explicit-tail-calls/become-operator.rs
new file mode 100644
index 0000000000000..c0851d7b270e8
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-operator.rs
@@ -0,0 +1,41 @@
+// run-rustfix
+#![feature(explicit_tail_calls)]
+#![allow(unused)]
+use std::num::Wrapping;
+use std::ops::{Not, Add, BitXorAssign};
+
+// built-ins and overloaded operators are handled differently
+
+fn f(a: u64, b: u64) -> u64 {
+ become a + b; //~ error: `become` does not support operators
+}
+
+fn g(a: String, b: &str) -> String {
+ become a + b; //~ error: `become` does not support operators
+}
+
+fn h(x: u64) -> u64 {
+ become !x; //~ error: `become` does not support operators
+}
+
+fn i_do_not_know_any_more_letters(x: Wrapping) -> Wrapping {
+ become !x; //~ error: `become` does not support operators
+}
+
+fn builtin_index(x: &[u8], i: usize) -> u8 {
+ become x[i] //~ error: `become` does not support operators
+}
+
+// FIXME(explicit_tail_calls): overloaded index is represented like `[&]*x.index(i)`,
+// and so need additional handling
+
+fn a(a: &mut u8, _: u8) {
+ become *a ^= 1; //~ error: `become` does not support operators
+}
+
+fn b(b: &mut Wrapping, _: u8) {
+ become *b ^= 1; //~ error: `become` does not support operators
+}
+
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/become-operator.stderr b/tests/ui/explicit-tail-calls/become-operator.stderr
new file mode 100644
index 0000000000000..75e2acbac65b0
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-operator.stderr
@@ -0,0 +1,75 @@
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:10:12
+ |
+LL | become a + b;
+ | -------^^^^^
+ | |
+ | help: try using `return` instead: `return`
+ |
+ = note: using `become` on a builtin operator is not useful
+
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:14:12
+ |
+LL | become a + b;
+ | ^^^^^
+ |
+help: try using the method directly
+ |
+LL | become (a).add(b);
+ | + ~~~~~~ +
+
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:18:12
+ |
+LL | become !x;
+ | -------^^
+ | |
+ | help: try using `return` instead: `return`
+ |
+ = note: using `become` on a builtin operator is not useful
+
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:22:12
+ |
+LL | become !x;
+ | ^^
+ |
+help: try using the method directly
+ |
+LL | become (x).not();
+ | ~ +++++++
+
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:26:12
+ |
+LL | become x[i]
+ | -------^^^^
+ | |
+ | help: try using `return` instead: `return`
+ |
+ = note: using `become` on a builtin operator is not useful
+
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:33:12
+ |
+LL | become *a ^= 1;
+ | -------^^^^^^^
+ | |
+ | help: try using `return` instead: `return`
+ |
+ = note: using `become` on a builtin operator is not useful
+
+error: `become` does not support operators
+ --> $DIR/become-operator.rs:37:12
+ |
+LL | become *b ^= 1;
+ | ^^^^^^^
+ |
+help: try using the method directly
+ |
+LL | become (*b).bitxor_assign(1);
+ | + ~~~~~~~~~~~~~~~~ +
+
+error: aborting due to 7 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/become-outside.array.stderr b/tests/ui/explicit-tail-calls/become-outside.array.stderr
new file mode 100644
index 0000000000000..acbff8b0db63b
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-outside.array.stderr
@@ -0,0 +1,9 @@
+error[E0572]: `become` statement outside of function body
+ --> $DIR/become-outside.rs:10:17
+ |
+LL | struct Bad([(); become f()]);
+ | ^^^^^^^^^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0572`.
diff --git a/tests/ui/explicit-tail-calls/become-outside.constant.stderr b/tests/ui/explicit-tail-calls/become-outside.constant.stderr
new file mode 100644
index 0000000000000..4c4c11bf9cca9
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-outside.constant.stderr
@@ -0,0 +1,9 @@
+error[E0572]: `become` statement outside of function body
+ --> $DIR/become-outside.rs:6:5
+ |
+LL | become f();
+ | ^^^^^^^^^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0572`.
diff --git a/tests/ui/explicit-tail-calls/become-outside.rs b/tests/ui/explicit-tail-calls/become-outside.rs
new file mode 100644
index 0000000000000..7967a63e2a5d0
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-outside.rs
@@ -0,0 +1,14 @@
+// revisions: constant array
+#![feature(explicit_tail_calls)]
+
+#[cfg(constant)]
+const _: () = {
+ become f(); //[constant]~ error: `become` statement outside of function body
+};
+
+#[cfg(array)]
+struct Bad([(); become f()]); //[array]~ error: `become` statement outside of function body
+
+fn f() {}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/become-uncallable.fixed b/tests/ui/explicit-tail-calls/become-uncallable.fixed
new file mode 100644
index 0000000000000..e0c2b2caff6a8
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-uncallable.fixed
@@ -0,0 +1,17 @@
+// run-rustfix
+#![feature(explicit_tail_calls)]
+#![allow(unused)]
+
+fn f() -> u64 {
+ return 1; //~ error: `become` requires a function call
+}
+
+fn g() {
+ return { h() }; //~ error: `become` requires a function call
+}
+
+fn h() {
+ return *&g(); //~ error: `become` requires a function call
+}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/become-uncallable.rs b/tests/ui/explicit-tail-calls/become-uncallable.rs
new file mode 100644
index 0000000000000..c18f2b7a070ed
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-uncallable.rs
@@ -0,0 +1,17 @@
+// run-rustfix
+#![feature(explicit_tail_calls)]
+#![allow(unused)]
+
+fn f() -> u64 {
+ become 1; //~ error: `become` requires a function call
+}
+
+fn g() {
+ become { h() }; //~ error: `become` requires a function call
+}
+
+fn h() {
+ become *&g(); //~ error: `become` requires a function call
+}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/become-uncallable.stderr b/tests/ui/explicit-tail-calls/become-uncallable.stderr
new file mode 100644
index 0000000000000..16dbd1ab81155
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-uncallable.stderr
@@ -0,0 +1,44 @@
+error: `become` requires a function call
+ --> $DIR/become-uncallable.rs:6:12
+ |
+LL | become 1;
+ | -------^
+ | |
+ | help: try using `return` instead: `return`
+ |
+note: not a function call
+ --> $DIR/become-uncallable.rs:6:12
+ |
+LL | become 1;
+ | ^
+
+error: `become` requires a function call
+ --> $DIR/become-uncallable.rs:10:12
+ |
+LL | become { h() };
+ | -------^^^^^^^
+ | |
+ | help: try using `return` instead: `return`
+ |
+note: not a function call
+ --> $DIR/become-uncallable.rs:10:12
+ |
+LL | become { h() };
+ | ^^^^^^^
+
+error: `become` requires a function call
+ --> $DIR/become-uncallable.rs:14:12
+ |
+LL | become *&g();
+ | -------^^^^^
+ | |
+ | help: try using `return` instead: `return`
+ |
+note: not a function call
+ --> $DIR/become-uncallable.rs:14:12
+ |
+LL | become *&g();
+ | ^^^^^
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/caller-location-mismatch.rs b/tests/ui/explicit-tail-calls/caller-location-mismatch.rs
new file mode 100644
index 0000000000000..8b54e0c72cc5b
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/caller-location-mismatch.rs
@@ -0,0 +1,28 @@
+#![feature(explicit_tail_calls)]
+use std::panic::Location;
+
+fn get_some_location1() -> &'static Location<'static> {
+ #[track_caller]
+ fn inner() -> &'static Location<'static> {
+ become Location::caller()
+ }
+
+ become inner()
+ //~^ error: a function mot marked with `#[track_caller]` cannot tail-call one that is
+}
+
+#[track_caller]
+fn get_some_location2() -> &'static Location<'static> {
+ fn inner() -> &'static Location<'static> {
+ become Location::caller()
+ //~^ error: a function mot marked with `#[track_caller]` cannot tail-call one that is
+ }
+
+ become inner()
+ //~^ error: a function marked with `#[track_caller]` cannot tail-call one that is not
+}
+
+fn main() {
+ get_some_location1();
+ get_some_location2();
+}
diff --git a/tests/ui/explicit-tail-calls/caller-location-mismatch.stderr b/tests/ui/explicit-tail-calls/caller-location-mismatch.stderr
new file mode 100644
index 0000000000000..9e133de773590
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/caller-location-mismatch.stderr
@@ -0,0 +1,20 @@
+error: a function mot marked with `#[track_caller]` cannot tail-call one that is
+ --> $DIR/caller-location-mismatch.rs:10:5
+ |
+LL | become inner()
+ | ^^^^^^^^^^^^^^
+
+error: a function marked with `#[track_caller]` cannot tail-call one that is not
+ --> $DIR/caller-location-mismatch.rs:21:5
+ |
+LL | become inner()
+ | ^^^^^^^^^^^^^^
+
+error: a function mot marked with `#[track_caller]` cannot tail-call one that is
+ --> $DIR/caller-location-mismatch.rs:17:9
+ |
+LL | become Location::caller()
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/caller-location.rs b/tests/ui/explicit-tail-calls/caller-location.rs
new file mode 100644
index 0000000000000..4f25c8dbdf330
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/caller-location.rs
@@ -0,0 +1,18 @@
+// run-pass
+#![feature(explicit_tail_calls)]
+use std::panic::Location;
+
+fn main() {
+ assert_eq!(get_caller_location().line(), 6);
+ assert_eq!(get_caller_location().line(), 7);
+}
+
+#[track_caller]
+fn get_caller_location() -> &'static Location<'static> {
+ #[track_caller]
+ fn inner() -> &'static Location<'static> {
+ become Location::caller()
+ }
+
+ become inner()
+}
diff --git a/tests/ui/explicit-tail-calls/closure.fixed b/tests/ui/explicit-tail-calls/closure.fixed
new file mode 100644
index 0000000000000..07d469f1343fc
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/closure.fixed
@@ -0,0 +1,30 @@
+// run-rustfix
+#![feature(explicit_tail_calls)]
+
+fn a() {
+ become ((|| ()) as fn() -> _)();
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn aa((): ()) {
+ become ((|()| ()) as fn(_) -> _)(());
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn aaa((): (), _: i32) {
+ become ((|(), _| ()) as fn(_, _) -> _)((), 1);
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn v((): (), ((), ()): ((), ())) -> (((), ()), ()) {
+ let f = |(), ((), ())| (((), ()), ());
+ become (f as fn(_, _) -> _)((), ((), ()));
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn main() {
+ a();
+ aa(());
+ aaa((), 1);
+ v((), ((), ()));
+}
diff --git a/tests/ui/explicit-tail-calls/closure.rs b/tests/ui/explicit-tail-calls/closure.rs
new file mode 100644
index 0000000000000..2a570d554ff20
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/closure.rs
@@ -0,0 +1,30 @@
+// run-rustfix
+#![feature(explicit_tail_calls)]
+
+fn a() {
+ become (|| ())();
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn aa((): ()) {
+ become (|()| ())(());
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn aaa((): (), _: i32) {
+ become (|(), _| ())((), 1);
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn v((): (), ((), ()): ((), ())) -> (((), ()), ()) {
+ let f = |(), ((), ())| (((), ()), ());
+ become f((), ((), ()));
+ //~^ ERROR: tail calling closures directly is not allowed
+}
+
+fn main() {
+ a();
+ aa(());
+ aaa((), 1);
+ v((), ((), ()));
+}
diff --git a/tests/ui/explicit-tail-calls/closure.stderr b/tests/ui/explicit-tail-calls/closure.stderr
new file mode 100644
index 0000000000000..46cf312877549
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/closure.stderr
@@ -0,0 +1,46 @@
+error: tail calling closures directly is not allowed
+ --> $DIR/closure.rs:5:5
+ |
+LL | become (|| ())();
+ | ^^^^^^^^^^^^^^^^
+ |
+help: try casting the closure to a function pointer type
+ |
+LL | become ((|| ()) as fn() -> _)();
+ | + +++++++++++++
+
+error: tail calling closures directly is not allowed
+ --> $DIR/closure.rs:10:5
+ |
+LL | become (|()| ())(());
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+help: try casting the closure to a function pointer type
+ |
+LL | become ((|()| ()) as fn(_) -> _)(());
+ | + ++++++++++++++
+
+error: tail calling closures directly is not allowed
+ --> $DIR/closure.rs:15:5
+ |
+LL | become (|(), _| ())((), 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try casting the closure to a function pointer type
+ |
+LL | become ((|(), _| ()) as fn(_, _) -> _)((), 1);
+ | + +++++++++++++++++
+
+error: tail calling closures directly is not allowed
+ --> $DIR/closure.rs:21:5
+ |
+LL | become f((), ((), ()));
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try casting the closure to a function pointer type
+ |
+LL | become (f as fn(_, _) -> _)((), ((), ()));
+ | + +++++++++++++++++
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/constck.rs b/tests/ui/explicit-tail-calls/constck.rs
new file mode 100644
index 0000000000000..ada1a5052308c
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/constck.rs
@@ -0,0 +1,21 @@
+#![feature(explicit_tail_calls)]
+
+const fn f() {
+ if false {
+ become not_const();
+ //~^ error: cannot call non-const fn `not_const` in constant functions
+ }
+}
+
+const fn g((): ()) {
+ if false {
+ become yes_const(not_const());
+ //~^ error: cannot call non-const fn `not_const` in constant functions
+ }
+}
+
+fn not_const() {}
+
+const fn yes_const((): ()) {}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/constck.stderr b/tests/ui/explicit-tail-calls/constck.stderr
new file mode 100644
index 0000000000000..455e431e10fae
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/constck.stderr
@@ -0,0 +1,19 @@
+error[E0015]: cannot call non-const fn `not_const` in constant functions
+ --> $DIR/constck.rs:5:16
+ |
+LL | become not_const();
+ | ^^^^^^^^^^^
+ |
+ = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+
+error[E0015]: cannot call non-const fn `not_const` in constant functions
+ --> $DIR/constck.rs:12:26
+ |
+LL | become yes_const(not_const());
+ | ^^^^^^^^^^^
+ |
+ = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0015`.
diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs
new file mode 100644
index 0000000000000..047bfd8789777
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs
@@ -0,0 +1,13 @@
+#![feature(explicit_tail_calls)]
+
+pub const fn test(_: &Type) {
+ const fn takes_borrow(_: &Type) {}
+
+ let local = Type;
+ become takes_borrow(&local);
+ //~^ error: `local` does not live long enough
+}
+
+struct Type;
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr
new file mode 100644
index 0000000000000..e92df550d7d74
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr
@@ -0,0 +1,14 @@
+error[E0597]: `local` does not live long enough
+ --> $DIR/ctfe-arg-bad-borrow.rs:7:25
+ |
+LL | let local = Type;
+ | ----- binding `local` declared here
+LL | become takes_borrow(&local);
+ | ^^^^^^ borrowed value does not live long enough
+LL |
+LL | }
+ | - `local` dropped here while still borrowed
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0597`.
diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs
new file mode 100644
index 0000000000000..325ed4ec21670
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs
@@ -0,0 +1,12 @@
+// check-pass
+#![feature(explicit_tail_calls)]
+
+pub const fn test(x: &Type) {
+ const fn takes_borrow(_: &Type) {}
+
+ become takes_borrow(x);
+}
+
+pub struct Type;
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-move.rs b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs
new file mode 100644
index 0000000000000..8520e765349f5
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs
@@ -0,0 +1,12 @@
+// check-pass
+#![feature(explicit_tail_calls)]
+
+pub const fn test(s: String) -> String {
+ const fn takes_string(s: String) -> String { s }
+
+ become takes_string(s);
+}
+
+struct Type;
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs
new file mode 100644
index 0000000000000..3f4291e9e171b
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs
@@ -0,0 +1,42 @@
+// run-pass
+#![feature(explicit_tail_calls)]
+
+/// A very unnecessarily complicated "implementation" of the callatz conjecture.
+/// Returns the number of steps to reach `1`.
+///
+/// This is just a test for tail calls, which involves multiple functions calling each other.
+///
+/// Panics if `x == 0`.
+const fn collatz(x: u32) -> u32 {
+ assert!(x > 0);
+
+ const fn switch(x: u32, steps: u32) -> u32 {
+ match x {
+ 1 => steps,
+ _ if x & 1 == 0 => become div2(x, steps + 1),
+ _ => become mul3plus1(x, steps + 1),
+ }
+ }
+
+ const fn div2(x: u32, steps: u32) -> u32 {
+ become switch(x >> 1, steps)
+ }
+
+ const fn mul3plus1(x: u32, steps: u32) -> u32 {
+ become switch(3*x + 1, steps)
+ }
+
+ switch(x, 0)
+}
+
+const ASSERTS: () = {
+ assert!(collatz(1) == 0);
+ assert!(collatz(2) == 1);
+ assert!(collatz(3) == 7);
+ assert!(collatz(4) == 2);
+ assert!(collatz(6171) == 261);
+};
+
+fn main() {
+ let _ = ASSERTS;
+}
diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr
new file mode 100644
index 0000000000000..877f1a866276f
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr
@@ -0,0 +1,36 @@
+error[E0080]: evaluation of constant value failed
+ --> $DIR/ctfe-id-unlimited.rs:16:42
+ |
+LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
+ | ^^^^^^^^^^^^^^^^^^^^^ reached the configured maximum number of stack frames
+ |
+note: inside `inner`
+ --> $DIR/ctfe-id-unlimited.rs:16:42
+ |
+LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
+ | ^^^^^^^^^^^^^^^^^^^^^
+note: [... 125 additional calls inside `inner` ...]
+ --> $DIR/ctfe-id-unlimited.rs:16:42
+ |
+LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
+ | ^^^^^^^^^^^^^^^^^^^^^
+note: inside `rec_id`
+ --> $DIR/ctfe-id-unlimited.rs:21:5
+ |
+LL | inner(0, n)
+ | ^^^^^^^^^^^
+note: inside `ID_ED`
+ --> $DIR/ctfe-id-unlimited.rs:28:20
+ |
+LL | const ID_ED: u32 = rec_id(ORIGINAL);
+ | ^^^^^^^^^^^^^^^^
+
+note: erroneous constant used
+ --> $DIR/ctfe-id-unlimited.rs:30:40
+ |
+LL | const ASSERT: () = assert!(ORIGINAL == ID_ED);
+ | ^^^^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs
new file mode 100644
index 0000000000000..b47c62141b7f0
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs
@@ -0,0 +1,34 @@
+// revisions: become return
+// [become] run-pass
+#![feature(explicit_tail_calls)]
+
+// This is an identity function (`|x| x`), but implemented using recursion.
+// Each step we increment accumulator and decrement the number.
+//
+// With normal calls this fails compilation because of the recursion limit,
+// but with tail calls/`become` we don't grow the stack/spend recursion limit
+// so this should compile.
+const fn rec_id(n: u32) -> u32 {
+ const fn inner(acc: u32, n: u32) -> u32 {
+ match n {
+ 0 => acc,
+ #[cfg(r#become)] _ => become inner(acc + 1, n - 1),
+ #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
+ //[return]~^ error: evaluation of constant value failed
+ }
+ }
+
+ inner(0, n)
+}
+
+// Some relatively big number that is higher than recursion limit
+const ORIGINAL: u32 = 12345;
+// Original number, but with identity function applied
+// (this is the same, but requires execution of the recursion)
+const ID_ED: u32 = rec_id(ORIGINAL);
+// Assert to make absolutely sure the computation actually happens
+const ASSERT: () = assert!(ORIGINAL == ID_ED);
+
+fn main() {
+ let _ = ASSERT;
+}
diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs
new file mode 100644
index 0000000000000..1c51263930636
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs
@@ -0,0 +1,18 @@
+#![feature(explicit_tail_calls)]
+
+pub const fn f() {
+ become g();
+}
+
+const fn g() {
+ panic!()
+ //~^ error: evaluation of constant value failed
+ //~| note: in this expansion of panic!
+ //~| note: inside `g`
+ //~| note: in this expansion of panic!
+}
+
+const _: () = f();
+//~^ note: inside `_`
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr
new file mode 100644
index 0000000000000..44834580b908a
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr
@@ -0,0 +1,21 @@
+error[E0080]: evaluation of constant value failed
+ --> $DIR/ctfe-tail-call-panic.rs:8:5
+ |
+LL | panic!()
+ | ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/ctfe-tail-call-panic.rs:8:5
+ |
+note: inside `g`
+ --> $DIR/ctfe-tail-call-panic.rs:8:5
+ |
+LL | panic!()
+ | ^^^^^^^^
+note: inside `_`
+ --> $DIR/ctfe-tail-call-panic.rs:15:15
+ |
+LL | const _: () = f();
+ | ^^^
+ = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs
new file mode 100644
index 0000000000000..6ef4f68e91694
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs
@@ -0,0 +1,23 @@
+#![feature(explicit_tail_calls, const_trait_impl, const_mut_refs)]
+
+pub const fn test(_: &View) {
+ const fn takes_view(_: &View) {}
+
+ become takes_view(HasDrop.as_view());
+ //~^ error: temporary value dropped while borrowed
+}
+
+struct HasDrop;
+struct View;
+
+impl HasDrop {
+ const fn as_view(&self) -> &View {
+ &View
+ }
+}
+
+impl const Drop for HasDrop {
+ fn drop(&mut self) {}
+}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr
new file mode 100644
index 0000000000000..3617f546932a4
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr
@@ -0,0 +1,18 @@
+error[E0716]: temporary value dropped while borrowed
+ --> $DIR/ctfe-tmp-arg-drop.rs:6:23
+ |
+LL | become takes_view(HasDrop.as_view());
+ | ------------------^^^^^^^------------ temporary value is freed at the end of this statement
+ | | |
+ | | creates a temporary value which is freed while still in use
+ | borrow later used here
+ |
+help: consider using a `let` binding to create a longer lived value
+ |
+LL ~ let binding = HasDrop;
+LL ~ become takes_view(binding.as_view());
+ |
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0716`.
diff --git a/tests/ui/explicit-tail-calls/drop-order.rs b/tests/ui/explicit-tail-calls/drop-order.rs
new file mode 100644
index 0000000000000..98f537f31fe91
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/drop-order.rs
@@ -0,0 +1,67 @@
+// run-pass
+#![feature(explicit_tail_calls)]
+use std::cell::RefCell;
+
+fn main() {
+ let tail_counter = Default::default();
+ tail_recursive(0, &tail_counter);
+ assert_eq!(tail_counter.into_inner(), (0..128).collect::>());
+
+ let simply_counter = Default::default();
+ simply_recursive(0, &simply_counter);
+ assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::>());
+
+ let scope_counter = Default::default();
+ out_of_inner_scope(&scope_counter);
+ assert_eq!(scope_counter.into_inner(), (0..8).collect::>());
+}
+
+fn tail_recursive(n: u8, order: &RefCell>) {
+ if n >= 128 {
+ return;
+ }
+
+ let _local = DropCounter(n, order);
+
+ become tail_recursive(n + 1, order)
+}
+
+fn simply_recursive(n: u8, order: &RefCell>) {
+ if n >= 128 {
+ return;
+ }
+
+ let _local = DropCounter(n, order);
+
+ return simply_recursive(n + 1, order)
+}
+
+fn out_of_inner_scope(order: &RefCell>) {
+ fn inner(order: &RefCell>) {
+ let _7 = DropCounter(7, order);
+ let _6 = DropCounter(6, order);
+ }
+
+ let _5 = DropCounter(5, order);
+ let _4 = DropCounter(4, order);
+
+ if true {
+ let _3 = DropCounter(3, order);
+ let _2 = DropCounter(2, order);
+ loop {
+ let _1 = DropCounter(1, order);
+ let _0 = DropCounter(0, order);
+
+ become inner(order);
+ }
+ }
+}
+
+struct DropCounter<'a>(u8, &'a RefCell>);
+
+impl Drop for DropCounter<'_> {
+ #[track_caller]
+ fn drop(&mut self) {
+ self.1.borrow_mut().push(self.0);
+ }
+}
diff --git a/tests/ui/explicit-tail-calls/dyn-fn-once.rs b/tests/ui/explicit-tail-calls/dyn-fn-once.rs
new file mode 100644
index 0000000000000..1d3871a24f590
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/dyn-fn-once.rs
@@ -0,0 +1,15 @@
+#![feature(explicit_tail_calls)]
+
+fn f() {
+ become (Box::new(|| ()) as Box ()>)();
+ //~^ error: mismatched function ABIs
+ //~| error: mismatched signatures
+}
+
+fn g() {
+ become (&g as &dyn FnOnce() -> ())();
+ //~^ error: mismatched function ABIs
+ //~| error: mismatched signatures
+}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/dyn-fn-once.stderr b/tests/ui/explicit-tail-calls/dyn-fn-once.stderr
new file mode 100644
index 0000000000000..d118f8431c7a2
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/dyn-fn-once.stderr
@@ -0,0 +1,40 @@
+error: mismatched function ABIs
+ --> $DIR/dyn-fn-once.rs:4:5
+ |
+LL | become (Box::new(|| ()) as Box ()>)();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `become` requires caller and callee to have the same ABI
+ = note: caller ABI is `"Rust"`, while callee ABI is `"rust-call"`
+
+error: mismatched signatures
+ --> $DIR/dyn-fn-once.rs:4:5
+ |
+LL | become (Box::new(|| ()) as Box ()>)();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `become` requires caller and callee to have matching signatures
+ = note: caller signature: `fn()`
+ = note: callee signature: `extern "rust-call" fn(Box, ())`
+
+error: mismatched function ABIs
+ --> $DIR/dyn-fn-once.rs:10:5
+ |
+LL | become (&g as &dyn FnOnce() -> ())();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `become` requires caller and callee to have the same ABI
+ = note: caller ABI is `"Rust"`, while callee ABI is `"rust-call"`
+
+error: mismatched signatures
+ --> $DIR/dyn-fn-once.rs:10:5
+ |
+LL | become (&g as &dyn FnOnce() -> ())();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `become` requires caller and callee to have matching signatures
+ = note: caller signature: `fn()`
+ = note: callee signature: `extern "rust-call" fn(dyn FnOnce(), ())`
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/fibonacci.rs b/tests/ui/explicit-tail-calls/fibonacci.rs
new file mode 100644
index 0000000000000..f1f3eac180bda
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/fibonacci.rs
@@ -0,0 +1,25 @@
+// run-pass
+
+fn fibonacci(n: u32) -> u128 {
+ fibonacci_impl(n, 0, 1)
+}
+
+fn fibonacci_impl(left: u32, prev_prev: u128, prev: u128) -> u128 {
+ match left {
+ 0 => prev_prev,
+ 1 => prev,
+ _ => fibonacci_impl(left - 1, prev, prev_prev + prev),
+ }
+}
+
+fn main() {
+ let expected =
+ [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181];
+ assert!((0..20).map(fibonacci).eq(expected));
+
+ // This is the highest fibonacci number that fits in a u128
+ assert_eq!(
+ std::hint::black_box(fibonacci(std::hint::black_box(186))),
+ 332825110087067562321196029789634457848
+ );
+}
diff --git a/tests/ui/explicit-tail-calls/fn-pointers.rs b/tests/ui/explicit-tail-calls/fn-pointers.rs
new file mode 100644
index 0000000000000..ef9c40a5fe6e0
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/fn-pointers.rs
@@ -0,0 +1,43 @@
+// run-pass
+#![feature(explicit_tail_calls)]
+
+fn main() {
+ use Inst::*;
+
+ let program = [
+ Inc, // st + 1 = 1
+ ShiftLeft, // st * 2 = 2
+ ShiftLeft, // st * 2 = 4
+ ShiftLeft, // st * 2 = 8
+ Inc, // st + 1 = 9
+ Inc, // st + 1 = 10
+ Inc, // st + 1 = 11
+ ShiftLeft, // st * 2 = 22
+ Halt, // st = 22
+ ];
+
+ assert_eq!(run(0, &program), 22);
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum Inst {
+ Inc,
+ ShiftLeft,
+ Halt,
+}
+
+fn run(state: u32, program: &[Inst]) -> u32 {
+ const DISPATCH_TABLE: [fn(u32, &[Inst]) -> u32; 3] = [inc, shift_left, |st, _| st];
+
+ become DISPATCH_TABLE[program[0] as usize](state, program);
+}
+
+fn inc(state: u32, program: &[Inst]) -> u32 {
+ assert_eq!(program[0], Inst::Inc);
+ become run(state + 1, &program[1..])
+}
+
+fn shift_left(state: u32, program: &[Inst]) -> u32 {
+ assert_eq!(program[0], Inst::ShiftLeft);
+ become run(state << 1, &program[1..])
+}
diff --git a/tests/ui/explicit-tail-calls/from-main-1.rs b/tests/ui/explicit-tail-calls/from-main-1.rs
new file mode 100644
index 0000000000000..2be833f4cc17a
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/from-main-1.rs
@@ -0,0 +1,8 @@
+// run-pass
+#![feature(explicit_tail_calls)]
+
+fn main() {
+ become f();
+}
+
+fn f() {}
diff --git a/tests/ui/explicit-tail-calls/from-main-2.rs b/tests/ui/explicit-tail-calls/from-main-2.rs
new file mode 100644
index 0000000000000..c4245d5e9c813
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/from-main-2.rs
@@ -0,0 +1,10 @@
+// run-pass
+#![feature(explicit_tail_calls)]
+
+fn main() -> Result<(), i32> {
+ become f();
+}
+
+fn f() -> Result<(), i32> {
+ Ok(())
+}
diff --git a/tests/ui/explicit-tail-calls/in-closure.rs b/tests/ui/explicit-tail-calls/in-closure.rs
new file mode 100644
index 0000000000000..680c8671aff7b
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/in-closure.rs
@@ -0,0 +1,7 @@
+#![feature(explicit_tail_calls)]
+
+fn main() {
+ || become f(); //~ error: `become` is not allowed in closures
+}
+
+fn f() {}
diff --git a/tests/ui/explicit-tail-calls/in-closure.stderr b/tests/ui/explicit-tail-calls/in-closure.stderr
new file mode 100644
index 0000000000000..5077c0a42b713
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/in-closure.stderr
@@ -0,0 +1,8 @@
+error: `become` is not allowed in closures
+ --> $DIR/in-closure.rs:4:8
+ |
+LL | || become f();
+ | ^^^^^^^^^^
+
+error: aborting due to previous error
+
diff --git a/tests/ui/explicit-tail-calls/ll.rs b/tests/ui/explicit-tail-calls/ll.rs
new file mode 100644
index 0000000000000..4cc20d3b81cde
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/ll.rs
@@ -0,0 +1,74 @@
+// revisions: tail nose
+//[tail] run-pass
+//[nose] run-fail
+#![feature(explicit_tail_calls)]
+
+fn main() {
+ with_smol_stack(|| List::from_elem((), 1024 * 32).rec_drop());
+}
+
+struct List {
+ next: Option>>,
+}
+
+struct Node {
+ elem: T,
+ next: Option>>,
+}
+
+impl List {
+ fn from_elem(elem: T, n: usize) -> Self
+ where
+ T: Clone,
+ {
+ List { next: None }.prepend_n(elem, n)
+ }
+
+ fn prepend_n(self, elem: T, n: usize) -> Self
+ where
+ T: Clone,
+ {
+ match n {
+ 0 => self,
+ 1 => Self { next: p(Node { elem, next: self.next }) },
+ _ => {
+ #[cfg(tail)]
+ become Self { next: p(Node { elem: elem.clone(), next: self.next }) }
+ .prepend_n(elem, n - 1);
+
+ #[cfg(nose)]
+ return Self { next: p(Node { elem: elem.clone(), next: self.next }) }
+ .prepend_n(elem, n - 1);
+ }
+ }
+ }
+
+ fn rec_drop(self) {
+ if let Some(node) = self.next {
+ node.rec_drop()
+ }
+ }
+}
+
+impl Node {
+ fn rec_drop(self) {
+ if let Some(node) = self.next {
+ _ = node.elem;
+ become node.rec_drop()
+ }
+ }
+}
+
+fn p(v: T) -> Option> {
+ Some(Box::new(v))
+}
+
+fn with_smol_stack(f: impl FnOnce() + Send + 'static) {
+ std::thread::Builder::new()
+ .stack_size(1024 /* bytes */)
+ .name("smol thread".to_owned())
+ .spawn(f)
+ .unwrap()
+ .join()
+ .unwrap();
+}
diff --git a/tests/ui/explicit-tail-calls/out-of-async.rs b/tests/ui/explicit-tail-calls/out-of-async.rs
new file mode 100644
index 0000000000000..15275edc3775c
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/out-of-async.rs
@@ -0,0 +1,20 @@
+// edition: 2021
+#![feature(explicit_tail_calls)]
+
+async fn a() {
+ become b();
+ //~^ error: mismatched function ABIs
+ //~| error: mismatched signatures
+}
+
+fn b() {}
+
+fn block() -> impl std::future::Future