@@ -16,6 +16,7 @@ use rustc_parse_format::{self as rpf, Alignment};
1616use rustc_span:: def_id:: DefId ;
1717use rustc_span:: hygiene:: { self , MacroKind , SyntaxContext } ;
1818use rustc_span:: { sym, BytePos , ExpnData , ExpnId , ExpnKind , Pos , Span , SpanData , Symbol } ;
19+ use std:: iter:: { once, zip} ;
1920use std:: ops:: ControlFlow ;
2021
2122const FORMAT_MACRO_DIAG_ITEMS : & [ Symbol ] = & [
@@ -412,7 +413,8 @@ impl FormatString {
412413}
413414
414415struct FormatArgsValues < ' tcx > {
415- /// See `FormatArgsExpn::value_args`
416+ /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
417+ /// `format!("{x} {} {y}", 1, z + 2)`.
416418 value_args : Vec < & ' tcx Expr < ' tcx > > ,
417419 /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
418420 /// `value_args`
@@ -765,12 +767,82 @@ pub struct FormatArgsExpn<'tcx> {
765767 /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
766768 /// include this added newline.
767769 pub newline : bool ,
768- /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
770+ /// Spans of the commas between the format string and explicit values, excluding any trailing
771+ /// comma
772+ ///
773+ /// ```ignore
774+ /// format!("..", 1, 2, 3,)
775+ /// // ^ ^ ^
776+ /// ```
777+ comma_spans : Vec < Span > ,
778+ /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
769779 /// `format!("{x} {} {y}", 1, z + 2)`.
770- value_args : Vec < & ' tcx Expr < ' tcx > > ,
780+ explicit_values : Vec < & ' tcx Expr < ' tcx > > ,
771781}
772782
773783impl < ' tcx > FormatArgsExpn < ' tcx > {
784+ /// Gets the spans of the commas inbetween the format string and explicit args, not including
785+ /// any trailing comma
786+ ///
787+ /// ```ignore
788+ /// format!("{} {}", a, b)
789+ /// // ^ ^
790+ /// ```
791+ ///
792+ /// Ensures that the format string and values aren't coming from a proc macro that sets the
793+ /// output span to that of its input
794+ fn comma_spans ( cx : & LateContext < ' _ > , explicit_values : & [ & Expr < ' _ > ] , fmt_span : Span ) -> Option < Vec < Span > > {
795+ // `format!("{} {} {c}", "one", "two", c = "three")`
796+ // ^^^^^ ^^^^^ ^^^^^^^
797+ let value_spans = explicit_values
798+ . iter ( )
799+ . map ( |val| hygiene:: walk_chain ( val. span , fmt_span. ctxt ( ) ) ) ;
800+
801+ // `format!("{} {} {c}", "one", "two", c = "three")`
802+ // ^^ ^^ ^^^^^^
803+ let between_spans = once ( fmt_span)
804+ . chain ( value_spans)
805+ . tuple_windows ( )
806+ . map ( |( start, end) | start. between ( end) ) ;
807+
808+ let mut comma_spans = Vec :: new ( ) ;
809+ for between_span in between_spans {
810+ let mut offset = 0 ;
811+ let mut seen_comma = false ;
812+
813+ for token in tokenize ( & snippet_opt ( cx, between_span) ?) {
814+ match token. kind {
815+ TokenKind :: LineComment { .. } | TokenKind :: BlockComment { .. } | TokenKind :: Whitespace => { } ,
816+ TokenKind :: Comma if !seen_comma => {
817+ seen_comma = true ;
818+
819+ let base = between_span. data ( ) ;
820+ comma_spans. push ( Span :: new (
821+ base. lo + BytePos ( offset) ,
822+ base. lo + BytePos ( offset + 1 ) ,
823+ base. ctxt ,
824+ base. parent ,
825+ ) ) ;
826+ } ,
827+ // named arguments, `start_val, name = end_val`
828+ // ^^^^^^^^^ between_span
829+ TokenKind :: Ident | TokenKind :: Eq if seen_comma => { } ,
830+ // An unexpected token usually indicates the format string or a value came from a proc macro output
831+ // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
832+ // emits a string literal with the span set to that of `"input"`
833+ _ => return None ,
834+ }
835+ offset += token. len ;
836+ }
837+
838+ if !seen_comma {
839+ return None ;
840+ }
841+ }
842+
843+ Some ( comma_spans)
844+ }
845+
774846 pub fn parse ( cx : & LateContext < ' _ > , expr : & ' tcx Expr < ' tcx > ) -> Option < Self > {
775847 let macro_name = macro_backtrace ( expr. span )
776848 . map ( |macro_call| cx. tcx . item_name ( macro_call. def_id ) )
@@ -845,11 +917,22 @@ impl<'tcx> FormatArgsExpn<'tcx> {
845917 } )
846918 . collect :: < Option < Vec < _ > > > ( ) ?;
847919
920+ let mut explicit_values = values. value_args ;
921+ // remove values generated for implicitly captured vars
922+ let len = explicit_values
923+ . iter ( )
924+ . take_while ( |val| !format_string. span . contains ( val. span ) )
925+ . count ( ) ;
926+ explicit_values. truncate ( len) ;
927+
928+ let comma_spans = Self :: comma_spans ( cx, & explicit_values, format_string. span ) ?;
929+
848930 Some ( Self {
849931 format_string,
850932 args,
851- value_args : values. value_args ,
852933 newline,
934+ comma_spans,
935+ explicit_values,
853936 } )
854937 } else {
855938 None
@@ -875,7 +958,7 @@ impl<'tcx> FormatArgsExpn<'tcx> {
875958
876959 /// Source callsite span of all inputs
877960 pub fn inputs_span ( & self ) -> Span {
878- match * self . value_args {
961+ match * self . explicit_values {
879962 [ ] => self . format_string . span ,
880963 [ .., last] => self
881964 . format_string
@@ -884,6 +967,22 @@ impl<'tcx> FormatArgsExpn<'tcx> {
884967 }
885968 }
886969
970+ /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
971+ ///
972+ /// ```ignore
973+ /// format("{}.{}", 10, 11)
974+ /// // ^^^^
975+ /// ```
976+ pub fn value_with_prev_comma_span ( & self , value_id : HirId ) -> Option < Span > {
977+ for ( comma_span, value) in zip ( & self . comma_spans , & self . explicit_values ) {
978+ if value. hir_id == value_id {
979+ return Some ( comma_span. to ( hygiene:: walk_chain ( value. span , comma_span. ctxt ( ) ) ) ) ;
980+ }
981+ }
982+
983+ None
984+ }
985+
887986 /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
888987 pub fn params ( & ' tcx self ) -> impl Iterator < Item = FormatParam < ' tcx > > {
889988 self . args
0 commit comments