1
1
use 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 } ;
3
3
use clippy_utils:: source:: { expand_past_previous_comma, snippet_opt} ;
4
4
use 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 } ;
6
7
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { Expr , ExprKind , HirIdMap , Impl , Item , ItemKind } ;
8
+ use rustc_hir:: { Expr , Impl , Item , ItemKind } ;
8
9
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9
10
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
10
11
use rustc_span:: { sym, BytePos } ;
@@ -297,34 +298,40 @@ impl<'tcx> LateLintPass<'tcx> for Write {
297
298
_ => return ,
298
299
}
299
300
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
+ }
306
306
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
+ }
316
316
317
- check_literal ( cx, & format_args, name) ;
317
+ check_literal ( cx, format_args, name) ;
318
318
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
+ }
323
329
}
324
330
}
325
- }
331
+ } ) ;
326
332
}
327
333
}
334
+
328
335
fn is_debug_impl ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> bool {
329
336
if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , .. } ) = & item. kind
330
337
&& let Some ( trait_id) = trait_ref. trait_def_id ( )
@@ -335,27 +342,28 @@ fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
335
342
}
336
343
}
337
344
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 } ;
343
347
344
348
let count_vertical_whitespace = || {
345
- format_string_parts
349
+ format_args
350
+ . template
346
351
. 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 ( ) )
348
357
. filter ( |ch| matches ! ( ch, '\r' | '\n' ) )
349
358
. count ( )
350
359
} ;
351
360
352
361
if last. as_str ( ) . ends_with ( '\n' )
353
362
// ignore format strings with other internal vertical whitespace
354
363
&& count_vertical_whitespace ( ) == 1
355
-
356
- // ignore trailing arguments: `print!("Issue\n{}", 1265);`
357
- && format_string_parts. len ( ) > format_args. args . len ( )
358
364
{
365
+ let mut format_string_span = format_args. span ;
366
+
359
367
let lint = if name == "write" {
360
368
format_string_span = expand_past_previous_comma ( cx, format_string_span) ;
361
369
@@ -373,7 +381,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
373
381
let name_span = cx. sess ( ) . source_map ( ) . span_until_char ( macro_call. span , '!' ) ;
374
382
let Some ( format_snippet) = snippet_opt ( cx, format_string_span) else { return } ;
375
383
376
- if format_string_parts . len ( ) == 1 && last. as_str ( ) == "\n " {
384
+ if format_args . template . len ( ) == 1 && last. as_str ( ) == "\n " {
377
385
// print!("\n"), write!(f, "\n")
378
386
379
387
diag. multipart_suggestion (
@@ -398,11 +406,12 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
398
406
}
399
407
}
400
408
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 "
405
412
{
413
+ let mut span = format_args. span ;
414
+
406
415
let lint = if name == "writeln" {
407
416
span = expand_past_previous_comma ( cx, span) ;
408
417
@@ -428,33 +437,43 @@ fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, ma
428
437
}
429
438
}
430
439
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
+ }
435
448
}
436
449
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
+ "\\ '" => "'" ,
452
471
_ => & value_string[ 1 ..value_string. len ( ) - 1 ] ,
453
472
}
454
473
. to_string ( ) ,
455
474
false ,
456
475
) ,
457
- LitKind :: Bool ( b ) => ( b . to_string ( ) , false ) ,
476
+ LitKind :: Bool => ( lit . symbol . to_string ( ) , false ) ,
458
477
_ => continue ,
459
478
} ;
460
479
@@ -464,7 +483,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
464
483
PRINT_LITERAL
465
484
} ;
466
485
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
+
468
489
let replacement = match ( format_string_is_raw, replace_raw) {
469
490
( false , false ) => Some ( replacement) ,
470
491
( false , true ) => Some ( replacement. replace ( '"' , "\\ \" " ) . replace ( '\\' , "\\ \\ " ) ) ,
@@ -485,23 +506,24 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
485
506
span_lint_and_then (
486
507
cx,
487
508
lint,
488
- value . span ,
509
+ arg . expr . span ,
489
510
"literal with an empty format string" ,
490
511
|diag| {
491
512
if let Some ( replacement) = replacement
492
513
// `format!("{}", "a")`, `format!("{named}", named = "b")
493
514
// ~~~~~ ~~~~~~~~~~~~~
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 )
495
516
{
496
517
let replacement = replacement. replace ( '{' , "{{" ) . replace ( '}' , "}}" ) ;
497
518
diag. multipart_suggestion (
498
519
"try this" ,
499
- vec ! [ ( arg . span , replacement) , ( value_span , String :: new( ) ) ] ,
520
+ vec ! [ ( * placeholder_span , replacement) , ( removal_span , String :: new( ) ) ] ,
500
521
Applicability :: MachineApplicable ,
501
522
) ;
502
523
}
503
524
} ,
504
525
) ;
526
+
505
527
}
506
528
}
507
529
}
0 commit comments