Skip to content

Commit 0833798

Browse files
committed
properly reject tail calls to &FnPtr or &FnDef
1 parent 4b55fe1 commit 0833798

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

compiler/rustc_mir_build/src/check_tail_calls.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
105105
};
106106
}
107107

108+
let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else {
109+
self.report_nonfn_callee(fn_span, self.thir[fun].span, ty);
110+
111+
// `fn_sig` below panics otherwise
112+
return;
113+
};
114+
108115
// Erase regions since tail calls don't care about lifetimes
109116
let callee_sig =
110117
self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx));
@@ -280,6 +287,31 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
280287
self.found_errors = Err(err);
281288
}
282289

290+
fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) {
291+
let mut err = self
292+
.tcx
293+
.dcx()
294+
.struct_span_err(
295+
call_sp,
296+
"tail calls can only be performed with function definitions or pointers",
297+
)
298+
.with_note(format!("callee has type `{ty}`"));
299+
300+
if ty.is_ref() {
301+
err.multipart_suggestion(
302+
"try dereferencing here",
303+
vec![
304+
(fun_sp.shrink_to_lo(), "(*".to_owned()),
305+
(fun_sp.shrink_to_hi(), ")".to_owned()),
306+
],
307+
Applicability::MachineApplicable,
308+
);
309+
}
310+
311+
let err = err.emit();
312+
self.found_errors = Err(err);
313+
}
314+
283315
fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) {
284316
let err = self
285317
.tcx
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ run-rustfix
2+
#![feature(explicit_tail_calls)]
3+
#![expect(incomplete_features)]
4+
5+
fn f() {}
6+
7+
fn g() {
8+
become (*(&f))() //~ error: tail calls can only be performed with function definitions or pointers
9+
}
10+
11+
fn h() {
12+
let table = [f as fn()];
13+
if let Some(fun) = table.get(0) {
14+
become (*fun)(); //~ error: tail calls can only be performed with function definitions or pointers
15+
}
16+
}
17+
18+
fn main() {
19+
g();
20+
h();
21+
}
22+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ run-rustfix
2+
#![feature(explicit_tail_calls)]
3+
#![expect(incomplete_features)]
4+
5+
fn f() {}
6+
7+
fn g() {
8+
become (&f)() //~ error: tail calls can only be performed with function definitions or pointers
9+
}
10+
11+
fn h() {
12+
let table = [f as fn()];
13+
if let Some(fun) = table.get(0) {
14+
become fun(); //~ error: tail calls can only be performed with function definitions or pointers
15+
}
16+
}
17+
18+
fn main() {
19+
g();
20+
h();
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: tail calls can only be performed with function definitions or pointers
2+
--> $DIR/callee_is_ref.rs:8:12
3+
|
4+
LL | become (&f)()
5+
| ^^^^^^
6+
|
7+
= note: callee has type `&fn() {f}`
8+
help: try dereferencing here
9+
|
10+
LL | become (*(&f))()
11+
| ++ +
12+
13+
error: tail calls can only be performed with function definitions or pointers
14+
--> $DIR/callee_is_ref.rs:14:16
15+
|
16+
LL | become fun();
17+
| ^^^^^
18+
|
19+
= note: callee has type `&fn()`
20+
help: try dereferencing here
21+
|
22+
LL | become (*fun)();
23+
| ++ +
24+
25+
error: aborting due to 2 previous errors
26+

0 commit comments

Comments
 (0)