@@ -16,6 +16,7 @@ use rustc_parse_format::{self as rpf, Alignment};
16
16
use rustc_span:: def_id:: DefId ;
17
17
use rustc_span:: hygiene:: { self , MacroKind , SyntaxContext } ;
18
18
use rustc_span:: { sym, BytePos , ExpnData , ExpnId , ExpnKind , Pos , Span , SpanData , Symbol } ;
19
+ use std:: iter:: { once, zip} ;
19
20
use std:: ops:: ControlFlow ;
20
21
21
22
const FORMAT_MACRO_DIAG_ITEMS : & [ Symbol ] = & [
@@ -412,7 +413,8 @@ impl FormatString {
412
413
}
413
414
414
415
struct 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)`.
416
418
value_args : Vec < & ' tcx Expr < ' tcx > > ,
417
419
/// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
418
420
/// `value_args`
@@ -675,6 +677,68 @@ impl<'tcx> Count<'tcx> {
675
677
}
676
678
}
677
679
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
+
678
742
/// Specification for the formatting of an argument in the format string. See
679
743
/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
680
744
#[ derive( Debug ) ]
@@ -765,9 +829,17 @@ pub struct FormatArgsExpn<'tcx> {
765
829
/// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
766
830
/// include this added newline.
767
831
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
769
841
/// `format!("{x} {} {y}", 1, z + 2)`.
770
- value_args : Vec < & ' tcx Expr < ' tcx > > ,
842
+ explicit_values : Vec < & ' tcx Expr < ' tcx > > ,
771
843
}
772
844
773
845
impl < ' tcx > FormatArgsExpn < ' tcx > {
@@ -845,11 +917,22 @@ impl<'tcx> FormatArgsExpn<'tcx> {
845
917
} )
846
918
. collect :: < Option < Vec < _ > > > ( ) ?;
847
919
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
+
848
930
Some ( Self {
849
931
format_string,
850
932
args,
851
- value_args : values. value_args ,
852
933
newline,
934
+ comma_spans,
935
+ explicit_values,
853
936
} )
854
937
} else {
855
938
None
@@ -875,7 +958,7 @@ impl<'tcx> FormatArgsExpn<'tcx> {
875
958
876
959
/// Source callsite span of all inputs
877
960
pub fn inputs_span ( & self ) -> Span {
878
- match * self . value_args {
961
+ match * self . explicit_values {
879
962
[ ] => self . format_string . span ,
880
963
[ .., last] => self
881
964
. format_string
@@ -884,6 +967,22 @@ impl<'tcx> FormatArgsExpn<'tcx> {
884
967
}
885
968
}
886
969
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
+
887
986
/// Iterator of all format params, both values and those referenced by `width`/`precision`s.
888
987
pub fn params ( & ' tcx self ) -> impl Iterator < Item = FormatParam < ' tcx > > {
889
988
self . args
0 commit comments