Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 49 additions & 30 deletions compiler/rustc_mir_build/src/check_tail_calls.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use rustc_abi::ExternAbi;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::Applicability;
use rustc_hir::LangItem;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::{LangItem, Safety};
use rustc_infer::infer::{DefineOpaqueTypes, TyCtxtInferExt as _};
use rustc_infer::traits::ObligationCause;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::span_bug;
use rustc_middle::thir::visit::{self, Visitor};
Expand All @@ -27,8 +29,12 @@ pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), E
tcx,
thir,
found_errors: Ok(()),
// FIXME(#132279): we're clearly in a body here.
typing_env: ty::TypingEnv::non_body_analysis(tcx, def),
// FIXME(explicit_tail_calls): we are blocked on next trait solver ^^'
typing_env: if tcx.next_trait_solver_globally() {
ty::TypingEnv::new(ty::ParamEnv::empty(), ty::TypingMode::borrowck(tcx, def))
} else {
ty::TypingEnv::non_body_analysis(tcx, def)
},
is_closure,
caller_def_id: def,
};
Expand Down Expand Up @@ -139,25 +145,46 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
self.report_unsupported_abi(expr.span, callee_sig.abi());
}

// FIXME(explicit_tail_calls): this currently fails for cases where opaques are used.
// e.g.
// ```
// fn a() -> impl Sized { become b() } // ICE
// fn b() -> u8 { 0 }
// ```
// we should think what is the expected behavior here.
// (we should probably just accept this by revealing opaques?)
if caller_sig.inputs_and_output != callee_sig.inputs_and_output {
let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder();

self.report_signature_mismatch(
expr.span,
self.tcx.liberate_late_bound_regions(
CRATE_DEF_ID.to_def_id(),
caller_ty.fn_sig(self.tcx),
),
self.tcx.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)),
);
if caller_sig.c_variadic() {
self.report_c_variadic_caller(expr.span);
}

if callee_sig.c_variadic() {
self.report_c_variadic_callee(expr.span);
}

if let Err(ErrorGuaranteed { .. }) = self.found_errors {
// We do a lot of special-cased errors above; equating signatures below would duplicate
// them, so bail out if we emitted any errors so far.
return;
}

// Checks that the signatures of the caller and callee match (as a proxy to check that they
// are ABI compatible and tail calls can always happen).
//
// This ignores lifetimes and safety, as those do not affect ABI.
{
let (infcx, param_env) = self.tcx.infer_ctxt().build_with_typing_env(self.typing_env);
let cause = ObligationCause::misc(expr.span, self.caller_def_id);

// Erase safety, as it never affects ABI and is thus irrelevant for tail calls
let caller_sig = caller_sig.set_safety(Safety::Safe);
let callee_sig = callee_sig.set_safety(Safety::Safe);

if let Err(_e) =
infcx.at(&cause, param_env).sub(DefineOpaqueTypes::No, caller_sig, callee_sig)
{
let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder();
self.report_signature_mismatch(
expr.span,
self.tcx.liberate_late_bound_regions(
CRATE_DEF_ID.to_def_id(),
caller_ty.fn_sig(self.tcx),
),
self.tcx
.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)),
);
}
}

{
Expand All @@ -181,14 +208,6 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
self.report_track_caller_caller(expr.span);
}
}

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 the caller function needs a location argument
Expand Down
33 changes: 18 additions & 15 deletions tests/ui/explicit-tail-calls/caller-lifetime-presence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,42 @@
#![feature(explicit_tail_calls)]
#![allow(incomplete_features)]

fn foo<'a>(_: fn(&'a ())) {
become bar(dummy);
struct A;
struct B;

fn foo<'a>(_: fn(&'a ()), _: A) {
become bar(dummy, B);
//~^ ERROR mismatched signatures
//~| NOTE `become` requires caller and callee to have matching signatures
//~| NOTE caller signature: `fn(fn(&'a ()))`
//~| NOTE callee signature: `fn(for<'a> fn(&'a ()))`
//~| NOTE caller signature: `fn(fn(&'a ()), A)`
//~| NOTE callee signature: `fn(for<'a> fn(&'a ()), B)`
}

fn bar(_: fn(&())) {}
fn bar(_: fn(&()), _: B) {}

fn dummy(_: &()) {}

fn foo_(_: fn(&())) {
become bar1(dummy2);
fn foo2(_: fn(&()), _: A) {
become bar2(dummy2, B);
//~^ ERROR mismatched signatures
//~| NOTE `become` requires caller and callee to have matching signatures
//~| NOTE caller signature: `fn(for<'a> fn(&'a ()))`
//~| NOTE callee signature: `fn(fn(&'a ()))`
//~| NOTE caller signature: `fn(for<'a> fn(&'a ()), A)`
//~| NOTE callee signature: `fn(fn(&'a ()), B)`
}

fn bar1<'a>(_: fn(&'a ())) {}
fn bar2<'a>(_: fn(&'a ()), _: B) {}

fn dummy2(_: &()) {}

