@@ -2,14 +2,14 @@ use std::borrow::Cow;
22use std:: ops:: Range ;
33
44use crate :: utils:: { snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then} ;
5- use rustc_ast:: ast:: { Expr , ExprKind , Mac , StrLit , StrStyle } ;
5+ use rustc_ast:: ast:: { Expr , ExprKind , Item , ItemKind , Mac , StrLit , StrStyle } ;
66use rustc_ast:: token;
77use rustc_ast:: tokenstream:: TokenStream ;
88use rustc_errors:: Applicability ;
99use rustc_lexer:: unescape:: { self , EscapeError } ;
1010use rustc_lint:: { EarlyContext , EarlyLintPass } ;
1111use rustc_parse:: parser;
12- use rustc_session:: { declare_lint_pass , declare_tool_lint } ;
12+ use rustc_session:: { declare_tool_lint , impl_lint_pass } ;
1313use rustc_span:: symbol:: Symbol ;
1414use rustc_span:: { BytePos , Span } ;
1515
@@ -175,7 +175,12 @@ declare_clippy_lint! {
175175 "writing a literal with a format string"
176176}
177177
178- declare_lint_pass ! ( Write => [
178+ #[ derive( Default ) ]
179+ pub struct Write {
180+ in_debug_impl : bool ,
181+ }
182+
183+ impl_lint_pass ! ( Write => [
179184 PRINT_WITH_NEWLINE ,
180185 PRINTLN_EMPTY_STRING ,
181186 PRINT_STDOUT ,
@@ -187,10 +192,34 @@ declare_lint_pass!(Write => [
187192] ) ;
188193
189194impl EarlyLintPass for Write {
195+ fn check_item ( & mut self , _: & EarlyContext < ' _ > , item : & Item ) {
196+ if let ItemKind :: Impl {
197+ of_trait : Some ( trait_ref) ,
198+ ..
199+ } = & item. kind
200+ {
201+ let trait_name = trait_ref
202+ . path
203+ . segments
204+ . iter ( )
205+ . last ( )
206+ . expect ( "path has at least one segment" )
207+ . ident
208+ . name ;
209+ if trait_name == sym ! ( Debug ) {
210+ self . in_debug_impl = true ;
211+ }
212+ }
213+ }
214+
215+ fn check_item_post ( & mut self , _: & EarlyContext < ' _ > , _: & Item ) {
216+ self . in_debug_impl = false ;
217+ }
218+
190219 fn check_mac ( & mut self , cx : & EarlyContext < ' _ > , mac : & Mac ) {
191220 if mac. path == sym ! ( println) {
192221 span_lint ( cx, PRINT_STDOUT , mac. span ( ) , "use of `println!`" ) ;
193- if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
222+ if let ( Some ( fmt_str) , _) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
194223 if fmt_str. symbol == Symbol :: intern ( "" ) {
195224 span_lint_and_sugg (
196225 cx,
@@ -205,7 +234,7 @@ impl EarlyLintPass for Write {
205234 }
206235 } else if mac. path == sym ! ( print) {
207236 span_lint ( cx, PRINT_STDOUT , mac. span ( ) , "use of `print!`" ) ;
208- if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
237+ if let ( Some ( fmt_str) , _) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
209238 if check_newlines ( & fmt_str) {
210239 span_lint_and_then (
211240 cx,
@@ -226,7 +255,7 @@ impl EarlyLintPass for Write {
226255 }
227256 }
228257 } else if mac. path == sym ! ( write) {
229- if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
258+ if let ( Some ( fmt_str) , _) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
230259 if check_newlines ( & fmt_str) {
231260 span_lint_and_then (
232261 cx,
@@ -247,7 +276,7 @@ impl EarlyLintPass for Write {
247276 }
248277 }
249278 } else if mac. path == sym ! ( writeln) {
250- if let ( Some ( fmt_str) , expr) = check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
279+ if let ( Some ( fmt_str) , expr) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
251280 if fmt_str. symbol == Symbol :: intern ( "" ) {
252281 let mut applicability = Applicability :: MachineApplicable ;
253282 let suggestion = expr. map_or_else (
@@ -296,129 +325,136 @@ fn newline_span(fmtstr: &StrLit) -> Span {
296325 sp. with_lo ( newline_sp_hi - newline_sp_len) . with_hi ( newline_sp_hi)
297326}
298327
299- /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
300- /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
301- /// the contents of the string, whether it's a raw string, and the span of the literal in the
302- /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
303- /// `format_str` should be written to.
304- ///
305- /// Example:
306- ///
307- /// Calling this function on
308- /// ```rust
309- /// # use std::fmt::Write;
310- /// # let mut buf = String::new();
311- /// # let something = "something";
312- /// writeln!(buf, "string to write: {}", something);
313- /// ```
314- /// will return
315- /// ```rust,ignore
316- /// (Some("string to write: {}"), Some(buf))
317- /// ```
318- #[ allow( clippy:: too_many_lines) ]
319- fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < StrLit > , Option < Expr > ) {
320- use fmt_macros:: {
321- AlignUnknown , ArgumentImplicitlyIs , ArgumentIs , ArgumentNamed , CountImplied , FormatSpec , Parser , Piece ,
322- } ;
323- let tts = tts. clone ( ) ;
324-
325- let mut parser = parser:: Parser :: new ( & cx. sess . parse_sess , tts, None , false , false , None ) ;
326- let mut expr: Option < Expr > = None ;
327- if is_write {
328- expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
329- Ok ( p) => Some ( p. into_inner ( ) ) ,
330- Err ( _) => return ( None , None ) ,
328+ impl Write {
329+ /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
330+ /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
331+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
332+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
333+ /// `format_str` should be written to.
334+ ///
335+ /// Example:
336+ ///
337+ /// Calling this function on
338+ /// ```rust
339+ /// # use std::fmt::Write;
340+ /// # let mut buf = String::new();
341+ /// # let something = "something";
342+ /// writeln!(buf, "string to write: {}", something);
343+ /// ```
344+ /// will return
345+ /// ```rust,ignore
346+ /// (Some("string to write: {}"), Some(buf))
347+ /// ```
348+ #[ allow( clippy:: too_many_lines) ]
349+ fn check_tts < ' a > (
350+ & self ,
351+ cx : & EarlyContext < ' a > ,
352+ tts : & TokenStream ,
353+ is_write : bool ,
354+ ) -> ( Option < StrLit > , Option < Expr > ) {
355+ use fmt_macros:: {
356+ AlignUnknown , ArgumentImplicitlyIs , ArgumentIs , ArgumentNamed , CountImplied , FormatSpec , Parser , Piece ,
331357 } ;
332- // might be `writeln!(foo)`
333- if parser. expect ( & token:: Comma ) . map_err ( |mut err| err. cancel ( ) ) . is_err ( ) {
334- return ( None , expr) ;
335- }
336- }
358+ let tts = tts. clone ( ) ;
337359
338- let fmtstr = match parser. parse_str_lit ( ) {
339- Ok ( fmtstr) => fmtstr,
340- Err ( _) => return ( None , expr) ,
341- } ;
342- let tmp = fmtstr. symbol . as_str ( ) ;
343- let mut args = vec ! [ ] ;
344- let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
345- while let Some ( piece) = fmt_parser. next ( ) {
346- if !fmt_parser. errors . is_empty ( ) {
347- return ( None , expr) ;
348- }
349- if let Piece :: NextArgument ( arg) = piece {
350- if arg. format . ty == "?" {
351- // FIXME: modify rustc's fmt string parser to give us the current span
352- span_lint ( cx, USE_DEBUG , parser. prev_token . span , "use of `Debug`-based formatting" ) ;
360+ let mut parser = parser:: Parser :: new ( & cx. sess . parse_sess , tts, None , false , false , None ) ;
361+ let mut expr: Option < Expr > = None ;
362+ if is_write {
363+ expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
364+ Ok ( p) => Some ( p. into_inner ( ) ) ,
365+ Err ( _) => return ( None , None ) ,
366+ } ;
367+ // might be `writeln!(foo)`
368+ if parser. expect ( & token:: Comma ) . map_err ( |mut err| err. cancel ( ) ) . is_err ( ) {
369+ return ( None , expr) ;
353370 }
354- args. push ( arg) ;
355371 }
356- }
357- let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL } ;
358- let mut idx = 0 ;
359- loop {
360- const SIMPLE : FormatSpec < ' _ > = FormatSpec {
361- fill : None ,
362- align : AlignUnknown ,
363- flags : 0 ,
364- precision : CountImplied ,
365- precision_span : None ,
366- width : CountImplied ,
367- width_span : None ,
368- ty : "" ,
369- ty_span : None ,
372+
373+ let fmtstr = match parser. parse_str_lit ( ) {
374+ Ok ( fmtstr) => fmtstr,
375+ Err ( _) => return ( None , expr) ,
370376 } ;
371- if !parser. eat ( & token:: Comma ) {
372- return ( Some ( fmtstr) , expr) ;
377+ let tmp = fmtstr. symbol . as_str ( ) ;
378+ let mut args = vec ! [ ] ;
379+ let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
380+ while let Some ( piece) = fmt_parser. next ( ) {
381+ if !fmt_parser. errors . is_empty ( ) {
382+ return ( None , expr) ;
383+ }
384+ if let Piece :: NextArgument ( arg) = piece {
385+ if !self . in_debug_impl && arg. format . ty == "?" {
386+ // FIXME: modify rustc's fmt string parser to give us the current span
387+ span_lint ( cx, USE_DEBUG , parser. prev_token . span , "use of `Debug`-based formatting" ) ;
388+ }
389+ args. push ( arg) ;
390+ }
373391 }
374- let token_expr = if let Ok ( expr) = parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
375- expr
376- } else {
377- return ( Some ( fmtstr) , None ) ;
378- } ;
379- match & token_expr. kind {
380- ExprKind :: Lit ( _) => {
381- let mut all_simple = true ;
382- let mut seen = false ;
383- for arg in & args {
384- match arg. position {
385- ArgumentImplicitlyIs ( n) | ArgumentIs ( n) => {
386- if n == idx {
387- all_simple &= arg. format == SIMPLE ;
388- seen = true ;
389- }
390- } ,
391- ArgumentNamed ( _) => { } ,
392+ let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL } ;
393+ let mut idx = 0 ;
394+ loop {
395+ const SIMPLE : FormatSpec < ' _ > = FormatSpec {
396+ fill : None ,
397+ align : AlignUnknown ,
398+ flags : 0 ,
399+ precision : CountImplied ,
400+ precision_span : None ,
401+ width : CountImplied ,
402+ width_span : None ,
403+ ty : "" ,
404+ ty_span : None ,
405+ } ;
406+ if !parser. eat ( & token:: Comma ) {
407+ return ( Some ( fmtstr) , expr) ;
408+ }
409+ let token_expr = if let Ok ( expr) = parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
410+ expr
411+ } else {
412+ return ( Some ( fmtstr) , None ) ;
413+ } ;
414+ match & token_expr. kind {
415+ ExprKind :: Lit ( _) => {
416+ let mut all_simple = true ;
417+ let mut seen = false ;
418+ for arg in & args {
419+ match arg. position {
420+ ArgumentImplicitlyIs ( n) | ArgumentIs ( n) => {
421+ if n == idx {
422+ all_simple &= arg. format == SIMPLE ;
423+ seen = true ;
424+ }
425+ } ,
426+ ArgumentNamed ( _) => { } ,
427+ }
392428 }
393- }
394- if all_simple && seen {
395- span_lint ( cx, lint, token_expr. span , "literal with an empty format string" ) ;
396- }
397- idx += 1 ;
398- } ,
399- ExprKind :: Assign ( lhs, rhs, _) => {
400- if let ExprKind :: Lit ( _) = rhs. kind {
401- if let ExprKind :: Path ( _, p) = & lhs. kind {
402- let mut all_simple = true ;
403- let mut seen = false ;
404- for arg in & args {
405- match arg. position {
406- ArgumentImplicitlyIs ( _) | ArgumentIs ( _) => { } ,
407- ArgumentNamed ( name) => {
408- if * p == name {
409- seen = true ;
410- all_simple &= arg. format == SIMPLE ;
411- }
412- } ,
429+ if all_simple && seen {
430+ span_lint ( cx, lint, token_expr. span , "literal with an empty format string" ) ;
431+ }
432+ idx += 1 ;
433+ } ,
434+ ExprKind :: Assign ( lhs, rhs, _) => {
435+ if let ExprKind :: Lit ( _) = rhs. kind {
436+ if let ExprKind :: Path ( _, p) = & lhs. kind {
437+ let mut all_simple = true ;
438+ let mut seen = false ;
439+ for arg in & args {
440+ match arg. position {
441+ ArgumentImplicitlyIs ( _) | ArgumentIs ( _) => { } ,
442+ ArgumentNamed ( name) => {
443+ if * p == name {
444+ seen = true ;
445+ all_simple &= arg. format == SIMPLE ;
446+ }
447+ } ,
448+ }
449+ }
450+ if all_simple && seen {
451+ span_lint ( cx, lint, rhs. span , "literal with an empty format string" ) ;
413452 }
414- }
415- if all_simple && seen {
416- span_lint ( cx, lint, rhs. span , "literal with an empty format string" ) ;
417453 }
418454 }
419- }
420- } ,
421- _ => idx += 1 ,
455+ } ,
456+ _ => idx += 1 ,
457+ }
422458 }
423459 }
424460}
0 commit comments