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
1 change: 1 addition & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ declare_lint_pass! {
SHADOWING_SUPERTRAIT_ITEMS,
SINGLE_USE_LIFETIMES,
STABLE_FEATURES,
TAIL_CALL_TRACK_CALLER,
TAIL_EXPR_DROP_ORDER,
TEST_UNSTABLE_LINT,
TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
Expand Down
34 changes: 14 additions & 20 deletions compiler/rustc_mir_build/src/check_tail_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ use rustc_errors::Applicability;
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)?;
Expand All @@ -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,
Expand All @@ -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]);
Expand All @@ -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> {
Expand Down Expand Up @@ -148,11 +148,13 @@ 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 {
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)),
);
Expand All @@ -173,7 +175,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
// 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);
Expand All @@ -189,19 +191,11 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
}
}

/// 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<'_>) {
Expand Down
9 changes: 9 additions & 0 deletions tests/ui/explicit-tail-calls/caller_is_track_caller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
8 changes: 7 additions & 1 deletion tests/ui/explicit-tail-calls/caller_is_track_caller.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

38 changes: 38 additions & 0 deletions tests/ui/explicit-tail-calls/default-trait-method.rs
Original file line number Diff line number Diff line change
@@ -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)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
#[allow(tail_call_track_caller)]
#[expect(tail_call_track_caller)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Turns out this does not work

error: 2 diagnostics reported in JSON output but not expected in test file
tests/ui/explicit-tail-calls/default-trait-method.rs:17:18: WARN: this lint expectation is unfulfilled [unfulfilled_lint_expectations]
tests/ui/explicit-tail-calls/default-trait-method.rs:17:18: WARN: this lint expectation is unfulfilled [unfulfilled_lint_expectations]

I think that is because there is some inconsistency between which HIR id is used, I actually suspect that we can/should emit this lint earlier so that we have more HIR stuff available.

I suspect that's blocked on some of the other work moving tail call logic to earlier stages, and in any case doesn't seem that relevant to this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actually, at least part of the issue is that the instance on Struct does not use track_caller, so the expect fails on that monomorphization anyway.

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);
}
Loading