1
1
#![ allow( clippy:: similar_names) ] // `expr` and `expn`
2
2
3
+ use crate :: source:: snippet_opt;
3
4
use crate :: visitors:: expr_visitor_no_bodies;
4
5
5
6
use arrayvec:: ArrayVec ;
6
7
use if_chain:: if_chain;
7
8
use rustc_ast:: ast:: LitKind ;
8
9
use rustc_hir:: intravisit:: Visitor ;
9
- use rustc_hir:: { self as hir, Expr , ExprKind , HirId , Node , QPath } ;
10
+ use rustc_hir:: { self as hir, Expr , ExprField , ExprKind , HirId , Node , QPath } ;
10
11
use rustc_lint:: LateContext ;
11
12
use rustc_span:: def_id:: DefId ;
12
13
use rustc_span:: hygiene:: { self , MacroKind , SyntaxContext } ;
13
- use rustc_span:: { sym, ExpnData , ExpnId , ExpnKind , Span , Symbol } ;
14
+ use rustc_span:: source_map:: Spanned ;
15
+ use rustc_span:: { sym, BytePos , ExpnData , ExpnId , ExpnKind , Pos , Span , Symbol } ;
16
+ use std:: iter;
14
17
use std:: ops:: ControlFlow ;
15
18
16
19
const FORMAT_MACRO_DIAG_ITEMS : & [ Symbol ] = & [
@@ -66,7 +69,7 @@ impl MacroCall {
66
69
67
70
/// Returns an iterator of expansions that created the given span
68
71
pub fn expn_backtrace ( mut span : Span ) -> impl Iterator < Item = ( ExpnId , ExpnData ) > {
69
- std :: iter:: from_fn ( move || {
72
+ iter:: from_fn ( move || {
70
73
let ctxt = span. ctxt ( ) ;
71
74
if ctxt == SyntaxContext :: root ( ) {
72
75
return None ;
@@ -90,7 +93,7 @@ pub fn expn_is_local(expn: ExpnId) -> bool {
90
93
}
91
94
let data = expn. expn_data ( ) ;
92
95
let backtrace = expn_backtrace ( data. call_site ) ;
93
- std :: iter:: once ( ( expn, data) )
96
+ iter:: once ( ( expn, data) )
94
97
. chain ( backtrace)
95
98
. find_map ( |( _, data) | data. macro_def_id )
96
99
. map_or ( true , DefId :: is_local)
@@ -467,20 +470,24 @@ impl<'tcx> FormatArgsExpn<'tcx> {
467
470
}
468
471
469
472
/// Returns a vector of `FormatArgsArg`.
470
- pub fn args ( & self ) -> Option < Vec < FormatArgsArg < ' tcx > > > {
473
+ pub fn args ( & self , cx : & LateContext < ' tcx > ) -> Option < Vec < FormatArgsArg < ' tcx > > > {
474
+ let spans = self . format_argument_spans ( cx) ?;
475
+
471
476
if self . specs . is_empty ( ) {
472
- let args = std:: iter:: zip ( & self . value_args , & self . formatters )
473
- . map ( |( value, & ( _, format_trait) ) | FormatArgsArg {
477
+ let args = iter:: zip ( & self . value_args , & self . formatters )
478
+ . zip ( spans)
479
+ . map ( |( ( value, & ( _, format_trait) ) , span) | FormatArgsArg {
474
480
value,
475
481
format_trait,
482
+ span,
476
483
spec : None ,
477
484
} )
478
485
. collect ( ) ;
479
486
return Some ( args) ;
480
487
}
481
- self . specs
482
- . iter ( )
483
- . map ( |spec| {
488
+
489
+ iter:: zip ( & self . specs , spans )
490
+ . map ( |( spec, span ) | {
484
491
if_chain ! {
485
492
// struct `core::fmt::rt::v1::Argument`
486
493
if let ExprKind :: Struct ( _, fields, _) = spec. kind;
@@ -493,6 +500,7 @@ impl<'tcx> FormatArgsExpn<'tcx> {
493
500
Some ( FormatArgsArg {
494
501
value: self . value_args[ j] ,
495
502
format_trait,
503
+ span,
496
504
spec: Some ( spec) ,
497
505
} )
498
506
} else {
@@ -503,6 +511,14 @@ impl<'tcx> FormatArgsExpn<'tcx> {
503
511
. collect ( )
504
512
}
505
513
514
+ pub fn is_raw ( & self , cx : & LateContext < ' tcx > ) -> bool {
515
+ if let Some ( format_string) = snippet_opt ( cx, self . format_string_span ) {
516
+ format_string. starts_with ( "r" )
517
+ } else {
518
+ false
519
+ }
520
+ }
521
+
506
522
/// Source callsite span of all inputs
507
523
pub fn inputs_span ( & self ) -> Span {
508
524
match * self . value_args {
@@ -512,6 +528,36 @@ impl<'tcx> FormatArgsExpn<'tcx> {
512
528
. to ( hygiene:: walk_chain ( last. span , self . format_string_span . ctxt ( ) ) ) ,
513
529
}
514
530
}
531
+
532
+ /// Returns the spans covering the `{}`s in the format string
533
+ fn format_argument_spans ( & self , cx : & LateContext < ' tcx > ) -> Option < Vec < Span > > {
534
+ let format_string = snippet_opt ( cx, self . format_string_span ) ?. into_bytes ( ) ;
535
+ let lo = self . format_string_span . lo ( ) ;
536
+ let get = |i| format_string. get ( i) . copied ( ) ;
537
+
538
+ let mut spans = Vec :: new ( ) ;
539
+ let mut i = 0 ;
540
+ let mut arg_start = 0 ;
541
+ loop {
542
+ match ( get ( i) , get ( i + 1 ) ) {
543
+ ( Some ( b'{' ) , Some ( b'{' ) ) | ( Some ( b'}' ) , Some ( b'}' ) ) => {
544
+ i += 1 ;
545
+ } ,
546
+ ( Some ( b'{' ) , _) => arg_start = i,
547
+ ( Some ( b'}' ) , _) => spans. push (
548
+ self . format_string_span
549
+ . with_lo ( lo + BytePos :: from_usize ( arg_start) )
550
+ . with_hi ( lo + BytePos :: from_usize ( i + 1 ) ) ,
551
+ ) ,
552
+ ( None , _) => break ,
553
+ _ => { } ,
554
+ }
555
+
556
+ i += 1 ;
557
+ }
558
+
559
+ Some ( spans)
560
+ }
515
561
}
516
562
517
563
/// Type representing a `FormatArgsExpn`'s format arguments
@@ -522,12 +568,36 @@ pub struct FormatArgsArg<'tcx> {
522
568
pub format_trait : Symbol ,
523
569
/// An element of `specs`
524
570
pub spec : Option < & ' tcx Expr < ' tcx > > ,
571
+ /// Span of the `{}`
572
+ pub span : Span ,
525
573
}
526
574
527
575
impl < ' tcx > FormatArgsArg < ' tcx > {
528
576
/// Returns true if any formatting parameters are used that would have an effect on strings,
529
- /// like `{:+2}` instead of just `{}`.
577
+ /// like `{:<2}` instead of just `{}`.
578
+ ///
579
+ /// `{:+}` would return false for `has_string_formatting`, but true for
580
+ /// `has_primitive_formatting` as it effects numbers but not strings
530
581
pub fn has_string_formatting ( & self ) -> bool {
582
+ self . has_formatting ( field_effects_string)
583
+ }
584
+
585
+ /// Returns true if any formatting parameters are used that would have an effect on primitives,
586
+ /// like `{:+2}` instead of just `{}`.
587
+ pub fn has_primitive_formatting ( & self ) -> bool {
588
+ self . has_formatting ( |field| match field. ident . name {
589
+ sym:: flags => matches ! (
590
+ field. expr. kind,
591
+ ExprKind :: Lit ( Spanned {
592
+ node: LitKind :: Int ( 0 , _) ,
593
+ ..
594
+ } ) ,
595
+ ) ,
596
+ _ => field_effects_string ( field) ,
597
+ } )
598
+ }
599
+
600
+ fn has_formatting ( & self , callback : impl FnMut ( & ExprField < ' _ > ) -> bool ) -> bool {
531
601
self . spec . map_or ( false , |spec| {
532
602
// `!` because these conditions check that `self` is unformatted.
533
603
!if_chain ! {
@@ -536,21 +606,23 @@ impl<'tcx> FormatArgsArg<'tcx> {
536
606
if let Some ( format_field) = fields. iter( ) . find( |f| f. ident. name == sym:: format) ;
537
607
// struct `core::fmt::rt::v1::FormatSpec`
538
608
if let ExprKind :: Struct ( _, subfields, _) = format_field. expr. kind;
539
- if subfields. iter( ) . all( |field| match field. ident. name {
540
- sym:: precision | sym:: width => match field. expr. kind {
541
- ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
542
- path. segments. last( ) . unwrap( ) . ident. name == sym:: Implied
543
- }
544
- _ => false ,
545
- }
546
- _ => true ,
547
- } ) ;
609
+ if subfields. iter( ) . all( callback) ;
548
610
then { true } else { false }
549
611
}
550
612
} )
551
613
}
552
614
}
553
615
616
+ fn field_effects_string ( field : & ExprField < ' _ > ) -> bool {
617
+ match field. ident . name {
618
+ sym:: precision | sym:: width => match field. expr . kind {
619
+ ExprKind :: Path ( QPath :: Resolved ( _, path) ) => path. segments . last ( ) . unwrap ( ) . ident . name == sym:: Implied ,
620
+ _ => false ,
621
+ } ,
622
+ _ => true ,
623
+ }
624
+ }
625
+
554
626
/// A node with a `HirId` and a `Span`
555
627
pub trait HirNode {
556
628
fn hir_id ( & self ) -> HirId ;
0 commit comments