fn foo__(_: fn(&'static ())) {
become bar(dummy3);
fn foo3(_: fn(&'static ()), _: A) {
become bar3(dummy3, B);
//~^ ERROR mismatched signatures
//~| NOTE `become` requires caller and callee to have matching signatures
//~| NOTE caller signature: `fn(fn(&'static ()))`
//~| NOTE callee signature: `fn(for<'a> fn(&'a ()))`
//~| NOTE caller signature: `fn(fn(&'static ()), A)`
//~| NOTE callee signature: `fn(for<'a> fn(&'a ()), B)`
}

fn bar2(_: fn(&())) {}
fn bar3(_: fn(&()), _: B) {}

fn dummy3(_: &()) {}

Expand Down
30 changes: 15 additions & 15 deletions tests/ui/explicit-tail-calls/caller-lifetime-presence.stderr
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
error: mismatched signatures
--> $DIR/caller-lifetime-presence.rs:16:5
--> $DIR/caller-lifetime-presence.rs:19:5
|
LL | become bar(dummy);
| ^^^^^^^^^^^^^^^^^
LL | become bar(dummy, B);
| ^^^^^^^^^^^^^^^^^^^^
|
= note: `become` requires caller and callee to have matching signatures
= note: caller signature: `fn(fn(&'a ()))`
= note: callee signature: `fn(for<'a> fn(&'a ()))`
= note: caller signature: `fn(fn(&'a ()), A)`
= note: callee signature: `fn(for<'a> fn(&'a ()), B)`

error: mismatched signatures
--> $DIR/caller-lifetime-presence.rs:28:5
--> $DIR/caller-lifetime-presence.rs:31:5
|
LL | become bar1(dummy2);
| ^^^^^^^^^^^^^^^^^^^
LL | become bar2(dummy2, B);
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: `become` requires caller and callee to have matching signatures
= note: caller signature: `fn(for<'a> fn(&'a ()))`
= note: callee signature: `fn(fn(&'a ()))`
= note: caller signature: `fn(for<'a> fn(&'a ()), A)`
= note: callee signature: `fn(fn(&'a ()), B)`

error: mismatched signatures
--> $DIR/caller-lifetime-presence.rs:40:5
--> $DIR/caller-lifetime-presence.rs:43:5
|
LL | become bar(dummy3);
| ^^^^^^^^^^^^^^^^^^
LL | become bar3(dummy3, B);
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: `become` requires caller and callee to have matching signatures
= note: caller signature: `fn(fn(&'static ()))`
= note: callee signature: `fn(for<'a> fn(&'a ()))`
= note: caller signature: `fn(fn(&'static ()), A)`
= note: callee signature: `fn(for<'a> fn(&'a ()), B)`

error: aborting due to 3 previous errors

2 changes: 1 addition & 1 deletion tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

fn foo() -> for<'a> fn(&'a i32) {
become bar();
//~^ ERROR mismatched signatures
//~^ ERROR mismatched types
}

fn bar() -> fn(&'static i32) {
Expand Down
10 changes: 5 additions & 5 deletions tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.stderr
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
error: mismatched signatures
error[E0308]: mismatched types
--> $DIR/ret-ty-hr-mismatch.rs:5:5
|
LL | become bar();
| ^^^^^^^^^^^^
| ^^^^^^^^^^^^ one type is more general than the other
|
= note: `become` requires caller and callee to have matching signatures
= note: caller signature: `fn() -> for<'a> fn(&'a i32)`
= note: callee signature: `fn() -> fn(&'static i32)`
= note: expected fn pointer `for<'a> fn(&'a _)`
found fn pointer `fn(&'static _)`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: mismatched signatures
--> $DIR/rpit.rs:14:5
--> $DIR/rpit.rs:19:5
|
LL | become foo(x, y);
| ^^^^^^^^^^^^^^^^
Expand Down
7 changes: 6 additions & 1 deletion tests/ui/explicit-tail-calls/rpit.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
//@[next] check-pass

#![feature(explicit_tail_calls)]
#![expect(incomplete_features)]

Expand All @@ -12,7 +17,7 @@ fn foo(x: u32, y: u32) -> u32 {

fn bar(x: u32, y: u32) -> impl ToString {
become foo(x, y);
//~^ ERROR mismatched signatures
//[current]~^ ERROR mismatched signatures
}
Copy link
Copy Markdown
Contributor

@folkertdev folkertdev Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc #144953

What exactly makes RPIT not work for the old trait solver?

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure (lcnr can probably explain better), but something something opaque types are more annoying to handle in the old solver and in this case they are not being revealed as their hidden type.


fn main() {
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/explicit-tail-calls/safety.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Checks that you can tail call functions with different unsafety
//
//@ check-pass
#![feature(explicit_tail_calls)]
#![expect(incomplete_features)]

fn a() {
unsafe {
become b();
}
}

unsafe fn b() {
become c();
}

fn c() {}

fn main() {}
28 changes: 28 additions & 0 deletions tests/ui/explicit-tail-calls/subtyping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Check that signature comparison for tail calls allows subtyping.
//
//@ check-pass
#![expect(incomplete_features)]
#![feature(explicit_tail_calls)]

fn f<'short>(
a: &'static (),
b: &'short (),
c: fn(&'static ()),
d: for<'a> fn(&'a ()),
) -> (&'short (), fn(&'static ()), for<'a> fn(&'a ())) {
become g(b, a, d, c);
}

fn g<'short>(
// swapped short/static
a: &'short (),
b: &'static (),
// swapped binder/non binder
c: for<'a> fn(&'a ()),
d: fn(&'static ()),
// 'short=>'static; non binder=>binder
) -> (&'static (), for<'a> fn(&'a ()), for<'a> fn(&'a ())) {
(b, c, c)
}

fn main() {}
Loading