From c1dee68378aa51642f22845844c245003c3b9824 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 30 Apr 2026 12:31:07 +0200 Subject: [PATCH 1/7] do proper type equality of signatures for tail calls --- .../rustc_mir_build/src/check_tail_calls.rs | 66 ++++++++++++------- .../caller-lifetime-presence.rs | 33 +++++----- .../caller-lifetime-presence.stderr | 30 ++++----- .../explicit-tail-calls/ret-ty-hr-mismatch.rs | 2 +- .../ret-ty-hr-mismatch.stderr | 10 +-- 5 files changed, 83 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 26deba3f58c5a..746487ad570e3 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -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}; @@ -138,6 +140,20 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { self.report_unsupported_abi(expr.span, callee_sig.abi()); } + 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; + } + // FIXME(explicit_tail_calls): this currently fails for cases where opaques are used. // e.g. // ``` @@ -146,19 +162,33 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { // ``` // 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 - && !matches!(callee_sig.abi(), ExternAbi::RustTail) - { - 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)), - ); + // 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. + if !matches!(callee_sig.abi(), ExternAbi::RustTail) { + 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 + // TODO: check if there is a test for this + 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)), + ); + } } { @@ -183,14 +213,6 @@ impl<'tcx> TailCallCkVisitor<'_, '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); - } - for &arg_ty in callee_sig.inputs() { if !arg_ty.is_sized(self.tcx, self.typing_env) { self.report_unsized_argument(expr.span, arg_ty); diff --git a/tests/ui/explicit-tail-calls/caller-lifetime-presence.rs b/tests/ui/explicit-tail-calls/caller-lifetime-presence.rs index 2382bc9dd1e05..565faa6845cd3 100644 --- a/tests/ui/explicit-tail-calls/caller-lifetime-presence.rs +++ b/tests/ui/explicit-tail-calls/caller-lifetime-presence.rs @@ -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(_: &()) {} diff --git a/tests/ui/explicit-tail-calls/caller-lifetime-presence.stderr b/tests/ui/explicit-tail-calls/caller-lifetime-presence.stderr index 2fb981d968206..1f0009e854844 100644 --- a/tests/ui/explicit-tail-calls/caller-lifetime-presence.stderr +++ b/tests/ui/explicit-tail-calls/caller-lifetime-presence.stderr @@ -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 diff --git a/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.rs b/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.rs index 8ad244568a3ee..d78be669c6ec5 100644 --- a/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.rs +++ b/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.rs @@ -3,7 +3,7 @@ fn foo() -> for<'a> fn(&'a i32) { become bar(); - //~^ ERROR mismatched signatures + //~^ ERROR mismatched types } fn bar() -> fn(&'static i32) { diff --git a/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.stderr b/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.stderr index f6594580ba52d..39c31bfa71f03 100644 --- a/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.stderr +++ b/tests/ui/explicit-tail-calls/ret-ty-hr-mismatch.stderr @@ -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`. From 8404b7453a25443198d287c49e6b353603241f0d Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 30 Apr 2026 12:31:07 +0200 Subject: [PATCH 2/7] allow tail calls with rpit in next solver --- compiler/rustc_mir_build/src/check_tail_calls.rs | 10 +--------- .../{rpit.stderr => rpit.current.stderr} | 2 +- tests/ui/explicit-tail-calls/rpit.rs | 7 ++++++- 3 files changed, 8 insertions(+), 11 deletions(-) rename tests/ui/explicit-tail-calls/{rpit.stderr => rpit.current.stderr} (93%) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 746487ad570e3..f459f93536c67 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -29,7 +29,7 @@ pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), E tcx, thir, found_errors: Ok(()), - typing_env: ty::TypingEnv::post_typeck_until_borrowck_for_mir_build(tcx, def), + typing_env: ty::TypingEnv::post_typeck_unil_borrowck_for_mir_build(tcx, def), is_closure, caller_def_id: def, }; @@ -154,14 +154,6 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { return; } - // 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?) // 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). // diff --git a/tests/ui/explicit-tail-calls/rpit.stderr b/tests/ui/explicit-tail-calls/rpit.current.stderr similarity index 93% rename from tests/ui/explicit-tail-calls/rpit.stderr rename to tests/ui/explicit-tail-calls/rpit.current.stderr index 9c181db8b8019..ffb82c60ce844 100644 --- a/tests/ui/explicit-tail-calls/rpit.stderr +++ b/tests/ui/explicit-tail-calls/rpit.current.stderr @@ -1,5 +1,5 @@ error: mismatched signatures - --> $DIR/rpit.rs:14:5 + --> $DIR/rpit.rs:19:5 | LL | become foo(x, y); | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/explicit-tail-calls/rpit.rs b/tests/ui/explicit-tail-calls/rpit.rs index 0d1f2c78fd13d..550a88996072f 100644 --- a/tests/ui/explicit-tail-calls/rpit.rs +++ b/tests/ui/explicit-tail-calls/rpit.rs @@ -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)] @@ -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 } fn main() { From 6cdf5a81f9886a49e1804239f63e7d6afb3eed40 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 30 Apr 2026 17:28:09 +0200 Subject: [PATCH 3/7] add a test --- .../rustc_mir_build/src/check_tail_calls.rs | 1 - tests/ui/explicit-tail-calls/safety.rs | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/ui/explicit-tail-calls/safety.rs diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index f459f93536c67..a1f688be0a6ed 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -163,7 +163,6 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { let cause = ObligationCause::misc(expr.span, self.caller_def_id); // Erase safety, as it never affects ABI and is thus irrelevant for tail calls - // TODO: check if there is a test for this let caller_sig = caller_sig.set_safety(Safety::Safe); let callee_sig = callee_sig.set_safety(Safety::Safe); diff --git a/tests/ui/explicit-tail-calls/safety.rs b/tests/ui/explicit-tail-calls/safety.rs new file mode 100644 index 0000000000000..593c448c09c05 --- /dev/null +++ b/tests/ui/explicit-tail-calls/safety.rs @@ -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() {} From 3985d8f81c00be78e745118673156c6e88260f1e Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 1 May 2026 16:16:24 +0200 Subject: [PATCH 4/7] add a test for subtyping in tail call signature check --- tests/ui/explicit-tail-calls/subtyping.rs | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/subtyping.rs diff --git a/tests/ui/explicit-tail-calls/subtyping.rs b/tests/ui/explicit-tail-calls/subtyping.rs new file mode 100644 index 0000000000000..c6034a6a9c3cd --- /dev/null +++ b/tests/ui/explicit-tail-calls/subtyping.rs @@ -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() {} From 64accfd5d25ad1f1208ad14dca7c01472994a647 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 4 Jun 2026 14:12:10 +0200 Subject: [PATCH 5/7] check that we are using correct param_env for tail call checks --- .../tail-call-with-projection-arg.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/tail-call-with-projection-arg.rs diff --git a/tests/ui/explicit-tail-calls/tail-call-with-projection-arg.rs b/tests/ui/explicit-tail-calls/tail-call-with-projection-arg.rs new file mode 100644 index 0000000000000..1409fac618776 --- /dev/null +++ b/tests/ui/explicit-tail-calls/tail-call-with-projection-arg.rs @@ -0,0 +1,21 @@ +// Check that tail call checks correctly handle projections in signatures. +// (this was briefly broken while working on #156007) +// +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass +#![expect(incomplete_features)] +#![feature(explicit_tail_calls)] + +trait Trait { + type Assoc; +} + +fn a(_: T::Assoc) {} + +fn b(x: T::Assoc) { + become a::(x) +} + +fn main() {} From 4e106b821e2d539cd4f27ede27590d3528882206 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 11 Jun 2026 16:31:53 +0200 Subject: [PATCH 6/7] refactor tail call checks to use results for early returns --- .../rustc_mir_build/src/check_tail_calls.rs | 177 ++++++++++-------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index a1f688be0a6ed..cf6d59149b894 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -53,10 +53,9 @@ struct TailCallCkVisitor<'a, 'tcx> { } impl<'tcx> TailCallCkVisitor<'_, 'tcx> { - fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) { + fn check_tail_call(&self, call: &Expr<'_>, expr: &Expr<'_>) -> Result<(), ErrorGuaranteed> { if self.is_closure { - self.report_in_closure(expr); - return; + self.report_in_closure(expr)? as !; } let BodyTy::Fn(caller_sig) = self.thir.body_type else { @@ -82,17 +81,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { | ExprKind::AssignOp { .. } | ExprKind::Index { .. } ) { - self.report_builtin_op(call, expr); - return; + self.report_builtin_op(call, expr)? as !; } let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind else { - self.report_non_call(value, expr); - return; + self.report_non_call(value, expr)? as !; }; if !from_hir_call { - self.report_op(ty, args, fn_span, expr); + self.report_op(ty, args, fn_span, expr)? as !; } if let &ty::FnDef(did, args) = ty.kind() { @@ -104,54 +101,56 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { && let Some(this) = args.first() && let Some(this) = this.as_type() { - if this.is_closure() { - self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr); + let err = 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); - } + 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. - return; + err? as !; }; if self.tcx.intrinsic(did).is_some() { - self.report_calling_intrinsic(expr); + self.report_calling_intrinsic(expr)? as !; } } 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; + // early return, as `fn_sig` below panics otherwise + self.report_nonfn_callee(fn_span, self.thir[fun].span, ty)? as !; }; // 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()); - } + let mut found_errors = Ok(()); - if !callee_sig.abi().supports_guaranteed_tail_call() { - self.report_unsupported_abi(expr.span, callee_sig.abi()); - } + { + if caller_sig.abi() != callee_sig.abi() { + found_errors = self + .report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi()) + .map(|x| x); + } - if caller_sig.c_variadic() { - self.report_c_variadic_caller(expr.span); - } + if !callee_sig.abi().supports_guaranteed_tail_call() { + found_errors = self.report_unsupported_abi(expr.span, callee_sig.abi()).map(|x| x); + } - if callee_sig.c_variadic() { - self.report_c_variadic_callee(expr.span); - } + if caller_sig.c_variadic() { + found_errors = self.report_c_variadic_caller(expr.span).map(|x| x); + } + + if callee_sig.c_variadic() { + found_errors = self.report_c_variadic_callee(expr.span).map(|x| x); + } - 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; + found_errors?; } // Checks that the signatures of the caller and callee match (as a proxy to check that they @@ -170,15 +169,19 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { 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)), - ); + found_errors = 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), + ), + ) + .map(|x| x); } } @@ -200,15 +203,17 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { let caller_needs_location = self.caller_needs_location(); if caller_needs_location { - self.report_track_caller_caller(expr.span); + found_errors = self.report_track_caller_caller(expr.span).map(|x| x); } } for &arg_ty in callee_sig.inputs() { if !arg_ty.is_sized(self.tcx, self.typing_env) { - self.report_unsized_argument(expr.span, arg_ty); + found_errors = self.report_unsized_argument(expr.span, arg_ty).map(|x| x); } } + + found_errors } /// Returns true if the caller function needs a location argument @@ -218,12 +223,12 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { flags.contains(CodegenFnAttrFlags::TRACK_CALLER) } - fn report_in_closure(&mut self, expr: &Expr<'_>) { + fn report_in_closure(&self, expr: &Expr<'_>) -> Result { let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures"); - self.found_errors = Err(err); + Err(err) } - fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { + fn report_builtin_op(&self, value: &Expr<'_>, expr: &Expr<'_>) -> Result { let err = self .tcx .dcx() @@ -236,10 +241,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { Applicability::MachineApplicable, ) .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) { + fn report_op( + &self, + fun_ty: Ty<'_>, + args: &[ExprId], + fn_span: Span, + expr: &Expr<'_>, + ) -> Result { let mut err = self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators"); @@ -279,10 +290,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { } } - self.found_errors = Err(err.emit()); + Err(err.emit()) } - fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { + fn report_non_call(&self, value: &Expr<'_>, expr: &Expr<'_>) -> Result { let err = self .tcx .dcx() @@ -295,10 +306,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { Applicability::MaybeIncorrect, ) .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) { + fn report_calling_closure( + &self, + fun: &Expr<'_>, + tupled_args: Ty<'_>, + expr: &Expr<'_>, + ) -> Result { let underscored_args = match tupled_args.kind() { ty::Tuple(tys) if tys.is_empty() => "".to_owned(), ty::Tuple(tys) => std::iter::repeat_n("_, ", tys.len() - 1).chain(["_"]).collect(), @@ -318,20 +334,25 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { Applicability::MaybeIncorrect, ) .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_calling_intrinsic(&mut self, expr: &Expr<'_>) { + fn report_calling_intrinsic(&self, expr: &Expr<'_>) -> Result { let err = self .tcx .dcx() .struct_span_err(expr.span, "tail calling intrinsics is not allowed") .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) { + fn report_nonfn_callee( + &self, + call_sp: Span, + fun_sp: Span, + ty: Ty<'_>, + ) -> Result { let mut err = self .tcx .dcx() @@ -361,11 +382,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { ); } - let err = err.emit(); - self.found_errors = Err(err); + Err(err.emit()) } - fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) { + fn report_abi_mismatch( + &self, + sp: Span, + caller_abi: ExternAbi, + callee_abi: ExternAbi, + ) -> Result { let err = self .tcx .dcx() @@ -373,25 +398,29 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { .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); + Err(err) } - fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) { + fn report_unsupported_abi( + &self, + sp: Span, + callee_abi: ExternAbi, + ) -> Result { let err = self .tcx .dcx() .struct_span_err(sp, "ABI does not support guaranteed tail calls") .with_note(format!("`become` is not supported for `extern {callee_abi}` functions")) .emit(); - self.found_errors = Err(err); + Err(err) } fn report_signature_mismatch( - &mut self, + &self, sp: Span, caller_sig: ty::FnSig<'_>, callee_sig: ty::FnSig<'_>, - ) { + ) -> Result { let err = self .tcx .dcx() @@ -400,10 +429,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { .with_note(format!("caller signature: `{caller_sig}`")) .with_note(format!("callee signature: `{callee_sig}`")) .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_track_caller_caller(&mut self, sp: Span) { + fn report_track_caller_caller(&self, sp: Span) -> Result { let err = self .tcx .dcx() @@ -413,10 +442,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { ) .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_c_variadic_caller(&mut self, sp: Span) { + fn report_c_variadic_caller(&self, sp: Span) -> Result { let err = self .tcx .dcx() @@ -424,10 +453,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions") .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_c_variadic_callee(&mut self, sp: Span) { + fn report_c_variadic_callee(&self, sp: Span) -> Result { let err = self .tcx .dcx() @@ -435,10 +464,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { .struct_span_err(sp, "c-variadic functions can't be tail-called") .emit(); - self.found_errors = Err(err); + Err(err) } - fn report_unsized_argument(&mut self, sp: Span, arg_ty: Ty<'tcx>) { + fn report_unsized_argument(&self, sp: Span, arg_ty: Ty<'tcx>) -> Result { let err = self .tcx .dcx() @@ -446,7 +475,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { .with_note(format!("unsized argument of type `{arg_ty}`")) .emit(); - self.found_errors = Err(err); + Err(err) } } @@ -459,7 +488,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { ensure_sufficient_stack(|| { if let ExprKind::Become { value } = expr.kind { let call = &self.thir[value]; - self.check_tail_call(call, expr); + self.found_errors = self.check_tail_call(call, expr).and(self.found_errors); } visit::walk_expr(self, expr); From b4e151436e56a0b186ad23a3d42079ccabf3a818 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 9 Jul 2026 20:03:58 +0200 Subject: [PATCH 7/7] change the style of early returns --- .../rustc_mir_build/src/check_tail_calls.rs | 169 +++++++----------- 1 file changed, 62 insertions(+), 107 deletions(-) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index cf6d59149b894..bdf50ca702f80 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -29,7 +29,7 @@ pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), E tcx, thir, found_errors: Ok(()), - typing_env: ty::TypingEnv::post_typeck_unil_borrowck_for_mir_build(tcx, def), + typing_env: ty::TypingEnv::post_typeck_until_borrowck_for_mir_build(tcx, def), is_closure, caller_def_id: def, }; @@ -55,7 +55,7 @@ struct TailCallCkVisitor<'a, 'tcx> { impl<'tcx> TailCallCkVisitor<'_, 'tcx> { fn check_tail_call(&self, call: &Expr<'_>, expr: &Expr<'_>) -> Result<(), ErrorGuaranteed> { if self.is_closure { - self.report_in_closure(expr)? as !; + return Err(self.report_in_closure(expr)); } let BodyTy::Fn(caller_sig) = self.thir.body_type else { @@ -81,15 +81,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { | ExprKind::AssignOp { .. } | ExprKind::Index { .. } ) { - self.report_builtin_op(call, expr)? as !; + return Err(self.report_builtin_op(call, expr)); } let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind else { - self.report_non_call(value, expr)? as !; + return Err(self.report_non_call(value, expr)); }; if !from_hir_call { - self.report_op(ty, args, fn_span, expr)? as !; + return Err(self.report_op(ty, args, fn_span, expr)); } if let &ty::FnDef(did, args) = ty.kind() { @@ -110,17 +110,17 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { // Tail calling is likely to cause unrelated errors (ABI, argument mismatches), // skip them, producing an error about calling a closure is enough. - err? as !; + return Err(err); }; if self.tcx.intrinsic(did).is_some() { - self.report_calling_intrinsic(expr)? as !; + return Err(self.report_calling_intrinsic(expr)); } } let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else { // early return, as `fn_sig` below panics otherwise - self.report_nonfn_callee(fn_span, self.thir[fun].span, ty)? as !; + return Err(self.report_nonfn_callee(fn_span, self.thir[fun].span, ty)); }; // Erase regions since tail calls don't care about lifetimes @@ -131,21 +131,20 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { { if caller_sig.abi() != callee_sig.abi() { - found_errors = self - .report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi()) - .map(|x| x); + found_errors = + Err(self.report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi())); } if !callee_sig.abi().supports_guaranteed_tail_call() { - found_errors = self.report_unsupported_abi(expr.span, callee_sig.abi()).map(|x| x); + found_errors = Err(self.report_unsupported_abi(expr.span, callee_sig.abi())); } if caller_sig.c_variadic() { - found_errors = self.report_c_variadic_caller(expr.span).map(|x| x); + found_errors = Err(self.report_c_variadic_caller(expr.span)); } if callee_sig.c_variadic() { - found_errors = self.report_c_variadic_callee(expr.span).map(|x| x); + found_errors = Err(self.report_c_variadic_callee(expr.span)); } // We do a lot of special-cased errors above; equating signatures below would duplicate @@ -169,19 +168,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { 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(); - found_errors = 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), - ), - ) - .map(|x| x); + found_errors = Err(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)), + )); } } @@ -203,13 +198,13 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { let caller_needs_location = self.caller_needs_location(); if caller_needs_location { - found_errors = self.report_track_caller_caller(expr.span).map(|x| x); + found_errors = Err(self.report_track_caller_caller(expr.span)); } } for &arg_ty in callee_sig.inputs() { if !arg_ty.is_sized(self.tcx, self.typing_env) { - found_errors = self.report_unsized_argument(expr.span, arg_ty).map(|x| x); + found_errors = Err(self.report_unsized_argument(expr.span, arg_ty)); } } @@ -223,14 +218,12 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { flags.contains(CodegenFnAttrFlags::TRACK_CALLER) } - fn report_in_closure(&self, expr: &Expr<'_>) -> Result { - let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures"); - Err(err) + fn report_in_closure(&self, expr: &Expr<'_>) -> ErrorGuaranteed { + self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures") } - fn report_builtin_op(&self, value: &Expr<'_>, expr: &Expr<'_>) -> Result { - let err = self - .tcx + fn report_builtin_op(&self, value: &Expr<'_>, expr: &Expr<'_>) -> ErrorGuaranteed { + self.tcx .dcx() .struct_span_err(value.span, "`become` does not support operators") .with_note("using `become` on a builtin operator is not useful") @@ -240,8 +233,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { "return ", Applicability::MachineApplicable, ) - .emit(); - Err(err) + .emit() } fn report_op( @@ -250,7 +242,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { args: &[ExprId], fn_span: Span, expr: &Expr<'_>, - ) -> Result { + ) -> ErrorGuaranteed { let mut err = self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators"); @@ -290,12 +282,11 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { } } - Err(err.emit()) + err.emit() } - fn report_non_call(&self, value: &Expr<'_>, expr: &Expr<'_>) -> Result { - let err = self - .tcx + fn report_non_call(&self, value: &Expr<'_>, expr: &Expr<'_>) -> ErrorGuaranteed { + self.tcx .dcx() .struct_span_err(value.span, "`become` requires a function call") .with_span_note(value.span, "not a function call") @@ -305,8 +296,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { "return ", Applicability::MaybeIncorrect, ) - .emit(); - Err(err) + .emit() } fn report_calling_closure( @@ -314,15 +304,14 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>, - ) -> Result { + ) -> ErrorGuaranteed { let underscored_args = match tupled_args.kind() { ty::Tuple(tys) if tys.is_empty() => "".to_owned(), ty::Tuple(tys) => std::iter::repeat_n("_, ", tys.len() - 1).chain(["_"]).collect(), _ => "_".to_owned(), }; - let err = self - .tcx + self.tcx .dcx() .struct_span_err(expr.span, "tail calling closures directly is not allowed") .with_multipart_suggestion( @@ -333,26 +322,14 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { ], Applicability::MaybeIncorrect, ) - .emit(); - Err(err) + .emit() } - fn report_calling_intrinsic(&self, expr: &Expr<'_>) -> Result { - let err = self - .tcx - .dcx() - .struct_span_err(expr.span, "tail calling intrinsics is not allowed") - .emit(); - - Err(err) + fn report_calling_intrinsic(&self, expr: &Expr<'_>) -> ErrorGuaranteed { + self.tcx.dcx().struct_span_err(expr.span, "tail calling intrinsics is not allowed").emit() } - fn report_nonfn_callee( - &self, - call_sp: Span, - fun_sp: Span, - ty: Ty<'_>, - ) -> Result { + fn report_nonfn_callee(&self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) -> ErrorGuaranteed { let mut err = self .tcx .dcx() @@ -382,7 +359,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { ); } - Err(err.emit()) + err.emit() } fn report_abi_mismatch( @@ -390,29 +367,21 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi, - ) -> Result { - let err = self - .tcx + ) -> ErrorGuaranteed { + 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(); - Err(err) + .emit() } - fn report_unsupported_abi( - &self, - sp: Span, - callee_abi: ExternAbi, - ) -> Result { - let err = self - .tcx + fn report_unsupported_abi(&self, sp: Span, callee_abi: ExternAbi) -> ErrorGuaranteed { + self.tcx .dcx() .struct_span_err(sp, "ABI does not support guaranteed tail calls") .with_note(format!("`become` is not supported for `extern {callee_abi}` functions")) - .emit(); - Err(err) + .emit() } fn report_signature_mismatch( @@ -420,62 +389,48 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { sp: Span, caller_sig: ty::FnSig<'_>, callee_sig: ty::FnSig<'_>, - ) -> Result { - let err = self - .tcx + ) -> ErrorGuaranteed { + 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(); - Err(err) + .emit() } - fn report_track_caller_caller(&self, sp: Span) -> Result { - let err = self - .tcx + fn report_track_caller_caller(&self, sp: Span) -> ErrorGuaranteed { + self.tcx .dcx() .struct_span_err( sp, "a function marked with `#[track_caller]` cannot perform a tail-call", ) - .emit(); - - Err(err) + .emit() } - fn report_c_variadic_caller(&self, sp: Span) -> Result { - let err = self - .tcx + fn report_c_variadic_caller(&self, sp: Span) -> ErrorGuaranteed { + self.tcx .dcx() // FIXME(explicit_tail_calls): highlight the `...` .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions") - .emit(); - - Err(err) + .emit() } - fn report_c_variadic_callee(&self, sp: Span) -> Result { - let err = self - .tcx + fn report_c_variadic_callee(&self, sp: Span) -> ErrorGuaranteed { + 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(); - - Err(err) + .emit() } - fn report_unsized_argument(&self, sp: Span, arg_ty: Ty<'tcx>) -> Result { - let err = self - .tcx + fn report_unsized_argument(&self, sp: Span, arg_ty: Ty<'tcx>) -> ErrorGuaranteed { + self.tcx .dcx() .struct_span_err(sp, format!("unsized arguments cannot be used in a tail call")) .with_note(format!("unsized argument of type `{arg_ty}`")) - .emit(); - - Err(err) + .emit() } }