@@ -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`
@@ -675,6 +677,68 @@ impl<'tcx> Count<'tcx> {
675677 }
676678}
677679
680+ /// Gets the spans of the commas inbetween the format string and explicit args, not including any
681+ /// trailing comma
682+ ///
683+ /// ```ignore
684+ /// format!("{} {}", a, b)
685+ /// // ^ ^
686+ /// ```
687+ ///
688+ /// Ensures that the format string and values aren't coming from a proc macro that sets the output
689+ /// span to that of its input
690+ fn comma_spans ( cx : & LateContext < ' _ > , explicit_values : & [ & Expr < ' _ > ] , fmt_span : Span ) -> Option < Vec < Span > > {
691+ // `format!("{} {} {c}", "one", "two", c = "three")`
692+ // ^^^^^ ^^^^^ ^^^^^^^
693+ let value_spans = explicit_values
694+ . iter ( )
695+ . map ( |val| hygiene:: walk_chain ( val. span , fmt_span. ctxt ( ) ) ) ;
696+
697+ // `format!("{} {} {c}", "one", "two", c = "three")`
698+ // ^^ ^^ ^^^^^^
699+ let between_spans = once ( fmt_span)
700+ . chain ( value_spans)
701+ . tuple_windows ( )
702+ . map ( |( start, end) | start. between ( end) ) ;
703+
704+ let mut comma_spans = Vec :: new ( ) ;
705+ for between_span in between_spans {
706+ let mut offset = 0 ;
707+ let mut seen_comma = false ;
708+
709+ for token in tokenize ( & snippet_opt ( cx, between_span) ?) {
710+ match token. kind {
711+ TokenKind :: LineComment { .. } | TokenKind :: BlockComment { .. } | TokenKind :: Whitespace => { } ,
712+ TokenKind :: Comma if !seen_comma => {
713+ seen_comma = true ;
714+
715+ let base = between_span. data ( ) ;
716+ comma_spans. push ( Span :: new (
717+ base. lo + BytePos ( offset) ,
718+ base. lo + BytePos ( offset + 1 ) ,
719+ base. ctxt ,
720+ base. parent ,
721+ ) ) ;
722+ } ,
723+ // named arguments, `start_val, name = end_val`
724+ // ^^^^^^^^^ between_span
725+ TokenKind :: Ident | TokenKind :: Eq if seen_comma => { } ,
726+ // An unexpected token usually indicates the format string or a value came from a proc macro output that
727+ // sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that emits a
728+ // string literal with the span set to that of `"input"`
729+ _ => return None ,
730+ }
731+ offset += token. len ;
732+ }
733+
734+ if !seen_comma {
735+ return None ;
736+ }
737+ }
738+
739+ Some ( comma_spans)
740+ }
741+
678742/// Specification for the formatting of an argument in the format string. See
679743/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
680744#[ derive( Debug ) ]
@@ -765,9 +829,17 @@ pub struct FormatArgsExpn<'tcx> {
765829 /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
766830 /// include this added newline.
767831 pub newline : bool ,
768- /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
832+ /// Spans of the commas between the format string and explicit values, excluding any trailing
833+ /// comma
834+ ///
835+ /// ```ignore
836+ /// format!("..", 1, 2, 3,)
837+ /// // ^ ^ ^
838+ /// ```
839+ comma_spans : Vec < Span > ,
840+ /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
769841 /// `format!("{x} {} {y}", 1, z + 2)`.
770- value_args : Vec < & ' tcx Expr < ' tcx > > ,
842+ explicit_values : Vec < & ' tcx Expr < ' tcx > > ,
771843}
772844
773845impl < ' tcx > FormatArgsExpn < ' tcx > {
@@ -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 = 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