11#![ allow( clippy:: similar_names) ] // `expr` and `expn`
22
3+ use crate :: source:: snippet_opt;
34use crate :: visitors:: expr_visitor_no_bodies;
45
56use arrayvec:: ArrayVec ;
67use if_chain:: if_chain;
78use rustc_ast:: ast:: LitKind ;
89use 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 } ;
1011use rustc_lint:: LateContext ;
1112use rustc_span:: def_id:: DefId ;
1213use 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;
1417use std:: ops:: ControlFlow ;
1518
1619const FORMAT_MACRO_DIAG_ITEMS : & [ Symbol ] = & [
@@ -66,7 +69,7 @@ impl MacroCall {
6669
6770/// Returns an iterator of expansions that created the given span
6871pub fn expn_backtrace ( mut span : Span ) -> impl Iterator < Item = ( ExpnId , ExpnData ) > {
69- std :: iter:: from_fn ( move || {
72+ iter:: from_fn ( move || {
7073 let ctxt = span. ctxt ( ) ;
7174 if ctxt == SyntaxContext :: root ( ) {
7275 return None ;
@@ -90,7 +93,7 @@ pub fn expn_is_local(expn: ExpnId) -> bool {
9093 }
9194 let data = expn. expn_data ( ) ;
9295 let backtrace = expn_backtrace ( data. call_site ) ;
93- std :: iter:: once ( ( expn, data) )
96+ iter:: once ( ( expn, data) )
9497 . chain ( backtrace)
9598 . find_map ( |( _, data) | data. macro_def_id )
9699 . map_or ( true , DefId :: is_local)
@@ -467,20 +470,24 @@ impl<'tcx> FormatArgsExpn<'tcx> {
467470 }
468471
469472 /// 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+
471476 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 {
474480 value,
475481 format_trait,
482+ span,
476483 spec : None ,
477484 } )
478485 . collect ( ) ;
479486 return Some ( args) ;
480487 }
481- self . specs
482- . iter ( )
483- . map ( |spec| {
488+
489+ iter:: zip ( & self . specs , spans )
490+ . map ( |( spec, span ) | {
484491 if_chain ! {
485492 // struct `core::fmt::rt::v1::Argument`
486493 if let ExprKind :: Struct ( _, fields, _) = spec. kind;
@@ -493,6 +500,7 @@ impl<'tcx> FormatArgsExpn<'tcx> {
493500 Some ( FormatArgsArg {
494501 value: self . value_args[ j] ,
495502 format_trait,
503+ span,
496504 spec: Some ( spec) ,
497505 } )
498506 } else {
@@ -503,6 +511,14 @@ impl<'tcx> FormatArgsExpn<'tcx> {
503511 . collect ( )
504512 }
505513
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+
506522 /// Source callsite span of all inputs
507523 pub fn inputs_span ( & self ) -> Span {
508524 match * self . value_args {
@@ -512,6 +528,36 @@ impl<'tcx> FormatArgsExpn<'tcx> {
512528 . to ( hygiene:: walk_chain ( last. span , self . format_string_span . ctxt ( ) ) ) ,
513529 }
514530 }
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+ }
515561}
516562
517563/// Type representing a `FormatArgsExpn`'s format arguments
@@ -522,12 +568,36 @@ pub struct FormatArgsArg<'tcx> {
522568 pub format_trait : Symbol ,
523569 /// An element of `specs`
524570 pub spec : Option < & ' tcx Expr < ' tcx > > ,
571+ /// Span of the `{}`
572+ pub span : Span ,
525573}
526574
527575impl < ' tcx > FormatArgsArg < ' tcx > {
528576 /// 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
530581 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 {
531601 self . spec . map_or ( false , |spec| {
532602 // `!` because these conditions check that `self` is unformatted.
533603 !if_chain ! {
@@ -536,21 +606,23 @@ impl<'tcx> FormatArgsArg<'tcx> {
536606 if let Some ( format_field) = fields. iter( ) . find( |f| f. ident. name == sym:: format) ;
537607 // struct `core::fmt::rt::v1::FormatSpec`
538608 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) ;
548610 then { true } else { false }
549611 }
550612 } )
551613 }
552614}
553615
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+
554626/// A node with a `HirId` and a `Span`
555627pub trait HirNode {
556628 fn hir_id ( & self ) -> HirId ;
0 commit comments