about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMaybe Waffle <waffle.lapkin@gmail.com>2023-05-11 11:43:09 +0000
committerMaybe Lapkin <waffle.lapkin@gmail.com>2024-11-29 04:44:41 +0100
commitcfb78419cd865a19dac54d234d2a6abde0f14628 (patch)
treed16774324337b1b56560124fe33bdd3513546df3
parentc1cfab230ebb2e9cb9f4ea69773fef956c706a71 (diff)
downloadrust-cfb78419cd865a19dac54d234d2a6abde0f14628.tar.gz
rust-cfb78419cd865a19dac54d234d2a6abde0f14628.zip
implement checks for tail calls
this implements checks necessary to guarantee that we can actually
perform a tail call. while extremely restrictive, this is what is
documented in the RFC, and all these checks are needed for one reason or
another.
-rw-r--r--compiler/rustc_middle/src/query/mod.rs6
-rw-r--r--compiler/rustc_mir_build/src/build/mod.rs4
-rw-r--r--compiler/rustc_mir_build/src/check_tail_calls.rs387
-rw-r--r--compiler/rustc_mir_build/src/lib.rs2
-rw-r--r--tests/ui/explicit-tail-calls/become-macro.rs13
-rw-r--r--tests/ui/explicit-tail-calls/become-operator.fixed42
-rw-r--r--tests/ui/explicit-tail-calls/become-operator.rs42
-rw-r--r--tests/ui/explicit-tail-calls/become-operator.stderr75
-rw-r--r--tests/ui/explicit-tail-calls/become-uncallable.fixed18
-rw-r--r--tests/ui/explicit-tail-calls/become-uncallable.rs18
-rw-r--r--tests/ui/explicit-tail-calls/become-uncallable.stderr44
-rw-r--r--tests/ui/explicit-tail-calls/closure.fixed31
-rw-r--r--tests/ui/explicit-tail-calls/closure.rs31
-rw-r--r--tests/ui/explicit-tail-calls/closure.stderr46
-rw-r--r--tests/ui/explicit-tail-calls/in-closure.rs8
-rw-r--r--tests/ui/explicit-tail-calls/in-closure.stderr8
-rw-r--r--tests/ui/explicit-tail-calls/signature-mismatch.rs33
-rw-r--r--tests/ui/explicit-tail-calls/signature-mismatch.stderr40
18 files changed, 848 insertions, 0 deletions
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 0f2a6d598a0..d3748af4077 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -916,6 +916,12 @@ rustc_queries! {
         cache_on_disk_if { true }
     }
 
+    /// Checks well-formedness of tail calls (`become f()`).
+    query 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_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs
index 3317f3b7f8a..f43c29d8f5d 100644
--- a/compiler/rustc_mir_build/src/build/mod.rs
+++ b/compiler/rustc_mir_build/src/build/mod.rs
@@ -50,6 +50,10 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx
         return construct_error(tcx, def, e);
     }
 
