diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index a3f5c18f2e75c..3634e6e47ce12 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -656,18 +656,14 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Closure(c) }; - let track_caller = self - .attrs - .get(&outer_hir_id.local_id) - .map_or(false, |attrs| attrs.into_iter().any(|attr| attr.has_name(sym::track_caller))); - let hir_id = self.lower_node_id(closure_node_id); - if track_caller { - let unstable_span = self.mark_span_with_reason( - DesugaringKind::Async, - span, - self.allow_gen_future.clone(), - ); + let unstable_span = + self.mark_span_with_reason(DesugaringKind::Async, span, self.allow_gen_future.clone()); + + if self.tcx.features().closure_track_caller + && let Some(attrs) = self.attrs.get(&outer_hir_id.local_id) + && attrs.into_iter().any(|attr| attr.has_name(sym::track_caller)) + { self.lower_attrs( hir_id, &[Attribute { diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl index 7e28f22c0ba8b..2eb409a5ddd5e 100644 --- a/compiler/rustc_error_messages/locales/en-US/lint.ftl +++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl @@ -350,6 +350,9 @@ lint_builtin_mutable_transmutes = lint_builtin_unstable_features = unstable feature +lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op + .label = this function will not propagate the caller location + lint_builtin_unreachable_pub = unreachable `pub` {$what} .suggestion = consider restricting its visibility .help = or consider exporting it for use by other crates diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index cd19e65b6fc32..d6de6e70ead80 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -25,6 +25,7 @@ use crate::{ types::{transparent_newtype_field, CItemKind}, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext, }; +use hir::IsAsync; use rustc_ast::attr; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::visit::{FnCtxt, FnKind}; @@ -40,7 +41,10 @@ use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, Gate use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID}; -use rustc_hir::{ForeignItemKind, GenericParamKind, HirId, Node, PatKind, PredicateOrigin}; +use rustc_hir::intravisit::FnKind as HirFnKind; +use rustc_hir::{ + Body, FnDecl, ForeignItemKind, GenericParamKind, HirId, Node, PatKind, PredicateOrigin, +}; use rustc_index::vec::Idx; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::layout::{LayoutError, LayoutOf}; @@ -1370,6 +1374,72 @@ impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { } } +declare_lint! { + /// The `ungated_async_fn_track_caller` lint warns when the + /// `#[track_caller]` attribute is used on an async function, method, or + /// closure, without enabling the corresponding unstable feature flag. + /// + /// ### Example + /// + /// ```rust + /// #[track_caller] + /// async fn foo() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The attribute must be used in conjunction with the + /// [`closure_track_caller` feature flag]. Otherwise, the `#[track_caller]` + /// annotation will function as as no-op. + /// + /// [`closure_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/closure-track-caller.html + UNGATED_ASYNC_FN_TRACK_CALLER, + Warn, + "enabling track_caller on an async fn is a no-op unless the closure_track_caller feature is enabled" +} + +declare_lint_pass!( + /// Explains corresponding feature flag must be enabled for the `#[track_caller] attribute to + /// do anything + UngatedAsyncFnTrackCaller => [UNGATED_ASYNC_FN_TRACK_CALLER] +); + +impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller { + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fn_kind: HirFnKind<'_>, + _: &'tcx FnDecl<'_>, + _: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if fn_kind.asyncness() == IsAsync::Async + && !cx.tcx.features().closure_track_caller + && let attrs = cx.tcx.hir().attrs(hir_id) + // Now, check if the function has the `#[track_caller]` attribute + && let Some(attr) = attrs.iter().find(|attr| attr.has_name(sym::track_caller)) + { + cx.struct_span_lint( + UNGATED_ASYNC_FN_TRACK_CALLER, + attr.span, + fluent::lint_ungated_async_fn_track_caller, + |lint| { + lint.span_label(span, fluent::label); + rustc_session::parse::add_feature_diagnostics( + lint, + &cx.tcx.sess.parse_sess, + sym::closure_track_caller, + ); + lint + }, + ); + } + } +} + declare_lint! { /// The `unreachable_pub` lint triggers for `pub` items not reachable from /// the crate root. diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 11022eb80ea5f..1275d6f223c7a 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -219,6 +219,7 @@ late_lint_methods!( // May Depend on constants elsewhere UnusedBrokenConst: UnusedBrokenConst, UnstableFeatures: UnstableFeatures, + UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller, ArrayIntoIter: ArrayIntoIter::default(), DropTraitConstraints: DropTraitConstraints, TemporaryCStringAsPtr: TemporaryCStringAsPtr, diff --git a/src/test/ui/async-await/track-caller/async-closure-gate.rs b/src/test/ui/async-await/track-caller/async-closure-gate.rs index 9593fdb1908e2..d9d556855991b 100644 --- a/src/test/ui/async-await/track-caller/async-closure-gate.rs +++ b/src/test/ui/async-await/track-caller/async-closure-gate.rs @@ -5,6 +5,5 @@ fn main() { let _ = #[track_caller] async || { //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - //~| ERROR `#[track_caller]` on closures is currently unstable [E0658] }; } diff --git a/src/test/ui/async-await/track-caller/async-closure-gate.stderr b/src/test/ui/async-await/track-caller/async-closure-gate.stderr index be3d110eccdb0..498f1b43b9bca 100644 --- a/src/test/ui/async-await/track-caller/async-closure-gate.stderr +++ b/src/test/ui/async-await/track-caller/async-closure-gate.stderr @@ -7,19 +7,6 @@ LL | let _ = #[track_caller] async || { = note: see issue #87417 for more information = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable -error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:6:38 - | -LL | let _ = #[track_caller] async || { - | ______________________________________^ -LL | | -LL | | -LL | | }; - | |_____^ - | - = note: see issue #87417 for more information - = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable - -error: aborting due to 2 previous errors +error: aborting due to previous error For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/async-await/track-caller/panic-track-caller.nofeat.stderr b/src/test/ui/async-await/track-caller/panic-track-caller.nofeat.stderr new file mode 100644 index 0000000000000..51ea225f4cbd4 --- /dev/null +++ b/src/test/ui/async-await/track-caller/panic-track-caller.nofeat.stderr @@ -0,0 +1,29 @@ +warning: `#[track_caller]` on async functions is a no-op + --> $DIR/panic-track-caller.rs:50:1 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | / async fn bar_track_caller() { +LL | | panic!() +LL | | } + | |_- this function will not propagate the caller location + | + = note: see issue #87417 for more information + = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable + = note: `#[warn(ungated_async_fn_track_caller)]` on by default + +warning: `#[track_caller]` on async functions is a no-op + --> $DIR/panic-track-caller.rs:62:5 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | / async fn bar_assoc() { +LL | | panic!(); +LL | | } + | |_____- this function will not propagate the caller location + | + = note: see issue #87417 for more information + = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable + +warning: 2 warnings emitted + diff --git a/src/test/ui/async-await/track-caller/panic-track-caller.rs b/src/test/ui/async-await/track-caller/panic-track-caller.rs index 066cf97628fa5..f45243b0ea6f5 100644 --- a/src/test/ui/async-await/track-caller/panic-track-caller.rs +++ b/src/test/ui/async-await/track-caller/panic-track-caller.rs @@ -1,7 +1,9 @@ // run-pass // edition:2021 +// revisions: feat nofeat // needs-unwind -#![feature(closure_track_caller, async_closure, stmt_expr_attributes)] +#![feature(async_closure, stmt_expr_attributes)] +#![cfg_attr(feat, feature(closure_track_caller))] use std::future::Future; use std::panic; @@ -45,7 +47,7 @@ async fn foo() { bar().await } -#[track_caller] +#[track_caller] //[nofeat]~ WARN `#[track_caller]` on async functions is a no-op async fn bar_track_caller() { panic!() } @@ -57,7 +59,7 @@ async fn foo_track_caller() { struct Foo; impl Foo { - #[track_caller] + #[track_caller] //[nofeat]~ WARN `#[track_caller]` on async functions is a no-op async fn bar_assoc() { panic!(); } @@ -67,6 +69,9 @@ async fn foo_assoc() { Foo::bar_assoc().await } +// Since compilation is expected to fail for this fn when using +// `nofeat`, we test that separately in `async-closure-gate.rs` +#[cfg(feat)] async fn foo_closure() { let c = #[track_caller] async || { panic!(); @@ -91,8 +96,18 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { } fn main() { - assert_eq!(panicked_at(|| block_on(foo())), 41); - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 54); - assert_eq!(panicked_at(|| block_on(foo_assoc())), 67); - assert_eq!(panicked_at(|| block_on(foo_closure())), 74); + assert_eq!(panicked_at(|| block_on(foo())), 43); + + #[cfg(feat)] + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 56); + #[cfg(nofeat)] + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 52); + + #[cfg(feat)] + assert_eq!(panicked_at(|| block_on(foo_assoc())), 69); + #[cfg(nofeat)] + assert_eq!(panicked_at(|| block_on(foo_assoc())), 64); + + #[cfg(feat)] + assert_eq!(panicked_at(|| block_on(foo_closure())), 79); }