@@ -69,23 +69,65 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
69
69
70
70
let ( span, panic) = panic_call ( cx, f) ;
71
71
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| {
73
89
let mut l = lint. build ( "panic message is not a string literal" ) ;
74
90
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 {
76
110
l. span_suggestion_verbose (
77
- arg . span . shrink_to_lo ( ) ,
111
+ arg_span . shrink_to_lo ( ) ,
78
112
"add a \" {}\" format string to Display the message" ,
79
113
"\" {}\" , " . into ( ) ,
80
114
Applicability :: MaybeIncorrect ,
81
115
) ;
82
116
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
+ }
89
131
}
90
132
}
91
133
l. emit ( ) ;
@@ -175,6 +217,19 @@ fn check_panic_str<'tcx>(
175
217
}
176
218
}
177
219
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
+
178
233
fn panic_call < ' tcx > ( cx : & LateContext < ' tcx > , f : & ' tcx hir:: Expr < ' tcx > ) -> ( Span , Symbol ) {
179
234
let mut expn = f. span . ctxt ( ) . outer_expn_data ( ) ;
180
235
0 commit comments