+    if let Err(err) = tcx.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/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs
new file mode 100644
index 00000000000..911a6cb7de6
--- /dev/null
+++ b/compiler/rustc_mir_build/src/check_tail_calls.rs
@@ -0,0 +1,387 @@
+use rustc_abi::ExternAbi;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem;
+use rustc_hir::def::DefKind;
+use rustc_middle::span_bug;
+use rustc_middle::thir::visit::{self, Visitor};
+use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir};
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_span::def_id::{DefId, LocalDefId};
+use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
+
+pub(crate) fn 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(()),
+        // FIXME(#132279): we're clearly in a body here.
+        typing_env: ty::TypingEnv::non_body_analysis(tcx, def),
+        is_closure,
+        caller_ty,
+    };
+
+    visitor.visit_expr(&thir[expr]);
+
+    visitor.found_errors
+}
+
+struct TailCallCkVisitor<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    thir: &'a Thir<'tcx>,
+    typing_env: ty::TypingEnv<'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.typing_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, self.typing_env, did, substs, DUMMY_SP)
+                    .polymorphize(self.tcx);
+
+            instance.def.requires_caller_location(self.tcx)
+        } else {
+            false
+        }
+    }
+
+    fn report_in_closure(&mut self, expr: &Expr<'_>) {
+        let err = self.tcx.dcx().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
+            .dcx()
+            .struct_span_err(value.span, "`become` does not support operators")
+            .with_note("using `become` on a builtin operator is not useful")
+            .with_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.dcx().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
+            .dcx()
+            .struct_span_err(value.span, "`become` requires a function call")
+            .with_span_note(value.span, "not a function call")
+            .with_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
+            .dcx()
+            .struct_span_err(expr.span, "tail calling closures directly is not allowed")
+            .with_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: ExternAbi, callee_abi: ExternAbi) {
+        let err = self
+            .tcx
+            .dcx()
+            .struct_span_err(sp, "mismatched function ABIs")
+            .with_note("`become` requires caller and callee to have the same ABI")
+            .with_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
+            .dcx()
+            .struct_span_err(sp, "mismatched signatures")
+            .with_note("`become` requires caller and callee to have matching signatures")
+            .with_note(format!("caller signature: `{caller_sig}`"))
+            .with_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
+                .dcx()
+                .struct_span_err(
+                    sp,
+                    "a function marked with `#[track_caller]` cannot tail-call one that is not",
+                )
+                .emit(),
+            false => self
+                .tcx
+                .dcx()
+                .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
+            .dcx()
+            // 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
+            .dcx()
+            // 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: &'a 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/lib.rs b/compiler/rustc_mir_build/src/lib.rs
index 3dbb552cdbb..833e5019865 100644
--- a/compiler/rustc_mir_build/src/lib.rs
+++ b/compiler/rustc_mir_build/src/lib.rs
@@ -12,6 +12,7 @@
 // tidy-alphabetical-end
 
 mod build;
+mod check_tail_calls;
 mod check_unsafety;
 mod errors;
 pub mod lints;
@@ -28,6 +29,7 @@ pub fn provide(providers: &mut Providers) {
     providers.closure_saved_names_of_captured_variables =
         build::closure_saved_names_of_captured_variables;
     providers.check_unsafety = check_unsafety::check_unsafety;
+    providers.check_tail_calls = check_tail_calls::check_tail_calls;
     providers.thir_body = thir::cx::thir_body;
     providers.hooks.thir_tree = thir::print::thir_tree;
     providers.hooks.thir_flat = thir::print::thir_flat;
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 00000000000..0bbc483b2c7
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-macro.rs
@@ -0,0 +1,13 @@
+//@ check-pass
+#![allow(incomplete_features)]
+#![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 00000000000..36925d21b7a
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-operator.fixed
@@ -0,0 +1,42 @@
+//@ run-rustfix
+#![allow(incomplete_features)]
+#![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<u32>) -> Wrapping<u32> {
+    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>, _: 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 00000000000..b1af3f59df7
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-operator.rs
@@ -0,0 +1,42 @@
+//@ run-rustfix
+#![allow(incomplete_features)]
+#![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<u32>) -> Wrapping<u32> {
+    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>, _: 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 00000000000..26e4343faae
--- /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:11: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:15: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:19: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:23:12
+   |
+LL |     become !x;
+   |            ^^
+   |
+help: try using the method directly
+   |
+LL |     become (x).not();
+   |            ~ +++++++
+
+error: `become` does not support operators
+  --> $DIR/become-operator.rs:27: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:34: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:38: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-uncallable.fixed b/tests/ui/explicit-tail-calls/become-uncallable.fixed
new file mode 100644
index 00000000000..af6785e08fe
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-uncallable.fixed
@@ -0,0 +1,18 @@
+//@ run-rustfix
+#![allow(incomplete_features)]
+#![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 00000000000..60026b0d5d6
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/become-uncallable.rs
@@ -0,0 +1,18 @@
+//@ run-rustfix
+#![allow(incomplete_features)]
+#![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 00000000000..90f10b05d2a
--- /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:7:12
+   |
+LL |     become 1;
+   |     -------^
+   |     |
+   |     help: try using `return` instead: `return`
+   |
+note: not a function call
+  --> $DIR/become-uncallable.rs:7:12
+   |
+LL |     become 1;
+   |            ^
+
+error: `become` requires a function call
+  --> $DIR/become-uncallable.rs:11:12
+   |
+LL |     become { h() };
+   |     -------^^^^^^^
+   |     |
+   |     help: try using `return` instead: `return`
+   |
+note: not a function call
+  --> $DIR/become-uncallable.rs:11:12
+   |
+LL |     become { h() };
+   |            ^^^^^^^
+
+error: `become` requires a function call
+  --> $DIR/become-uncallable.rs:15:12
+   |
+LL |     become *&g();
+   |     -------^^^^^
+   |     |
+   |     help: try using `return` instead: `return`
+   |
+note: not a function call
+  --> $DIR/become-uncallable.rs:15:12
+   |
+LL |     become *&g();
+   |            ^^^^^
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/closure.fixed b/tests/ui/explicit-tail-calls/closure.fixed
new file mode 100644
index 00000000000..18384d91e0f
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/closure.fixed
@@ -0,0 +1,31 @@
+//@ run-rustfix
+#![allow(incomplete_features)]
+#![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 00000000000..b65ebed594b
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/closure.rs
@@ -0,0 +1,31 @@
+//@ run-rustfix
+#![allow(incomplete_features)]
+#![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 00000000000..5d57bece68e
--- /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:6: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:11: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:16: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:22: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/in-closure.rs b/tests/ui/explicit-tail-calls/in-closure.rs
new file mode 100644
index 00000000000..225bc0a7fd7
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/in-closure.rs
@@ -0,0 +1,8 @@
+#![allow(incomplete_features)]
+#![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 00000000000..d221f732e93
--- /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:5:8
+   |
+LL |     || become f();
+   |        ^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.rs b/tests/ui/explicit-tail-calls/signature-mismatch.rs
new file mode 100644
index 00000000000..3a01cc1ca2f
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/signature-mismatch.rs
@@ -0,0 +1,33 @@
+#![allow(incomplete_features)]
+#![feature(explicit_tail_calls)]
+#![feature(c_variadic)]
+
+fn _f0((): ()) {
+    become _g0(); //~ error: mismatched signatures
+}
+
+fn _g0() {}
+
+
+fn _f1() {
+    become _g1(()); //~ error: mismatched signatures
+}
+
+fn _g1((): ()) {}
+
+
+extern "C" fn _f2() {
+    become _g2(); //~ error: mismatched function ABIs
+}
+
+fn _g2() {}
+
+
+fn _f3() {
+    become _g3(); //~ error: mismatched function ABIs
+}
+
+extern "C" fn _g3() {}
+
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.stderr b/tests/ui/explicit-tail-calls/signature-mismatch.stderr
new file mode 100644
index 00000000000..ba9e9dcb984
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/signature-mismatch.stderr
@@ -0,0 +1,40 @@
+error: mismatched signatures
+  --> $DIR/signature-mismatch.rs:6:5
+   |
+LL |     become _g0();
+   |     ^^^^^^^^^^^^
+   |
+   = note: `become` requires caller and callee to have matching signatures
+   = note: caller signature: `fn(())`
+   = note: callee signature: `fn()`
+
+error: mismatched signatures
+  --> $DIR/signature-mismatch.rs:13:5
+   |
+LL |     become _g1(());
+   |     ^^^^^^^^^^^^^^
+   |
+   = note: `become` requires caller and callee to have matching signatures
+   = note: caller signature: `fn()`
+   = note: callee signature: `fn(())`
+
+error: mismatched function ABIs
+  --> $DIR/signature-mismatch.rs:20:5
+   |
+LL |     become _g2();
+   |     ^^^^^^^^^^^^
+   |
+   = note: `become` requires caller and callee to have the same ABI
+   = note: caller ABI is `"C"`, while callee ABI is `"Rust"`
+
+error: mismatched function ABIs
+  --> $DIR/signature-mismatch.rs:27:5
+   |
+LL |     become _g3();
+   |     ^^^^^^^^^^^^
+   |
+   = note: `become` requires caller and callee to have the same ABI
+   = note: caller ABI is `"Rust"`, while callee ABI is `"C"`
+
+error: aborting due to 4 previous errors
+