properly reject tail calls to `&FnPtr` or `&FnDef`
diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs
index 6ed1008..b4c8b20 100644
--- a/compiler/rustc_mir_build/src/check_tail_calls.rs
+++ b/compiler/rustc_mir_build/src/check_tail_calls.rs
@@ -95,9 +95,15 @@ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
// So we have to check for them in this weird way...
let parent = self.tcx.parent(did);
if self.tcx.fn_trait_kind_from_def_id(parent).is_some()
- && args.first().and_then(|arg| arg.as_type()).is_some_and(Ty::is_closure)
+ && let Some(this) = args.first()
+ && let Some(this) = this.as_type()
{
- self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr);
+ if this.is_closure() {
+ self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr);
+ } else {
+ // This can happen when tail calling `Box` that wraps a function
+ self.report_nonfn_callee(fn_span, self.thir[fun].span, this);
+ }
// Tail calling is likely to cause unrelated errors (ABI, argument mismatches),
// skip them, producing an error about calling a closure is enough.
@@ -109,6 +115,13 @@ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
}
}
+ let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else {
+ self.report_nonfn_callee(fn_span, self.thir[fun].span, ty);
+
+ // `fn_sig` below panics otherwise
+ 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));
@@ -294,6 +307,40 @@ fn report_calling_intrinsic(&mut self, expr: &Expr<'_>) {
self.found_errors = Err(err);
}
+ fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) {
+ let mut err = self
+ .tcx
+ .dcx()
+ .struct_span_err(
+ call_sp,
+ "tail calls can only be performed with function definitions or pointers",
+ )
+ .with_note(format!("callee has type `{ty}`"));
+
+ let mut ty = ty;
+ let mut refs = 0;
+ while ty.is_box() || ty.is_ref() {
+ ty = ty.builtin_deref(false).unwrap();
+ refs += 1;
+ }
+
+ if refs > 0 && ty.is_fn() {
+ let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" };
+
+ let derefs =
+ std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>();
+
+ err.multipart_suggestion(
+ format!("consider dereferencing the expression to get a function {thing}"),
+ vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())],
+ Applicability::MachineApplicable,
+ );
+ }
+
+ let err = err.emit();
+ self.found_errors = Err(err);
+ }
+
fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) {
let err = self
.tcx
diff --git a/tests/ui/explicit-tail-calls/callee_is_ref.fixed b/tests/ui/explicit-tail-calls/callee_is_ref.fixed
new file mode 100644
index 0000000..7525e5c
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_ref.fixed
@@ -0,0 +1,26 @@
+//@ run-rustfix
+#![feature(explicit_tail_calls)]
+#![expect(incomplete_features)]
+
+fn f() {}
+
+fn g() {
+ become (*(&f))() //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn h() {
+ let table = [f as fn()];
+ if let Some(fun) = table.get(0) {
+ become (*fun)(); //~ error: tail calls can only be performed with function definitions or pointers
+ }
+}
+
+fn i() {
+ become (***Box::new(&mut &f))(); //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn main() {
+ g();
+ h();
+ i();
+}
diff --git a/tests/ui/explicit-tail-calls/callee_is_ref.rs b/tests/ui/explicit-tail-calls/callee_is_ref.rs
new file mode 100644
index 0000000..36bf9ef
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_ref.rs
@@ -0,0 +1,26 @@
+//@ run-rustfix
+#![feature(explicit_tail_calls)]
+#![expect(incomplete_features)]
+
+fn f() {}
+
+fn g() {
+ become (&f)() //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn h() {
+ let table = [f as fn()];
+ if let Some(fun) = table.get(0) {
+ become fun(); //~ error: tail calls can only be performed with function definitions or pointers
+ }
+}
+
+fn i() {
+ become Box::new(&mut &f)(); //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn main() {
+ g();
+ h();
+ i();
+}
diff --git a/tests/ui/explicit-tail-calls/callee_is_ref.stderr b/tests/ui/explicit-tail-calls/callee_is_ref.stderr
new file mode 100644
index 0000000..4a2ff46
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_ref.stderr
@@ -0,0 +1,38 @@
+error: tail calls can only be performed with function definitions or pointers
+ --> $DIR/callee_is_ref.rs:8:12
+ |
+LL | become (&f)()
+ | ^^^^^^
+ |
+ = note: callee has type `&fn() {f}`
+help: consider dereferencing the expression to get a function definition
+ |
+LL | become (*(&f))()
+ | ++ +
+
+error: tail calls can only be performed with function definitions or pointers
+ --> $DIR/callee_is_ref.rs:14:16
+ |
+LL | become fun();
+ | ^^^^^
+ |
+ = note: callee has type `&fn()`
+help: consider dereferencing the expression to get a function pointer
+ |
+LL | become (*fun)();
+ | ++ +
+
+error: tail calls can only be performed with function definitions or pointers
+ --> $DIR/callee_is_ref.rs:19:12
+ |
+LL | become Box::new(&mut &f)();
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = note: callee has type `Box<&mut &fn() {f}>`
+help: consider dereferencing the expression to get a function definition
+ |
+LL | become (***Box::new(&mut &f))();
+ | ++++ +
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/explicit-tail-calls/callee_is_weird.rs b/tests/ui/explicit-tail-calls/callee_is_weird.rs
new file mode 100644
index 0000000..b3ca878
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_weird.rs
@@ -0,0 +1,29 @@
+#![feature(explicit_tail_calls, exclusive_wrapper, fn_traits, unboxed_closures)]
+#![expect(incomplete_features)]
+
+fn f() {}
+
+fn g() {
+ become std::sync::Exclusive::new(f)() //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn h() {
+ become (&mut &std::sync::Exclusive::new(f))() //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn i() {
+ struct J;
+
+ impl FnOnce<()> for J {
+ type Output = ();
+ extern "rust-call" fn call_once(self, (): ()) -> Self::Output {}
+ }
+
+ become J(); //~ error: tail calls can only be performed with function definitions or pointers
+}
+
+fn main() {
+ g();
+ h();
+ i();
+}
diff --git a/tests/ui/explicit-tail-calls/callee_is_weird.stderr b/tests/ui/explicit-tail-calls/callee_is_weird.stderr
new file mode 100644
index 0000000..a4e5a38
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_weird.stderr
@@ -0,0 +1,26 @@
+error: tail calls can only be performed with function definitions or pointers
+ --> $DIR/callee_is_weird.rs:7:12
+ |
+LL | become std::sync::Exclusive::new(f)()
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: callee has type `Exclusive<fn() {f}>`
+
+error: tail calls can only be performed with function definitions or pointers
+ --> $DIR/callee_is_weird.rs:11:12
+ |
+LL | become (&mut &std::sync::Exclusive::new(f))()
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: callee has type `Exclusive<fn() {f}>`
+
+error: tail calls can only be performed with function definitions or pointers
+ --> $DIR/callee_is_weird.rs:22:12
+ |
+LL | become J();
+ | ^^^
+ |
+ = note: callee has type `J`
+
+error: aborting due to 3 previous errors
+