1- use clippy_utils:: diagnostics:: span_lint_and_sugg;
2- use clippy_utils:: format:: { check_unformatted, is_display_arg} ;
3- use clippy_utils:: higher:: { FormatArgsExpn , FormatExpn } ;
1+ use clippy_utils:: diagnostics:: { span_lint_and_help, span_lint_and_sugg} ;
2+ use clippy_utils:: higher:: { FormatArgsArg , FormatArgsExpn , FormatExpn , Formatting } ;
43use clippy_utils:: source:: snippet_opt;
54use clippy_utils:: ty:: implements_trait;
65use clippy_utils:: { get_trait_def_id, match_def_path, paths} ;
@@ -78,22 +77,21 @@ impl<'tcx> LateLintPass<'tcx> for ToStringInFormatArgs {
7877
7978fn check_expr < ' tcx , F > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , check_value : F )
8079where
81- F : Fn ( & LateContext < ' _ > , & FormatArgsExpn < ' _ > , Span , Symbol , usize , & Expr < ' _ > ) -> bool ,
80+ F : Fn ( & LateContext < ' _ > , & FormatArgsExpn < ' _ > , Span , Symbol , usize , & FormatArgsArg < ' _ > ) -> bool ,
8281{
8382 if_chain ! {
8483 if let Some ( format_args) = FormatArgsExpn :: parse( expr) ;
8584 let call_site = expr. span. ctxt( ) . outer_expn_data( ) . call_site;
8685 if call_site. from_expansion( ) ;
8786 let expn_data = call_site. ctxt( ) . outer_expn_data( ) ;
8887 if let ExpnKind :: Macro ( _, name) = expn_data. kind;
89- if format_args. fmt_expr . map_or ( true , check_unformatted ) ;
88+ if ! format_args. has_formatting ( Formatting :: all ( ) ) ;
9089 then {
91- assert_eq!( format_args. args. len( ) , format_args. value_args. len( ) ) ;
92- for ( i, ( arg, value) ) in format_args. args. iter( ) . zip( format_args. value_args. iter( ) ) . enumerate( ) {
93- if !is_display_arg( arg) {
90+ for ( i, arg) in format_args. args( ) . enumerate( ) {
91+ if !arg. is_display( ) {
9492 continue ;
9593 }
96- if check_value( cx, & format_args, expn_data. call_site, name, i, value ) {
94+ if check_value( cx, & format_args, expn_data. call_site, name, i, & arg ) {
9795 break ;
9896 }
9997 }
@@ -107,22 +105,14 @@ fn format_in_format_args(
107105 call_site : Span ,
108106 name : Symbol ,
109107 i : usize ,
110- value : & Expr < ' _ > ,
108+ arg : & FormatArgsArg < ' _ > ,
111109) -> bool {
112- if_chain ! {
113- if let Some ( FormatExpn { format_args: inner_format_args, .. } ) = FormatExpn :: parse( value) ;
114- if let Some ( format_string) = snippet_opt( cx, format_args. format_string_span) ;
115- if let Some ( inner_format_string) = snippet_opt( cx, inner_format_args. format_string_span) ;
116- if let Some ( ( sugg, applicability) ) = format_in_format_args_sugg(
117- cx,
118- name,
119- & format_string,
120- & format_args. value_args,
121- i,
122- & inner_format_string,
123- & inner_format_args. value_args
124- ) ;
125- then {
110+ if let Some ( FormatExpn {
111+ format_args : inner_format_args,
112+ ..
113+ } ) = FormatExpn :: parse ( arg. value ( ) )
114+ {
115+ if let Some ( ( sugg, applicability) ) = format_in_format_args_sugg ( cx, name, format_args, i, & inner_format_args) {
126116 span_lint_and_sugg (
127117 cx,
128118 FORMAT_IN_FORMAT_ARGS ,
@@ -132,9 +122,18 @@ fn format_in_format_args(
132122 sugg,
133123 applicability,
134124 ) ;
135- // Report only the first instance.
136- return true ;
125+ } else {
126+ span_lint_and_help (
127+ cx,
128+ FORMAT_IN_FORMAT_ARGS ,
129+ trim_semicolon ( cx, call_site) ,
130+ & format ! ( "`format!` in `{}!` args" , name) ,
131+ None ,
132+ "inline the `format!` call" ,
133+ ) ;
137134 }
135+ // Report only the first instance.
136+ return true ;
138137 }
139138 false
140139}
@@ -145,8 +144,9 @@ fn to_string_in_format_args(
145144 _call_site : Span ,
146145 name : Symbol ,
147146 _i : usize ,
148- value : & Expr < ' _ > ,
147+ arg : & FormatArgsArg < ' _ > ,
149148) -> bool {
149+ let value = arg. value ( ) ;
150150 if_chain ! {
151151 if let ExprKind :: MethodCall ( _, _, args, _) = value. kind;
152152 if let Some ( method_def_id) = cx. typeck_results( ) . type_dependent_def_id( value. hir_id) ;
@@ -178,27 +178,35 @@ fn to_string_in_format_args(
178178fn format_in_format_args_sugg (
179179 cx : & LateContext < ' _ > ,
180180 name : Symbol ,
181- format_string : & str ,
182- values : & [ & Expr < ' _ > ] ,
181+ format_args : & FormatArgsExpn < ' _ > ,
183182 i : usize ,
184- inner_format_string : & str ,
185- inner_values : & [ & Expr < ' _ > ] ,
183+ inner_format_args : & FormatArgsExpn < ' _ > ,
186184) -> Option < ( String , Applicability ) > {
187- let ( left, right) = split_format_string ( format_string, i) ;
185+ let format_string = snippet_opt ( cx, format_args. format_string_span ) ?;
186+ if is_positional ( & format_string) {
187+ return None ;
188+ }
189+ let ( left, right) = split_format_string ( & format_string, i) ;
190+ let inner_format_string = snippet_opt ( cx, inner_format_args. format_string_span ) ?;
191+ if is_positional ( & inner_format_string) {
192+ return None ;
193+ }
188194 // If the inner format string is raw, the user is on their own.
189195 let ( new_format_string, applicability) = if inner_format_string. starts_with ( 'r' ) {
190196 ( left + ".." + & right, Applicability :: HasPlaceholders )
191197 } else {
192198 (
193- left + & trim_quotes ( inner_format_string) + & right,
199+ left + & trim_quotes ( & inner_format_string) + & right,
194200 Applicability :: MachineApplicable ,
195201 )
196202 } ;
197- let values = values
203+ let values = format_args
204+ . value_args
198205 . iter ( )
199206 . map ( |value| snippet_opt ( cx, value. span ) )
200207 . collect :: < Option < Vec < _ > > > ( ) ?;
201- let inner_values = inner_values
208+ let inner_values = inner_format_args
209+ . value_args
202210 . iter ( )
203211 . map ( |value| snippet_opt ( cx, value. span ) )
204212 . collect :: < Option < Vec < _ > > > ( ) ?;
@@ -216,6 +224,23 @@ fn format_in_format_args_sugg(
216224 ) )
217225}
218226
227+ // Checking the position fields of the `core::fmt::rt::v1::Argument` would not be sufficient
228+ // because, for example, "{}{}" and "{}{1:}" could not be distinguished. Note that the `{}` could be
229+ // replaced in the former, but not the latter.
230+ fn is_positional ( format_string : & str ) -> bool {
231+ let mut iter = format_string. chars ( ) ;
232+ while let Some ( first_char) = iter. next ( ) {
233+ if first_char != '{' {
234+ continue ;
235+ }
236+ let second_char = iter. next ( ) . unwrap ( ) ;
237+ if second_char. is_digit ( 10 ) {
238+ return true ;
239+ }
240+ }
241+ false
242+ }
243+
219244fn split_format_string ( format_string : & str , i : usize ) -> ( String , String ) {
220245 let mut iter = format_string. chars ( ) ;
221246 for j in 0 ..=i {
0 commit comments