@@ -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`
@@ -765,12 +767,82 @@ pub struct FormatArgsExpn<'tcx> {
765
767
/// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
766
768
/// include this added newline.
767
769
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
769
779
/// `format!("{x} {} {y}", 1, z + 2)`.
770
- value_args : Vec < & ' tcx Expr < ' tcx > > ,
780
+ explicit_values : Vec < & ' tcx Expr < ' tcx > > ,
771
781
}
772
782
773
783
impl < ' 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
+
774
846
pub fn parse ( cx : & LateContext < ' _ > , expr : & ' tcx Expr < ' tcx > ) -> Option < Self > {
775
847
let macro_name = macro_backtrace ( expr. span )
776
848
. map ( |macro_call| cx. tcx . item_name ( macro_call. def_id ) )
@@ -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 = Self :: 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