11use clippy_utils:: diagnostics:: { span_lint, span_lint_and_then} ;
2- use clippy_utils:: macros:: { root_macro_call_first_node , FormatArgsExpn , MacroCall } ;
2+ use clippy_utils:: macros:: { find_format_args , format_arg_removal_span , root_macro_call_first_node , MacroCall } ;
33use clippy_utils:: source:: { expand_past_previous_comma, snippet_opt} ;
44use clippy_utils:: { is_in_cfg_test, is_in_test_function} ;
5- use rustc_ast:: LitKind ;
5+ use rustc_ast:: token:: LitKind ;
6+ use rustc_ast:: { FormatArgPosition , FormatArgs , FormatArgsPiece , FormatOptions , FormatPlaceholder , FormatTrait } ;
67use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind , HirIdMap , Impl , Item , ItemKind } ;
8+ use rustc_hir:: { Expr , Impl , Item , ItemKind } ;
89use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
910use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1011use rustc_span:: { sym, BytePos } ;
@@ -297,34 +298,40 @@ impl<'tcx> LateLintPass<'tcx> for Write {
297298 _ => return ,
298299 }
299300
300- let Some ( format_args) = FormatArgsExpn :: find_nested ( cx, expr, macro_call. expn ) else { return } ;
301-
302- // ignore `writeln!(w)` and `write!(v, some_macro!())`
303- if format_args. format_string . span . from_expansion ( ) {
304- return ;
305- }
301+ find_format_args ( cx, expr, macro_call. expn , |format_args| {
302+ // ignore `writeln!(w)` and `write!(v, some_macro!())`
303+ if format_args. span . from_expansion ( ) {
304+ return ;
305+ }
306306
307- match diag_name {
308- sym:: print_macro | sym:: eprint_macro | sym:: write_macro => {
309- check_newline ( cx, & format_args, & macro_call, name) ;
310- } ,
311- sym:: println_macro | sym:: eprintln_macro | sym:: writeln_macro => {
312- check_empty_string ( cx, & format_args, & macro_call, name) ;
313- } ,
314- _ => { } ,
315- }
307+ match diag_name {
308+ sym:: print_macro | sym:: eprint_macro | sym:: write_macro => {
309+ check_newline ( cx, format_args, & macro_call, name) ;
310+ } ,
311+ sym:: println_macro | sym:: eprintln_macro | sym:: writeln_macro => {
312+ check_empty_string ( cx, format_args, & macro_call, name) ;
313+ } ,
314+ _ => { } ,
315+ }
316316
317- check_literal ( cx, & format_args, name) ;
317+ check_literal ( cx, format_args, name) ;
318318
319- if !self . in_debug_impl {
320- for arg in & format_args. args {
321- if arg. format . r#trait == sym:: Debug {
322- span_lint ( cx, USE_DEBUG , arg. span , "use of `Debug`-based formatting" ) ;
319+ if !self . in_debug_impl {
320+ for piece in & format_args. template {
321+ if let & FormatArgsPiece :: Placeholder ( FormatPlaceholder {
322+ span : Some ( span) ,
323+ format_trait : FormatTrait :: Debug ,
324+ ..
325+ } ) = piece
326+ {
327+ span_lint ( cx, USE_DEBUG , span, "use of `Debug`-based formatting" ) ;
328+ }
323329 }
324330 }
325- }
331+ } ) ;
326332 }
327333}
334+
328335fn is_debug_impl ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> bool {
329336 if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , .. } ) = & item. kind
330337 && let Some ( trait_id) = trait_ref. trait_def_id ( )
@@ -335,27 +342,28 @@ fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
335342 }
336343}
337344
338- fn check_newline ( cx : & LateContext < ' _ > , format_args : & FormatArgsExpn < ' _ > , macro_call : & MacroCall , name : & str ) {
339- let format_string_parts = & format_args. format_string . parts ;
340- let mut format_string_span = format_args. format_string . span ;
341-
342- let Some ( last) = format_string_parts. last ( ) else { return } ;
345+ fn check_newline ( cx : & LateContext < ' _ > , format_args : & FormatArgs , macro_call : & MacroCall , name : & str ) {
346+ let Some ( FormatArgsPiece :: Literal ( last) ) = format_args. template . last ( ) else { return } ;
343347
344348 let count_vertical_whitespace = || {
345- format_string_parts
349+ format_args
350+ . template
346351 . iter ( )
347- . flat_map ( |part| part. as_str ( ) . chars ( ) )
352+ . filter_map ( |piece| match piece {
353+ FormatArgsPiece :: Literal ( literal) => Some ( literal) ,
354+ FormatArgsPiece :: Placeholder ( _) => None ,
355+ } )
356+ . flat_map ( |literal| literal. as_str ( ) . chars ( ) )
348357 . filter ( |ch| matches ! ( ch, '\r' | '\n' ) )
349358 . count ( )
350359 } ;
351360
352361 if last. as_str ( ) . ends_with ( '\n' )
353362 // ignore format strings with other internal vertical whitespace
354363 && count_vertical_whitespace ( ) == 1
355-
356- // ignore trailing arguments: `print!("Issue\n{}", 1265);`
357- && format_string_parts. len ( ) > format_args. args . len ( )
358364 {
365+ let mut format_string_span = format_args. span ;
366+
359367 let lint = if name == "write" {
360368 format_string_span = expand_past_previous_comma ( cx, format_string_span) ;
361369
@@ -373,7 +381,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
373381 let name_span = cx. sess ( ) . source_map ( ) . span_until_char ( macro_call. span , '!' ) ;
374382 let Some ( format_snippet) = snippet_opt ( cx, format_string_span) else { return } ;
375383
376- if format_string_parts . len ( ) == 1 && last. as_str ( ) == "\n " {
384+ if format_args . template . len ( ) == 1 && last. as_str ( ) == "\n " {
377385 // print!("\n"), write!(f, "\n")
378386
379387 diag. multipart_suggestion (
@@ -398,11 +406,12 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
398406 }
399407}
400408
401- fn check_empty_string ( cx : & LateContext < ' _ > , format_args : & FormatArgsExpn < ' _ > , macro_call : & MacroCall , name : & str ) {
402- if let [ part] = & format_args. format_string . parts [ ..]
403- && let mut span = format_args. format_string . span
404- && part. as_str ( ) == "\n "
409+ fn check_empty_string ( cx : & LateContext < ' _ > , format_args : & FormatArgs , macro_call : & MacroCall , name : & str ) {
410+ if let [ FormatArgsPiece :: Literal ( literal) ] = & format_args. template [ ..]
411+ && literal. as_str ( ) == "\n "
405412 {
413+ let mut span = format_args. span ;
414+
406415 let lint = if name == "writeln" {
407416 span = expand_past_previous_comma ( cx, span) ;
408417
@@ -428,33 +437,43 @@ fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, ma
428437 }
429438}
430439
431- fn check_literal ( cx : & LateContext < ' _ > , format_args : & FormatArgsExpn < ' _ > , name : & str ) {
432- let mut counts = HirIdMap :: < usize > :: default ( ) ;
433- for param in format_args. params ( ) {
434- * counts. entry ( param. value . hir_id ) . or_default ( ) += 1 ;
440+ fn check_literal ( cx : & LateContext < ' _ > , format_args : & FormatArgs , name : & str ) {
441+ let arg_index = |argument : & FormatArgPosition | argument. index . unwrap_or_else ( |pos| pos) ;
442+
443+ let mut counts = vec ! [ 0u32 ; format_args. arguments. all_args( ) . len( ) ] ;
444+ for piece in & format_args. template {
445+ if let FormatArgsPiece :: Placeholder ( placeholder) = piece {
446+ counts[ arg_index ( & placeholder. argument ) ] += 1 ;
447+ }
435448 }
436449
437- for arg in & format_args. args {
438- let value = arg. param . value ;
439-
440- if counts[ & value. hir_id ] == 1
441- && arg. format . is_default ( )
442- && let ExprKind :: Lit ( lit) = & value. kind
443- && !value. span . from_expansion ( )
444- && let Some ( value_string) = snippet_opt ( cx, value. span )
445- {
446- let ( replacement, replace_raw) = match lit. node {
447- LitKind :: Str ( ..) => extract_str_literal ( & value_string) ,
448- LitKind :: Char ( ch) => (
449- match ch {
450- '"' => "\\ \" " ,
451- '\'' => "'" ,
450+ for piece in & format_args. template {
451+ if let FormatArgsPiece :: Placeholder ( FormatPlaceholder {
452+ argument,
453+ span : Some ( placeholder_span) ,
454+ format_trait : FormatTrait :: Display ,
455+ format_options,
456+ } ) = piece
457+ && * format_options == FormatOptions :: default ( )
458+ && let index = arg_index ( argument)
459+ && counts[ index] == 1
460+ && let Some ( arg) = format_args. arguments . by_index ( index)
461+ && let rustc_ast:: ExprKind :: Lit ( lit) = & arg. expr . kind
462+ && !arg. expr . span . from_expansion ( )
463+ && let Some ( value_string) = snippet_opt ( cx, arg. expr . span )
464+ {
465+ let ( replacement, replace_raw) = match lit. kind {
466+ LitKind :: Str | LitKind :: StrRaw ( _) => extract_str_literal ( & value_string) ,
467+ LitKind :: Char => (
468+ match lit. symbol . as_str ( ) {
469+ "\" " => "\\ \" " ,
470+ "\\ '" => "'" ,
452471 _ => & value_string[ 1 ..value_string. len ( ) - 1 ] ,
453472 }
454473 . to_string ( ) ,
455474 false ,
456475 ) ,
457- LitKind :: Bool ( b ) => ( b . to_string ( ) , false ) ,
476+ LitKind :: Bool => ( lit . symbol . to_string ( ) , false ) ,
458477 _ => continue ,
459478 } ;
460479
@@ -464,7 +483,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
464483 PRINT_LITERAL
465484 } ;
466485
467- let format_string_is_raw = format_args. format_string . style . is_some ( ) ;
486+ let Some ( format_string_snippet) = snippet_opt ( cx, format_args. span ) else { continue } ;
487+ let format_string_is_raw = format_string_snippet. starts_with ( 'r' ) ;
488+
468489 let replacement = match ( format_string_is_raw, replace_raw) {
469490 ( false , false ) => Some ( replacement) ,
470491 ( false , true ) => Some ( replacement. replace ( '"' , "\\ \" " ) . replace ( '\\' , "\\ \\ " ) ) ,
@@ -485,23 +506,24 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
485506 span_lint_and_then (
486507 cx,
487508 lint,
488- value . span ,
509+ arg . expr . span ,
489510 "literal with an empty format string" ,
490511 |diag| {
491512 if let Some ( replacement) = replacement
492513 // `format!("{}", "a")`, `format!("{named}", named = "b")
493514 // ~~~~~ ~~~~~~~~~~~~~
494- && let Some ( value_span ) = format_args . value_with_prev_comma_span ( value . hir_id )
515+ && let Some ( removal_span ) = format_arg_removal_span ( format_args , index )
495516 {
496517 let replacement = replacement. replace ( '{' , "{{" ) . replace ( '}' , "}}" ) ;
497518 diag. multipart_suggestion (
498519 "try this" ,
499- vec ! [ ( arg . span , replacement) , ( value_span , String :: new( ) ) ] ,
520+ vec ! [ ( * placeholder_span , replacement) , ( removal_span , String :: new( ) ) ] ,
500521 Applicability :: MachineApplicable ,
501522 ) ;
502523 }
503524 } ,
504525 ) ;
526+
505527 }
506528 }
507529}
0 commit comments