1- use clippy_utils:: diagnostics:: { span_lint_and_help , span_lint_and_sugg } ;
1+ use clippy_utils:: diagnostics:: { span_lint_and_sugg , span_lint_and_then } ;
22use clippy_utils:: higher:: { FormatArgsArg , FormatArgsExpn , FormatExpn } ;
33use clippy_utils:: source:: snippet_opt;
44use clippy_utils:: ty:: implements_trait;
5- use clippy_utils:: { get_trait_def_id, match_def_path, paths} ;
5+ use clippy_utils:: { get_trait_def_id, is_diag_trait_item , match_def_path, paths} ;
66use if_chain:: if_chain;
7- use rustc_ast:: ast:: LitKind ;
87use rustc_errors:: Applicability ;
9- use rustc_hir:: def_id:: DefId ;
108use rustc_hir:: { Expr , ExprKind } ;
119use rustc_lint:: { LateContext , LateLintPass } ;
12- use rustc_middle:: ty:: subst :: GenericArg ;
10+ use rustc_middle:: ty:: adjustment :: { Adjust , Adjustment } ;
1311use rustc_middle:: ty:: Ty ;
1412use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
15- use rustc_span:: { sym, BytePos , ExpnKind , Span , Symbol } ;
13+ use rustc_span:: { sym, BytePos , ExpnData , ExpnKind , Span , Symbol } ;
1614
1715declare_clippy_lint ! {
1816 /// ### What it does
@@ -65,112 +63,129 @@ declare_clippy_lint! {
6563
6664declare_lint_pass ! ( FormatArgs => [ FORMAT_IN_FORMAT_ARGS , TO_STRING_IN_FORMAT_ARGS ] ) ;
6765
66+ const FORMAT_MACROS : & [ & [ & str ] ] = & [
67+ & [ "alloc" , "macros" , "format" ] ,
68+ & [ "core" , "macros" , "assert_eq" ] ,
69+ & [ "core" , "macros" , "assert_ne" ] ,
70+ & [ "core" , "macros" , "write" ] ,
71+ & [ "core" , "macros" , "writeln" ] ,
72+ & [ "core" , "macros" , "builtin" , "assert" ] ,
73+ & [ "core" , "macros" , "builtin" , "format_args" ] ,
74+ & [ "std" , "macros" , "eprint" ] ,
75+ & [ "std" , "macros" , "eprintln" ] ,
76+ & [ "std" , "macros" , "panic" ] ,
77+ & [ "std" , "macros" , "print" ] ,
78+ & [ "std" , "macros" , "println" ] ,
79+ ] ;
80+
6881impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
6982 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
7083 if_chain ! {
7184 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
78- }
79- } ;
80- if let ExpnKind :: Macro ( _, name) = expn_data. kind;
85+ let expr_expn_data = expr. span. ctxt( ) . outer_expn_data( ) ;
86+ let outermost_expn_data = outermost_expn_data( expr_expn_data) ;
87+ if let Some ( macro_def_id) = outermost_expn_data. macro_def_id;
88+ if FORMAT_MACROS . iter( ) . any( |path| match_def_path( cx, macro_def_id, path) ) ;
89+ if let ExpnKind :: Macro ( _, name) = outermost_expn_data. kind;
8190 if let Some ( args) = format_args. args( ) ;
82- if !args. iter( ) . any( FormatArgsArg :: has_string_formatting) ;
8391 then {
8492 for ( i, arg) in args. iter( ) . enumerate( ) {
8593 if !arg. is_display( ) {
8694 continue ;
8795 }
96+ if arg. has_string_formatting( ) {
97+ continue ;
98+ }
8899 if is_aliased( & args, i) {
89100 continue ;
90101 }
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) ;
102+ check_format_in_format_args( cx, outermost_expn_data . call_site, name, arg) ;
103+ check_to_string_in_format_args( cx, name, arg) ;
93104 }
94105 }
95106 }
96107 }
97108}
98109
99- fn check_format_in_format_args ( cx : & LateContext < ' _ > , call_site : Span , name : Symbol , arg : & FormatArgsArg < ' _ , ' _ > ) {
110+ fn outermost_expn_data ( expn_data : ExpnData ) -> ExpnData {
111+ if expn_data. call_site . from_expansion ( ) {
112+ outermost_expn_data ( expn_data. call_site . ctxt ( ) . outer_expn_data ( ) )
113+ } else {
114+ expn_data
115+ }
116+ }
117+
118+ fn check_format_in_format_args ( cx : & LateContext < ' _ > , call_site : Span , name : Symbol , arg : & FormatArgsArg < ' _ > ) {
100119 if FormatExpn :: parse ( arg. value ) . is_some ( ) {
101- span_lint_and_help (
120+ span_lint_and_then (
102121 cx,
103122 FORMAT_IN_FORMAT_ARGS ,
104123 trim_semicolon ( cx, call_site) ,
105124 & format ! ( "`format!` in `{}!` args" , name) ,
106- None ,
107- "inline the `format!` call" ,
125+ |diag| {
126+ diag. help ( & format ! (
127+ "combine the `format!(..)` arguments with the outer `{}!(..)` call" ,
128+ name
129+ ) ) ;
130+ diag. help ( "or consider changing `format!` to `format_args!`" ) ;
131+ } ,
108132 ) ;
109133 }
110134}
111135
112- fn check_to_string_in_format_args < ' tcx > (
113- cx : & LateContext < ' tcx > ,
114- name : Symbol ,
115- arg : & FormatArgsArg < ' _ , ' tcx > ,
116- ) {
136+ fn check_to_string_in_format_args < ' tcx > ( cx : & LateContext < ' tcx > , name : Symbol , arg : & FormatArgsArg < ' tcx > ) {
117137 let value = arg. value ;
118138 if_chain ! {
119- if let ExprKind :: MethodCall ( _, _, [ receiver] , span ) = value. kind;
139+ if let ExprKind :: MethodCall ( _, _, [ receiver] , _ ) = value. kind;
120140 if let Some ( method_def_id) = cx. typeck_results( ) . type_dependent_def_id( value. hir_id) ;
121141 if is_diag_trait_item( cx, method_def_id, sym:: ToString ) ;
122- if let Some ( receiver) = args. get( 0 ) ;
123- let ty = cx. typeck_results( ) . expr_ty( receiver) ;
142+ let receiver_ty = cx. typeck_results( ) . expr_ty( receiver) ;
124143 if let Some ( display_trait_id) = get_trait_def_id( cx, & paths:: DISPLAY_TRAIT ) ;
125- if let Some ( snippet) = snippet_opt( cx, value. span) ;
126- if let Some ( dot) = snippet. rfind( '.' ) ;
144+ if let Some ( value_snippet) = snippet_opt( cx, value. span) ;
145+ if let Some ( dot) = value_snippet. rfind( '.' ) ;
146+ if let Some ( receiver_snippet) = snippet_opt( cx, receiver. span) ;
127147 then {
128- let span = value. span. with_lo(
129- value. span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
148+ let ( n_derefs, target) = count_derefs(
149+ 0 ,
150+ receiver_ty,
151+ & mut cx. typeck_results( ) . expr_adjustments( receiver) . iter( ) ,
130152 ) ;
131- if implements_trait( cx, ty, display_trait_id, & [ ] ) {
132- span_lint_and_sugg(
133- cx,
134- TO_STRING_IN_FORMAT_ARGS ,
135- span,
136- & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
137- "remove this" ,
138- String :: new( ) ,
139- Applicability :: MachineApplicable ,
140- ) ;
141- } else if implements_trait_via_deref( cx, ty, display_trait_id, & [ ] ) {
142- span_lint_and_sugg(
143- cx,
144- TO_STRING_IN_FORMAT_ARGS ,
145- span,
146- & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
147- "use this" ,
148- String :: from( ".deref()" ) ,
149- Applicability :: MachineApplicable ,
150- ) ;
153+ if implements_trait( cx, target, display_trait_id, & [ ] ) {
154+ if n_derefs == 0 {
155+ let span = value. span. with_lo(
156+ value. span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
157+ ) ;
158+ span_lint_and_sugg(
159+ cx,
160+ TO_STRING_IN_FORMAT_ARGS ,
161+ span,
162+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
163+ "remove this" ,
164+ String :: new( ) ,
165+ Applicability :: MachineApplicable ,
166+ ) ;
167+ } else {
168+ span_lint_and_sugg(
169+ cx,
170+ TO_STRING_IN_FORMAT_ARGS ,
171+ value. span,
172+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
173+ "use this" ,
174+ format!( "{:*>width$}{}" , "" , receiver_snippet, width = n_derefs) ,
175+ Applicability :: MachineApplicable ,
176+ ) ;
177+ }
151178 }
152179 }
153180 }
154181}
155182
156183// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
157- fn is_aliased ( args : & [ FormatArgsArg < ' _ , ' _ > ] , i : usize ) -> bool {
158- args. iter ( ) . enumerate ( ) . any ( |( j, arg) | {
159- if_chain ! {
160- if let Some ( fmt) = arg. fmt;
161- // struct `core::fmt::rt::v1::Argument`
162- if let ExprKind :: Struct ( _, fields, _) = fmt. kind;
163- if let Some ( position_field) = fields. iter( ) . find( |f| f. ident. name == sym:: position) ;
164- if let ExprKind :: Lit ( lit) = & position_field. expr. kind;
165- if let LitKind :: Int ( position, _) = lit. node;
166- then {
167- let position = usize :: try_from( position) . unwrap( ) ;
168- ( j == i && position != i) || ( j != i && position == i)
169- } else {
170- false
171- }
172- }
173- } )
184+ fn is_aliased ( args : & [ FormatArgsArg < ' _ > ] , i : usize ) -> bool {
185+ let value = args[ i] . value ;
186+ args. iter ( )
187+ . enumerate ( )
188+ . any ( |( j, arg) | i != j && std:: ptr:: eq ( value, arg. value ) )
174189}
175190
176191fn trim_semicolon ( cx : & LateContext < ' _ > , span : Span ) -> Span {
@@ -180,23 +195,17 @@ fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
180195 } )
181196}
182197
183- fn implements_trait_via_deref < ' tcx > (
184- cx : & LateContext < ' tcx > ,
185- ty : Ty < ' tcx > ,
186- trait_id : DefId ,
187- ty_params : & [ GenericArg < ' tcx > ] ,
188- ) -> bool {
189- if_chain ! {
190- if let Some ( deref_trait_id) = cx. tcx. lang_items( ) . deref_trait( ) ;
191- if implements_trait( cx, ty, deref_trait_id, & [ ] ) ;
192- if let Some ( deref_target_id) = cx. tcx. lang_items( ) . deref_target( ) ;
193- then {
194- let substs = cx. tcx. mk_substs_trait( ty, & [ ] ) ;
195- let projection_ty = cx. tcx. mk_projection( deref_target_id, substs) ;
196- let target_ty = cx. tcx. normalize_erasing_regions( cx. param_env, projection_ty) ;
197- implements_trait( cx, target_ty, trait_id, ty_params)
198- } else {
199- false
200- }
198+ fn count_derefs < ' tcx , I > ( n : usize , ty : Ty < ' tcx > , iter : & mut I ) -> ( usize , Ty < ' tcx > )
199+ where
200+ I : Iterator < Item = & ' tcx Adjustment < ' tcx > > ,
201+ {
202+ if let Some ( Adjustment {
203+ kind : Adjust :: Deref ( Some ( _) ) ,
204+ target,
205+ } ) = iter. next ( )
206+ {
207+ count_derefs ( n + 1 , target, iter)
208+ } else {
209+ ( n, ty)
201210 }
202211}
0 commit comments