@@ -4,11 +4,15 @@ use clippy_utils::source::snippet_opt;
44use clippy_utils:: ty:: implements_trait;
55use clippy_utils:: { get_trait_def_id, match_def_path, paths} ;
66use if_chain:: if_chain;
7+ use rustc_ast:: ast:: LitKind ;
78use rustc_errors:: Applicability ;
9+ use rustc_hir:: def_id:: DefId ;
810use rustc_hir:: { Expr , ExprKind } ;
911use rustc_lint:: { LateContext , LateLintPass } ;
12+ use rustc_middle:: ty:: subst:: GenericArg ;
13+ use rustc_middle:: ty:: Ty ;
1014use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
11- use rustc_span:: { BytePos , ExpnKind , Span , Symbol } ;
15+ use rustc_span:: { sym , BytePos , ExpnKind , Span , Symbol } ;
1216
1317declare_clippy_lint ! {
1418 /// ### What it does
@@ -34,14 +38,6 @@ declare_clippy_lint! {
3438 "`format!` used in a macro that does formatting"
3539}
3640
37- declare_lint_pass ! ( FormatInFormatArgs => [ FORMAT_IN_FORMAT_ARGS ] ) ;
38-
39- impl < ' tcx > LateLintPass < ' tcx > for FormatInFormatArgs {
40- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
41- check_expr ( cx, expr, format_in_format_args) ;
42- }
43- }
44-
4541declare_clippy_lint ! {
4642 /// ### What it does
4743 /// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
@@ -67,216 +63,115 @@ declare_clippy_lint! {
6763 "`to_string` applied to a type that implements `Display` in format args"
6864}
6965
70- declare_lint_pass ! ( ToStringInFormatArgs => [ TO_STRING_IN_FORMAT_ARGS ] ) ;
71-
72- impl < ' tcx > LateLintPass < ' tcx > for ToStringInFormatArgs {
73- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
74- check_expr ( cx, expr, to_string_in_format_args) ;
75- }
76- }
77-
78- fn check_expr < ' tcx , F > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , check_value : F )
79- where
80- F : Fn ( & LateContext < ' _ > , & FormatArgsExpn < ' _ > , Span , Symbol , usize , & FormatArgsArg < ' _ > ) -> bool ,
81- {
82- if_chain ! {
83- if let Some ( format_args) = FormatArgsExpn :: parse( expr) ;
84- let call_site = expr. span. ctxt( ) . outer_expn_data( ) . call_site;
85- if call_site. from_expansion( ) ;
86- let expn_data = call_site. ctxt( ) . outer_expn_data( ) ;
87- if let ExpnKind :: Macro ( _, name) = expn_data. kind;
88- if !format_args. has_string_formatting( ) ;
89- then {
90- for ( i, arg) in format_args. args( ) . enumerate( ) {
91- if !arg. is_display( ) {
92- continue ;
66+ declare_lint_pass ! ( FormatArgs => [ FORMAT_IN_FORMAT_ARGS , TO_STRING_IN_FORMAT_ARGS ] ) ;
67+
68+ impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
69+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
70+ if_chain ! {
71+ if let Some ( format_args) = FormatArgsExpn :: parse( expr) ;
72+ let expn_data = {
73+ let expn_data = expr. span. ctxt( ) . outer_expn_data( ) ;
74+ if expn_data. call_site. from_expansion( ) {
75+ expn_data. call_site. ctxt( ) . outer_expn_data( )
76+ } else {
77+ expn_data
9378 }
94- if check_value( cx, & format_args, expn_data. call_site, name, i, & arg) {
95- break ;
79+ } ;
80+ if let ExpnKind :: Macro ( _, name) = expn_data. kind;
81+ if let Some ( args) = format_args. args( ) ;
82+ if !args. iter( ) . any( FormatArgsArg :: has_string_formatting) ;
83+ then {
84+ for ( i, arg) in args. iter( ) . enumerate( ) {
85+ if !arg. is_display( ) {
86+ continue ;
87+ }
88+ if is_aliased( & args, i) {
89+ continue ;
90+ }
91+ check_format_in_format_args( cx, expn_data. call_site, name, arg) ;
92+ check_to_string_in_format_args( cx, expn_data. call_site, name, arg) ;
9693 }
9794 }
9895 }
9996 }
10097}
10198
102- fn format_in_format_args (
103- cx : & LateContext < ' _ > ,
104- format_args : & FormatArgsExpn < ' _ > ,
105- call_site : Span ,
106- name : Symbol ,
107- i : usize ,
108- arg : & FormatArgsArg < ' _ > ,
109- ) -> bool {
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) {
116- span_lint_and_sugg (
117- cx,
118- FORMAT_IN_FORMAT_ARGS ,
119- trim_semicolon ( cx, call_site) ,
120- & format ! ( "`format!` in `{}!` args" , name) ,
121- "inline the `format!` call" ,
122- sugg,
123- applicability,
124- ) ;
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- ) ;
134- }
135- // Report only the first instance.
136- return true ;
99+ fn check_format_in_format_args ( cx : & LateContext < ' _ > , call_site : Span , name : Symbol , arg : & FormatArgsArg < ' _ , ' _ > ) {
100+ if FormatExpn :: parse ( arg. value ) . is_some ( ) {
101+ span_lint_and_help (
102+ cx,
103+ FORMAT_IN_FORMAT_ARGS ,
104+ trim_semicolon ( cx, call_site) ,
105+ & format ! ( "`format!` in `{}!` args" , name) ,
106+ None ,
107+ "inline the `format!` call" ,
108+ ) ;
137109 }
138- false
139110}
140111
141- fn to_string_in_format_args (
142- cx : & LateContext < ' _ > ,
143- _format_args : & FormatArgsExpn < ' _ > ,
112+ fn check_to_string_in_format_args < ' tcx > (
113+ cx : & LateContext < ' tcx > ,
144114 _call_site : Span ,
145115 name : Symbol ,
146- _i : usize ,
147- arg : & FormatArgsArg < ' _ > ,
148- ) -> bool {
149- let value = arg. value ( ) ;
116+ arg : & FormatArgsArg < ' _ , ' tcx > ,
117+ ) {
118+ let value = arg. value ;
150119 if_chain ! {
151120 if let ExprKind :: MethodCall ( _, _, args, _) = value. kind;
152121 if let Some ( method_def_id) = cx. typeck_results( ) . type_dependent_def_id( value. hir_id) ;
153122 if match_def_path( cx, method_def_id, & paths:: TO_STRING_METHOD ) ;
154123 if let Some ( receiver) = args. get( 0 ) ;
155124 let ty = cx. typeck_results( ) . expr_ty( receiver) ;
156125 if let Some ( display_trait_id) = get_trait_def_id( cx, & paths:: DISPLAY_TRAIT ) ;
157- if implements_trait( cx, ty, display_trait_id, & [ ] ) ;
158126 if let Some ( snippet) = snippet_opt( cx, value. span) ;
159127 if let Some ( dot) = snippet. rfind( '.' ) ;
160128 then {
161129 let span = value. span. with_lo(
162130 value. span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
163131 ) ;
164- span_lint_and_sugg(
165- cx,
166- TO_STRING_IN_FORMAT_ARGS ,
167- span,
168- & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
169- "remove this" ,
170- String :: new( ) ,
171- Applicability :: MachineApplicable ,
172- ) ;
173- }
174- }
175- false
176- }
177-
178- fn format_in_format_args_sugg (
179- cx : & LateContext < ' _ > ,
180- name : Symbol ,
181- format_args : & FormatArgsExpn < ' _ > ,
182- i : usize ,
183- inner_format_args : & FormatArgsExpn < ' _ > ,
184- ) -> Option < ( String , Applicability ) > {
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- }
194- // If the inner format string is raw, the user is on their own.
195- let ( new_format_string, applicability) = if inner_format_string. starts_with ( 'r' ) {
196- ( left + ".." + & right, Applicability :: HasPlaceholders )
197- } else {
198- (
199- left + & trim_quotes ( & inner_format_string) + & right,
200- Applicability :: MachineApplicable ,
201- )
202- } ;
203- let values = format_args
204- . value_args
205- . iter ( )
206- . map ( |value| snippet_opt ( cx, value. span ) )
207- . collect :: < Option < Vec < _ > > > ( ) ?;
208- let inner_values = inner_format_args
209- . value_args
210- . iter ( )
211- . map ( |value| snippet_opt ( cx, value. span ) )
212- . collect :: < Option < Vec < _ > > > ( ) ?;
213- let new_values = itertools:: join (
214- values
215- . iter ( )
216- . take ( i)
217- . chain ( inner_values. iter ( ) )
218- . chain ( values. iter ( ) . skip ( i + 1 ) ) ,
219- ", " ,
220- ) ;
221- Some ( (
222- format ! ( r#"{}!({}, {})"# , name, new_format_string, new_values) ,
223- applicability,
224- ) )
225- }
226-
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 ;
132+ if implements_trait( cx, ty, display_trait_id, & [ ] ) {
133+ span_lint_and_sugg(
134+ cx,
135+ TO_STRING_IN_FORMAT_ARGS ,
136+ span,
137+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
138+ "remove this" ,
139+ String :: new( ) ,
140+ Applicability :: MachineApplicable ,
141+ ) ;
142+ } else if implements_trait_via_deref( cx, ty, display_trait_id, & [ ] ) {
143+ span_lint_and_sugg(
144+ cx,
145+ TO_STRING_IN_FORMAT_ARGS ,
146+ span,
147+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
148+ "use this" ,
149+ String :: from( ".deref()" ) ,
150+ Applicability :: MachineApplicable ,
151+ ) ;
152+ }
239153 }
240154 }
241- false
242- }
243-
244- fn split_format_string ( format_string : & str , i : usize ) -> ( String , String ) {
245- let mut iter = format_string. chars ( ) ;
246- for j in 0 ..=i {
247- assert ! ( advance( & mut iter) == '}' || j < i) ;
248- }
249-
250- let right = iter. collect :: < String > ( ) ;
251-
252- let size = format_string. len ( ) ;
253- let right_size = right. len ( ) ;
254- let left_size = size - right_size;
255- assert ! ( left_size >= 2 ) ;
256- let left = std:: str:: from_utf8 ( & format_string. as_bytes ( ) [ ..left_size - 2 ] )
257- . unwrap ( )
258- . to_owned ( ) ;
259- ( left, right)
260155}
261156
262- fn advance ( iter : & mut std:: str:: Chars < ' _ > ) -> char {
263- loop {
264- let first_char = iter. next ( ) . unwrap ( ) ;
265- if first_char != '{' {
266- continue ;
267- }
268- let second_char = iter. next ( ) . unwrap ( ) ;
269- if second_char == '{' {
270- continue ;
157+ // Returns true if `args[i]` "refers to" or "is referred to by" another argument.
158+ fn is_aliased ( args : & [ FormatArgsArg < ' _ , ' _ > ] , i : usize ) -> bool {
159+ args. iter ( ) . enumerate ( ) . any ( |( j, arg) | {
160+ if_chain ! {
161+ if let Some ( fmt) = arg. fmt;
162+ // struct `core::fmt::rt::v1::Argument`
163+ if let ExprKind :: Struct ( _, fields, _) = fmt. kind;
164+ if let Some ( position_field) = fields. iter( ) . find( |f| f. ident. name == sym:: position) ;
165+ if let ExprKind :: Lit ( lit) = & position_field. expr. kind;
166+ if let LitKind :: Int ( position, _) = lit. node;
167+ then {
168+ let position = usize :: try_from( position) . unwrap( ) ;
169+ ( j == i && position != i) || ( j != i && position == i)
170+ } else {
171+ false
172+ }
271173 }
272- return second_char;
273- }
274- }
275-
276- fn trim_quotes ( string_literal : & str ) -> String {
277- let len = string_literal. chars ( ) . count ( ) ;
278- assert ! ( len >= 2 ) ;
279- string_literal. chars ( ) . skip ( 1 ) . take ( len - 2 ) . collect ( )
174+ } )
280175}
281176
282177fn trim_semicolon ( cx : & LateContext < ' _ > , span : Span ) -> Span {
@@ -285,3 +180,24 @@ fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
285180 span. with_hi ( span. lo ( ) + BytePos ( u32:: try_from ( snippet. len ( ) ) . unwrap ( ) ) )
286181 } )
287182}
183+
184+ fn implements_trait_via_deref < ' tcx > (
185+ cx : & LateContext < ' tcx > ,
186+ ty : Ty < ' tcx > ,
187+ trait_id : DefId ,
188+ ty_params : & [ GenericArg < ' tcx > ] ,
189+ ) -> bool {
190+ if_chain ! {
191+ if let Some ( deref_trait_id) = cx. tcx. lang_items( ) . deref_trait( ) ;
192+ if implements_trait( cx, ty, deref_trait_id, & [ ] ) ;
193+ if let Some ( deref_target_id) = cx. tcx. lang_items( ) . deref_target( ) ;
194+ then {
195+ let substs = cx. tcx. mk_substs_trait( ty, & [ ] ) ;
196+ let projection_ty = cx. tcx. mk_projection( deref_target_id, substs) ;
197+ let target_ty = cx. tcx. normalize_erasing_regions( cx. param_env, projection_ty) ;
198+ implements_trait( cx, target_ty, trait_id, ty_params)
199+ } else {
200+ false
201+ }
202+ }
203+ }
0 commit comments