Rollup merge of #144817 - WaffleLapkin:reject-referety, r=Urgau

Properly reject tail calls to `&FnPtr` or `&FnDef`

Fixes rust-lang/rust#144795
This commit is contained in:
Samuel Tardieu 2025-08-05 03:51:36 +02:00 committed by GitHub
commit eee8d775fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 195 additions and 3 deletions

View file

@ -1456,7 +1456,7 @@ impl<'tcx> Ty<'tcx> {
}
}
/// Returns the type and mutability of `*ty`.
/// Returns the type of `*ty`.
///
/// The parameter `explicit` indicates if this is an *explicit* dereference.
/// Some types -- notably raw ptrs -- can only be dereferenced explicitly.

View file

@ -95,9 +95,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
// 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 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
}
}
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 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
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

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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

View file

@ -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();
}

View file

@ -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