@@ -69,23 +69,65 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
6969
7070 let ( span, panic) = panic_call ( cx, f) ;
7171
72- cx. struct_span_lint ( NON_FMT_PANIC , arg. span , |lint| {
72+ // Find the span of the argument to `panic!()`, before expansion in the
73+ // case of `panic!(some_macro!())`.
74+ // We don't use source_callsite(), because this `panic!(..)` might itself
75+ // be expanded from another macro, in which case we want to stop at that
76+ // expansion.
77+ let mut arg_span = arg. span ;
78+ let mut arg_macro = None ;
79+ while !span. contains ( arg_span) {
80+ let expn = arg_span. ctxt ( ) . outer_expn_data ( ) ;
81+ if expn. is_root ( ) {
82+ break ;
83+ }
84+ arg_macro = expn. macro_def_id ;
85+ arg_span = expn. call_site ;
86+ }
87+
88+ cx. struct_span_lint ( NON_FMT_PANIC , arg_span, |lint| {
7389 let mut l = lint. build ( "panic message is not a string literal" ) ;
7490 l. note ( "this is no longer accepted in Rust 2021" ) ;
75- if span. contains ( arg. span ) {
91+ if !span. contains ( arg_span) {
92+ // No clue where this argument is coming from.
93+ l. emit ( ) ;
94+ return ;
95+ }
96+ if arg_macro. map_or ( false , |id| cx. tcx . is_diagnostic_item ( sym:: format_macro, id) ) {
97+ // A case of `panic!(format!(..))`.
98+ l. note ( "the panic!() macro supports formatting, so there's no need for the format!() macro here" ) ;
99+ if let Some ( ( open, close, _) ) = find_delimiters ( cx, arg_span) {
100+ l. multipart_suggestion (
101+ "remove the `format!(..)` macro call" ,
102+ vec ! [
103+ ( arg_span. until( open. shrink_to_hi( ) ) , "" . into( ) ) ,
104+ ( close. until( arg_span. shrink_to_hi( ) ) , "" . into( ) ) ,
105+ ] ,
106+ Applicability :: MachineApplicable ,
107+ ) ;
108+ }
109+ } else {
76110 l. span_suggestion_verbose (
77- arg . span . shrink_to_lo ( ) ,
111+ arg_span . shrink_to_lo ( ) ,
78112 "add a \" {}\" format string to Display the message" ,
79113 "\" {}\" , " . into ( ) ,
80114 Applicability :: MaybeIncorrect ,
81115 ) ;
82116 if panic == sym:: std_panic_macro {
83- l. span_suggestion_verbose (
84- span. until ( arg. span ) ,
85- "or use std::panic::panic_any instead" ,
86- "std::panic::panic_any(" . into ( ) ,
87- Applicability :: MachineApplicable ,
88- ) ;
117+ if let Some ( ( open, close, del) ) = find_delimiters ( cx, span) {
118+ l. multipart_suggestion (
119+ "or use std::panic::panic_any instead" ,
120+ if del == '(' {
121+ vec ! [ ( span. until( open) , "std::panic::panic_any" . into( ) ) ]
122+ } else {
123+ vec ! [
124+ ( span. until( open. shrink_to_hi( ) ) , "std::panic::panic_any(" . into( ) ) ,
125+ ( close, ")" . into( ) ) ,
126+ ]
127+ } ,
128+ Applicability :: MachineApplicable ,
129+ ) ;
130+ }
89131 }
90132 }
91133 l. emit ( ) ;
@@ -175,6 +217,19 @@ fn check_panic_str<'tcx>(
175217 }
176218}
177219
220+ /// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
221+ /// and the type of (opening) delimiter used.
222+ fn find_delimiters < ' tcx > ( cx : & LateContext < ' tcx > , span : Span ) -> Option < ( Span , Span , char ) > {
223+ let snippet = cx. sess ( ) . parse_sess . source_map ( ) . span_to_snippet ( span) . ok ( ) ?;
224+ let ( open, open_ch) = snippet. char_indices ( ) . find ( |& ( _, c) | "([{" . contains ( c) ) ?;
225+ let close = snippet. rfind ( |c| ")]}" . contains ( c) ) ?;
226+ Some ( (
227+ span. from_inner ( InnerSpan { start : open, end : open + 1 } ) ,
228+ span. from_inner ( InnerSpan { start : close, end : close + 1 } ) ,
229+ open_ch,
230+ ) )
231+ }
232+
178233fn panic_call < ' tcx > ( cx : & LateContext < ' tcx > , f : & ' tcx hir:: Expr < ' tcx > ) -> ( Span , Symbol ) {
179234 let mut expn = f. span . ctxt ( ) . outer_expn_data ( ) ;
180235
0 commit comments