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:: {
3
+ find_format_args, format_arg_removal_span, populate_ast_format_args, root_macro_call_first_node, MacroCall ,
4
+ } ;
3
5
use clippy_utils:: source:: { expand_past_previous_comma, snippet_opt} ;
4
6
use clippy_utils:: { is_in_cfg_test, is_in_test_function} ;
5
- use rustc_ast:: LitKind ;
7
+ use rustc_ast:: token:: LitKind ;
8
+ use rustc_ast:: { FormatArgPosition , FormatArgs , FormatArgsPiece , FormatOptions , FormatPlaceholder , FormatTrait } ;
6
9
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { Expr , ExprKind , HirIdMap , Impl , Item , ItemKind } ;
8
- use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10
+ use rustc_hir:: { Expr , Impl , Item , ItemKind } ;
11
+ use rustc_lint:: { EarlyLintPass , LateContext , LateLintPass , LintContext } ;
9
12
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
10
13
use rustc_span:: { sym, BytePos } ;
11
14
@@ -257,6 +260,12 @@ impl_lint_pass!(Write => [
257
260
WRITE_LITERAL ,
258
261
] ) ;
259
262
263
+ impl EarlyLintPass for Write {
264
+ fn check_expr ( & mut self , _: & rustc_lint:: EarlyContext < ' _ > , expr : & rustc_ast:: Expr ) {
265
+ populate_ast_format_args ( expr) ;
266
+ }
267
+ }
268
+
260
269
impl < ' tcx > LateLintPass < ' tcx > for Write {
261
270
fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
262
271
if is_debug_impl ( cx, item) {
@@ -297,34 +306,40 @@ impl<'tcx> LateLintPass<'tcx> for Write {
297
306
_ => return ,
298
307
}
299
308
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
- }
309
+ find_format_args ( cx, expr, macro_call. expn , |format_args| {
310
+ // ignore `writeln!(w)` and `write!(v, some_macro!())`
311
+ if format_args. span . from_expansion ( ) {
312
+ return ;
313
+ }
306
314
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
- }
315
+ match diag_name {
316
+ sym:: print_macro | sym:: eprint_macro | sym:: write_macro => {
317
+ check_newline ( cx, format_args, & macro_call, name) ;
318
+ } ,
319
+ sym:: println_macro | sym:: eprintln_macro | sym:: writeln_macro => {
320
+ check_empty_string ( cx, format_args, & macro_call, name) ;
321
+ } ,
322
+ _ => { } ,
323
+ }
316
324
317
- check_literal ( cx, & format_args, name) ;
325
+ check_literal ( cx, format_args, name) ;
318
326
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" ) ;
327
+ if !self . in_debug_impl {
328
+ for piece in & format_args. template {
329
+ if let & FormatArgsPiece :: Placeholder ( FormatPlaceholder {
330
+ span : Some ( span) ,
331
+ format_trait : FormatTrait :: Debug ,
332
+ ..
333
+ } ) = piece
334
+ {
335
+ span_lint ( cx, USE_DEBUG , span, "use of `Debug`-based formatting" ) ;
336
+ }
323
337
}
324
338
}
325
- }
339
+ } ) ;
326
340
}
327
341
}
342
+
328
343
fn is_debug_impl ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> bool {
329
344
if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , .. } ) = & item. kind
330
345
&& let Some ( trait_id) = trait_ref. trait_def_id ( )
@@ -335,27 +350,28 @@ fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
335
350
}
336
351
}
337
352
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 } ;
353
+ fn check_newline ( cx : & LateContext < ' _ > , format_args : & FormatArgs , macro_call : & MacroCall , name : & str ) {
354
+ let Some ( FormatArgsPiece :: Literal ( last) ) = format_args. template . last ( ) else { return } ;
343
355
344
356
let count_vertical_whitespace = || {
345
- format_string_parts
357
+ format_args
358
+ . template
346
359
. iter ( )
347
- . flat_map ( |part| part. as_str ( ) . chars ( ) )
360
+ . filter_map ( |piece| match piece {
361
+ FormatArgsPiece :: Literal ( literal) => Some ( literal) ,
362
+ FormatArgsPiece :: Placeholder ( _) => None ,
363
+ } )
364
+ . flat_map ( |literal| literal. as_str ( ) . chars ( ) )
348
365
. filter ( |ch| matches ! ( ch, '\r' | '\n' ) )
349
366
. count ( )
350
367
} ;
351
368
352
369
if last. as_str ( ) . ends_with ( '\n' )
353
370
// ignore format strings with other internal vertical whitespace
354
371
&& count_vertical_whitespace ( ) == 1
355
-
356
- // ignore trailing arguments: `print!("Issue\n{}", 1265);`
357
- && format_string_parts. len ( ) > format_args. args . len ( )
358
372
{
373
+ let mut format_string_span = format_args. span ;
374
+
359
375
let lint = if name == "write" {
360
376
format_string_span = expand_past_previous_comma ( cx, format_string_span) ;
361
377
@@ -373,7 +389,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
373
389
let name_span = cx. sess ( ) . source_map ( ) . span_until_char ( macro_call. span , '!' ) ;
374
390
let Some ( format_snippet) = snippet_opt ( cx, format_string_span) else { return } ;
375
391
376
- if format_string_parts . len ( ) == 1 && last. as_str ( ) == "\n " {
392
+ if format_args . template . len ( ) == 1 && last. as_str ( ) == "\n " {
377
393
// print!("\n"), write!(f, "\n")
378
394
379
395
diag. multipart_suggestion (
@@ -398,11 +414,12 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
398
414
}
399
415
}
400
416
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 "
417
+ fn check_empty_string ( cx : & LateContext < ' _ > , format_args : & FormatArgs , macro_call : & MacroCall , name : & str ) {
418
+ if let [ FormatArgsPiece :: Literal ( literal) ] = & format_args. template [ ..]
419
+ && literal. as_str ( ) == "\n "
405
420
{
421
+ let mut span = format_args. span ;
422
+
406
423
let lint = if name == "writeln" {
407
424
span = expand_past_previous_comma ( cx, span) ;
408
425
@@ -428,33 +445,43 @@ fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, ma
428
445
}
429
446
}
430
447
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 ;
448
+ fn check_literal ( cx : & LateContext < ' _ > , format_args : & FormatArgs , name : & str ) {
449
+ let arg_index = |argument : & FormatArgPosition | argument. index . unwrap_or_else ( |pos| pos) ;
450
+
451
+ let mut counts = vec ! [ 0u32 ; format_args. arguments. all_args( ) . len( ) ] ;
452
+ for piece in & format_args. template {
453
+ if let FormatArgsPiece :: Placeholder ( placeholder) = piece {
454
+ counts[ arg_index ( & placeholder. argument ) ] += 1 ;
455
+ }
435
456
}
436
457
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
- '\'' => "'" ,
458
+ for piece in & format_args. template {
459
+ if let FormatArgsPiece :: Placeholder ( FormatPlaceholder {
460
+ argument,
461
+ span : Some ( placeholder_span) ,
462
+ format_trait : FormatTrait :: Display ,
463
+ format_options,
464
+ } ) = piece
465
+ && * format_options == FormatOptions :: default ( )
466
+ && let index = arg_index ( argument)
467
+ && counts[ index] == 1
468
+ && let Some ( arg) = format_args. arguments . by_index ( index)
469
+ && let rustc_ast:: ExprKind :: Lit ( lit) = & arg. expr . kind
470
+ && !arg. expr . span . from_expansion ( )
471
+ && let Some ( value_string) = snippet_opt ( cx, arg. expr . span )
472
+ {
473
+ let ( replacement, replace_raw) = match lit. kind {
474
+ LitKind :: Str | LitKind :: StrRaw ( _) => extract_str_literal ( & value_string) ,
475
+ LitKind :: Char => (
476
+ match lit. symbol . as_str ( ) {
477
+ "\" " => "\\ \" " ,
478
+ "\\ '" => "'" ,
452
479
_ => & value_string[ 1 ..value_string. len ( ) - 1 ] ,
453
480
}
454
481
. to_string ( ) ,
455
482
false ,
456
483
) ,
457
- LitKind :: Bool ( b ) => ( b . to_string ( ) , false ) ,
484
+ LitKind :: Bool => ( lit . symbol . to_string ( ) , false ) ,
458
485
_ => continue ,
459
486
} ;
460
487
@@ -464,7 +491,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
464
491
PRINT_LITERAL
465
492
} ;
466
493
467
- let format_string_is_raw = format_args. format_string . style . is_some ( ) ;
494
+ let Some ( format_string_snippet) = snippet_opt ( cx, format_args. span ) else { continue } ;
495
+ let format_string_is_raw = format_string_snippet. starts_with ( 'r' ) ;
496
+
468
497
let replacement = match ( format_string_is_raw, replace_raw) {
469
498
( false , false ) => Some ( replacement) ,
470
499
( false , true ) => Some ( replacement. replace ( '"' , "\\ \" " ) . replace ( '\\' , "\\ \\ " ) ) ,
@@ -485,23 +514,24 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
485
514
span_lint_and_then (
486
515
cx,
487
516
lint,
488
- value . span ,
517
+ arg . expr . span ,
489
518
"literal with an empty format string" ,
490
519
|diag| {
491
520
if let Some ( replacement) = replacement
492
521
// `format!("{}", "a")`, `format!("{named}", named = "b")
493
522
// ~~~~~ ~~~~~~~~~~~~~
494
- && let Some ( value_span ) = format_args . value_with_prev_comma_span ( value . hir_id )
523
+ && let Some ( removal_span ) = format_arg_removal_span ( format_args , index )
495
524
{
496
525
let replacement = replacement. replace ( '{' , "{{" ) . replace ( '}' , "}}" ) ;
497
526
diag. multipart_suggestion (
498
527
"try this" ,
499
- vec ! [ ( arg . span , replacement) , ( value_span , String :: new( ) ) ] ,
528
+ vec ! [ ( * placeholder_span , replacement) , ( removal_span , String :: new( ) ) ] ,
500
529
Applicability :: MachineApplicable ,
501
530
) ;
502
531
}
503
532
} ,
504
533
) ;
534
+
505
535
}
506
536
}
507
537
}
0 commit comments