diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs index 0c5456cf619..7432f476d7c 100644 --- a/compiler/rustc_lint/src/non_fmt_panic.rs +++ b/compiler/rustc_lint/src/non_fmt_panic.rs @@ -72,18 +72,38 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc // Find the span of the argument to `panic!()`, before expansion in the // case of `panic!(some_macro!())`. let mut arg_span = arg.span; + let mut arg_macro = None; while !span.contains(arg_span) { let expn = arg_span.ctxt().outer_expn_data(); if expn.is_root() { break; } + arg_macro = expn.macro_def_id; arg_span = expn.call_site; } cx.struct_span_lint(NON_FMT_PANIC, arg_span, |lint| { let mut l = lint.build("panic message is not a string literal"); l.note("this is no longer accepted in Rust 2021"); - if span.contains(arg_span) { + if !span.contains(arg_span) { + // No clue where this argument is coming from. + l.emit(); + return; + } + if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) { + // A case of `panic!(format!(..))`. + l.note("the panic!() macro supports formatting, so there's no need for the format!() macro here"); + if let Some(inner) = find_inner_span(cx, arg_span) { + l.multipart_suggestion( + "remove the `format!(..)` macro call", + vec![ + (arg_span.until(inner), "".into()), + (inner.between(arg_span.shrink_to_hi()), "".into()), + ], + Applicability::MachineApplicable, + ); + } + } else { l.span_suggestion_verbose( arg_span.shrink_to_lo(), "add a \"{}\" format string to Display the message", @@ -186,6 +206,15 @@ fn check_panic_str<'tcx>( } } +/// Given the span of `some_macro!(args)`, gives the span of `args`. +fn find_inner_span<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option { + let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?; + Some(span.from_inner(InnerSpan { + start: snippet.find(&['(', '{', '['][..])? + 1, + end: snippet.rfind(&[')', '}', ']'][..])?, + })) +} + fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol) { let mut expn = f.span.ctxt().outer_expn_data(); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 20e4f7262ac..831e82559ad 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -554,6 +554,7 @@ symbols! { format_args, format_args_capture, format_args_nl, + format_macro, freeze, freg, frem_fast, diff --git a/library/alloc/src/macros.rs b/library/alloc/src/macros.rs index a64a8b32ad7..88a6cec3a83 100644 --- a/library/alloc/src/macros.rs +++ b/library/alloc/src/macros.rs @@ -107,6 +107,7 @@ macro_rules! vec { /// ``` #[macro_export] #[stable(feature = "rust1", since = "1.0.0")] +#[rustc_diagnostic_item = "format_macro"] macro_rules! format { ($($arg:tt)*) => {{ let res = $crate::fmt::format($crate::__export::format_args!($($arg)*));