Fix an ICE when using become in a default trait method

The logic determining whether the relevant function is marked as `#[track_caller]`
only worked with functions that could be resolved to a concrete instance, which default
trait methods cannot. We need to check the codegen attribute on the default method itself.

Co-authored-by: Folkert de Vries <folkert@folkertdev.nl>
This commit is contained in:
Jacob Adam
2025-08-11 20:42:57 +01:00
committed by Folkert de Vries
parent ec2d669db8
commit ac5734a779
5 changed files with 69 additions and 21 deletions
+1
View File
@@ -109,6 +109,7 @@
SHADOWING_SUPERTRAIT_ITEMS,
SINGLE_USE_LIFETIMES,
STABLE_FEATURES,
TAIL_CALL_TRACK_CALLER,
TAIL_EXPR_DROP_ORDER,
TEST_UNSTABLE_LINT,
TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
@@ -4,12 +4,13 @@
use rustc_hir::LangItem;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::span_bug;
use rustc_middle::thir::visit::{self, Visitor};
use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
use rustc_span::{ErrorGuaranteed, Span};
pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> {
let (thir, expr) = tcx.thir_body(def)?;
@@ -21,7 +22,6 @@ pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), E
}
let is_closure = matches!(tcx.def_kind(def), DefKind::Closure);
let caller_ty = tcx.type_of(def).skip_binder();
let mut visitor = TailCallCkVisitor {
tcx,
@@ -30,7 +30,7 @@ pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), E
// FIXME(#132279): we're clearly in a body here.
typing_env: ty::TypingEnv::non_body_analysis(tcx, def),
is_closure,
caller_ty,
caller_def_id: def,
};
visitor.visit_expr(&thir[expr]);
@@ -47,8 +47,8 @@ struct TailCallCkVisitor<'a, 'tcx> {
/// The result of the checks, `Err(_)` if there was a problem with some
/// tail call, `Ok(())` if all of them were fine.
found_errors: Result<(), ErrorGuaranteed>,
/// Type of the caller function.
caller_ty: Ty<'tcx>,
/// `LocalDefId` of the caller function.
caller_def_id: LocalDefId,
}
impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
@@ -148,11 +148,13 @@ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
// 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(),
self.caller_ty.fn_sig(self.tcx),
caller_ty.fn_sig(self.tcx),
),
self.tcx.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)),
);
@@ -173,7 +175,7 @@ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
// coercing the function to an `fn()` pointer. (although in that case the tailcall is
// basically useless -- the shim calls the actual function, so tailcalling the shim is
// equivalent to calling the function)
let caller_needs_location = self.needs_location(self.caller_ty);
let caller_needs_location = self.caller_needs_location();
if caller_needs_location {
self.report_track_caller_caller(expr.span);
@@ -189,19 +191,11 @@ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
}
}
/// Returns true if function of type `ty` needs location argument
/// (i.e. if a function is marked as `#[track_caller]`).
///
/// Panics if the function's instance can't be immediately resolved.
fn needs_location(&self, ty: Ty<'tcx>) -> bool {
if let &ty::FnDef(did, substs) = ty.kind() {
let instance =
ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP);
instance.def.requires_caller_location(self.tcx)
} else {
false
}
/// Returns true if the caller function needs a location argument
/// (i.e. if a function is marked as `#[track_caller]`)
fn caller_needs_location(&self) -> bool {
let flags = self.tcx.codegen_fn_attrs(self.caller_def_id).flags;
flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
}
fn report_in_closure(&mut self, expr: &Expr<'_>) {
@@ -13,4 +13,13 @@ fn c() {
become a(); //~ error: a function marked with `#[track_caller]` cannot perform a tail-call
}
trait Trait {
fn d(&self);
#[track_caller]
fn e(&self) {
become self.d(); //~ error: a function marked with `#[track_caller]` cannot perform a tail-call
}
}
fn main() {}
@@ -10,5 +10,11 @@ error: a function marked with `#[track_caller]` cannot perform a tail-call
LL | become a();
| ^^^^^^^^^^
error: aborting due to 2 previous errors
error: a function marked with `#[track_caller]` cannot perform a tail-call
--> $DIR/caller_is_track_caller.rs:21:9
|
LL | become self.d();
| ^^^^^^^^^^^^^^^
error: aborting due to 3 previous errors
@@ -0,0 +1,38 @@
// A regression test for <https://github.com/rust-lang/rust/issues/144985>.
// Previously, using `become` in a default trait method would lead to an ICE
// in a path determining whether the method in question is marked as `#[track_caller]`.
//
//@ run-pass
//@ ignore-backends: gcc
#![feature(explicit_tail_calls)]
#![expect(incomplete_features)]
trait Trait {
fn bar(&self) -> usize {
123
}
fn foo(&self) -> usize {
#[allow(tail_call_track_caller)]
become self.bar();
}
}
struct Struct;
impl Trait for Struct {}
struct OtherStruct;
impl Trait for OtherStruct {
#[track_caller]
fn bar(&self) -> usize {
456
}
}
fn main() {
assert_eq!(Struct.foo(), 123);
assert_eq!(OtherStruct.foo(), 456);
}