@@ -3,6 +3,7 @@ use rustc_ast as ast;
33use rustc_errors:: Applicability ;
44use rustc_hir as hir;
55use rustc_middle:: ty;
6+ use rustc_parse_format:: { ParseMode , Parser , Piece } ;
67use rustc_span:: sym;
78
89declare_lint ! {
@@ -52,13 +53,28 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
5253 if cx. tcx . is_diagnostic_item ( sym:: std_panic_macro, id)
5354 || cx. tcx . is_diagnostic_item ( sym:: core_panic_macro, id)
5455 {
55- let s = sym. as_str ( ) ;
56- if !s . contains ( & [ '{' , '}' ] [ ..] ) {
56+ let fmt = sym. as_str ( ) ;
57+ if !fmt . contains ( & [ '{' , '}' ] [ ..] ) {
5758 return ;
5859 }
59- let s = s. replace ( "{{" , "" ) . replace ( "}}" , "" ) ;
60- let looks_like_placeholder =
61- s. find ( '{' ) . map_or ( false , |i| s[ i + 1 ..] . contains ( '}' ) ) ;
60+
61+ let fmt_span = arg. span . source_callsite ( ) ;
62+
63+ let ( snippet, style) =
64+ match cx. sess ( ) . parse_sess . source_map ( ) . span_to_snippet ( fmt_span) {
65+ Ok ( snippet) => {
66+ // Count the number of `#`s between the `r` and `"`.
67+ let style = snippet. strip_prefix ( 'r' ) . and_then ( |s| s. find ( '"' ) ) ;
68+ ( Some ( snippet) , style)
69+ }
70+ Err ( _) => ( None , None ) ,
71+ } ;
72+
73+ let mut fmt_parser =
74+ Parser :: new ( fmt. as_ref ( ) , style, snippet, false , ParseMode :: Format ) ;
75+ let n_arguments =
76+ ( & mut fmt_parser) . filter ( |a| matches ! ( a, Piece :: NextArgument ( _) ) ) . count ( ) ;
77+
6278 // Unwrap another level of macro expansion if this panic!()
6379 // was expanded from assert!() or debug_assert!().
6480 for & assert in & [ sym:: assert_macro, sym:: debug_assert_macro] {
@@ -70,15 +86,23 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
7086 expn = parent;
7187 }
7288 }
73- if looks_like_placeholder {
74- cx. struct_span_lint ( PANIC_FMT , arg. span . source_callsite ( ) , |lint| {
75- let mut l = lint. build ( "panic message contains an unused formatting placeholder" ) ;
89+
90+ if n_arguments > 0 && fmt_parser. errors . is_empty ( ) {
91+ let arg_spans: Vec < _ > = match & fmt_parser. arg_places [ ..] {
92+ [ ] => vec ! [ fmt_span] ,
93+ v => v. iter ( ) . map ( |span| fmt_span. from_inner ( * span) ) . collect ( ) ,
94+ } ;
95+ cx. struct_span_lint ( PANIC_FMT , arg_spans, |lint| {
96+ let mut l = lint. build ( match n_arguments {
97+ 1 => "panic message contains an unused formatting placeholder" ,
98+ _ => "panic message contains unused formatting placeholders" ,
99+ } ) ;
76100 l. note ( "this message is not used as a format string when given without arguments, but will be in a future Rust version" ) ;
77101 if expn. call_site . contains ( arg. span ) {
78102 l. span_suggestion (
79103 arg. span . shrink_to_hi ( ) ,
80104 "add the missing argument(s)" ,
81- ", argument " . into ( ) ,
105+ ", ... " . into ( ) ,
82106 Applicability :: HasPlaceholders ,
83107 ) ;
84108 l. span_suggestion (
0 commit comments