1- use clippy_utils:: diagnostics:: span_lint_and_then;
2- use clippy_utils:: paths;
3- use clippy_utils:: source:: { snippet, snippet_opt} ;
1+ use clippy_utils:: diagnostics:: span_lint_and_sugg;
2+ use clippy_utils:: higher:: FormatExpn ;
3+ use clippy_utils:: last_path_segment;
4+ use clippy_utils:: source:: { snippet_opt, snippet_with_applicability} ;
45use clippy_utils:: sugg:: Sugg ;
5- use clippy_utils:: ty:: is_type_diagnostic_item;
6- use clippy_utils:: { is_expn_of, last_path_segment, match_def_path, match_function_call} ;
76use if_chain:: if_chain;
8- use rustc_ast:: ast:: LitKind ;
97use rustc_errors:: Applicability ;
10- use rustc_hir:: { Arm , BorrowKind , Expr , ExprKind , MatchSource , PatKind } ;
11- use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
8+ use rustc_hir:: { BorrowKind , Expr , ExprKind , QPath } ;
9+ use rustc_lint:: { LateContext , LateLintPass } ;
10+ use rustc_middle:: ty;
1211use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
13- use rustc_span:: source_map :: Span ;
14- use rustc_span:: sym;
12+ use rustc_span:: symbol :: kw ;
13+ use rustc_span:: { sym, Span } ;
1514
1615declare_clippy_lint ! {
1716 /// **What it does:** Checks for the use of `format!("string literal with no
@@ -44,130 +43,78 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
4443
4544impl < ' tcx > LateLintPass < ' tcx > for UselessFormat {
4645 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
47- let span = match is_expn_of ( expr. span , "format" ) {
48- Some ( s ) if !s . from_expansion ( ) => s ,
46+ let FormatExpn { call_site , format_args } = match FormatExpn :: parse ( expr) {
47+ Some ( e ) if !e . call_site . from_expansion ( ) => e ,
4948 _ => return ,
5049 } ;
5150
52- // Operate on the only argument of `alloc::fmt::format`.
53- if let Some ( sugg) = on_new_v1 ( cx, expr) {
54- span_useless_format ( cx, span, "consider using `.to_string()`" , sugg) ;
55- } else if let Some ( sugg) = on_new_v1_fmt ( cx, expr) {
56- span_useless_format ( cx, span, "consider using `.to_string()`" , sugg) ;
57- }
58- }
59- }
60-
61- fn span_useless_format < T : LintContext > ( cx : & T , span : Span , help : & str , mut sugg : String ) {
62- let to_replace = span. source_callsite ( ) ;
63-
64- // The callsite span contains the statement semicolon for some reason.
65- let snippet = snippet ( cx, to_replace, ".." ) ;
66- if snippet. ends_with ( ';' ) {
67- sugg. push ( ';' ) ;
68- }
69-
70- span_lint_and_then ( cx, USELESS_FORMAT , span, "useless use of `format!`" , |diag| {
71- diag. span_suggestion (
72- to_replace,
73- help,
74- sugg,
75- Applicability :: MachineApplicable , // snippet
76- ) ;
77- } ) ;
78- }
79-
80- fn on_argumentv1_new < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , arms : & ' tcx [ Arm < ' _ > ] ) -> Option < String > {
81- if_chain ! {
82- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, format_args) = expr. kind;
83- if let ExprKind :: Array ( elems) = arms[ 0 ] . body. kind;
84- if elems. len( ) == 1 ;
85- if let Some ( args) = match_function_call( cx, & elems[ 0 ] , & paths:: FMT_ARGUMENTV1_NEW ) ;
86- // matches `core::fmt::Display::fmt`
87- if args. len( ) == 2 ;
88- if let ExprKind :: Path ( ref qpath) = args[ 1 ] . kind;
89- if let Some ( did) = cx. qpath_res( qpath, args[ 1 ] . hir_id) . opt_def_id( ) ;
90- if match_def_path( cx, did, & paths:: DISPLAY_FMT_METHOD ) ;
91- // check `(arg0,)` in match block
92- if let PatKind :: Tuple ( pats, None ) = arms[ 0 ] . pat. kind;
93- if pats. len( ) == 1 ;
94- then {
95- let ty = cx. typeck_results( ) . pat_ty( pats[ 0 ] ) . peel_refs( ) ;
96- if * ty. kind( ) != rustc_middle:: ty:: Str && !is_type_diagnostic_item( cx, ty, sym:: string_type) {
97- return None ;
98- }
99- if let ExprKind :: Lit ( ref lit) = format_args. kind {
100- if let LitKind :: Str ( ref s, _) = lit. node {
101- return Some ( format!( "{:?}.to_string()" , s. as_str( ) ) ) ;
51+ let mut applicability = Applicability :: MachineApplicable ;
52+ if format_args. value_args . is_empty ( ) {
53+ if_chain ! {
54+ if let [ e] = & * format_args. format_string_parts;
55+ if let ExprKind :: Lit ( lit) = & e. kind;
56+ if let Some ( s_src) = snippet_opt( cx, lit. span) ;
57+ then {
58+ // Simulate macro expansion, converting {{ and }} to { and }.
59+ let s_expand = s_src. replace( "{{" , "{" ) . replace( "}}" , "}" ) ;
60+ let sugg = format!( "{}.to_string()" , s_expand) ;
61+ span_useless_format( cx, call_site, sugg, applicability) ;
10262 }
103- } else {
104- let sugg = Sugg :: hir( cx, format_args, "<arg>" ) ;
105- if let ExprKind :: MethodCall ( path, _, _, _) = format_args. kind {
106- if path. ident. name == sym!( to_string) {
107- return Some ( format!( "{}" , sugg) ) ;
108- }
109- } else if let ExprKind :: Binary ( ..) = format_args. kind {
110- return Some ( format!( "{}" , sugg) ) ;
63+ }
64+ } else if let [ value] = * format_args. value_args {
65+ if_chain ! {
66+ if format_args. format_string_symbols == [ kw:: Empty ] ;
67+ if match cx. typeck_results( ) . expr_ty( value) . peel_refs( ) . kind( ) {
68+ ty:: Adt ( adt, _) => cx. tcx. is_diagnostic_item( sym:: string_type, adt. did) ,
69+ ty:: Str => true ,
70+ _ => false ,
71+ } ;
72+ if format_args. args. iter( ) . all( |e| is_display_arg( e) ) ;
73+ if format_args. fmt_expr. map_or( true , |e| check_unformatted( e) ) ;
74+ then {
75+ let is_new_string = match value. kind {
76+ ExprKind :: Binary ( ..) => true ,
77+ ExprKind :: MethodCall ( path, ..) => path. ident. name. as_str( ) == "to_string" ,
78+ _ => false ,
79+ } ;
80+ let sugg = if is_new_string {
81+ snippet_with_applicability( cx, value. span, ".." , & mut applicability) . into_owned( )
82+ } else {
83+ let sugg = Sugg :: hir_with_applicability( cx, value, "<arg>" , & mut applicability) ;
84+ format!( "{}.to_string()" , sugg. maybe_par( ) )
85+ } ;
86+ span_useless_format( cx, call_site, sugg, applicability) ;
11187 }
112- return Some ( format!( "{}.to_string()" , sugg. maybe_par( ) ) ) ;
11388 }
114- }
89+ } ;
11590 }
116- None
11791}
11892
119- fn on_new_v1 < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < String > {
120- if_chain ! {
121- if let Some ( args) = match_function_call( cx, expr, & paths:: FMT_ARGUMENTS_NEW_V1 ) ;
122- if args. len( ) == 2 ;
123- // Argument 1 in `new_v1()`
124- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arr) = args[ 0 ] . kind;
125- if let ExprKind :: Array ( pieces) = arr. kind;
126- if pieces. len( ) == 1 ;
127- if let ExprKind :: Lit ( ref lit) = pieces[ 0 ] . kind;
128- if let LitKind :: Str ( ref s, _) = lit. node;
129- // Argument 2 in `new_v1()`
130- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arg1) = args[ 1 ] . kind;
131- if let ExprKind :: Match ( matchee, arms, MatchSource :: Normal ) = arg1. kind;
132- if arms. len( ) == 1 ;
133- if let ExprKind :: Tup ( tup) = matchee. kind;
134- then {
135- // `format!("foo")` expansion contains `match () { () => [], }`
136- if tup. is_empty( ) {
137- if let Some ( s_src) = snippet_opt( cx, lit. span) {
138- // Simulate macro expansion, converting {{ and }} to { and }.
139- let s_expand = s_src. replace( "{{" , "{" ) . replace( "}}" , "}" ) ;
140- return Some ( format!( "{}.to_string()" , s_expand) ) ;
141- }
142- } else if s. as_str( ) . is_empty( ) {
143- return on_argumentv1_new( cx, & tup[ 0 ] , arms) ;
144- }
145- }
93+ fn span_useless_format ( cx : & LateContext < ' _ > , span : Span , mut sugg : String , mut applicability : Applicability ) {
94+ // The callsite span contains the statement semicolon for some reason.
95+ if snippet_with_applicability ( cx, span, ".." , & mut applicability) . ends_with ( ';' ) {
96+ sugg. push ( ';' ) ;
14697 }
147- None
98+
99+ span_lint_and_sugg (
100+ cx,
101+ USELESS_FORMAT ,
102+ span,
103+ "useless use of `format!`" ,
104+ "consider using `.to_string()`" ,
105+ sugg,
106+ applicability,
107+ ) ;
148108}
149109
150- fn on_new_v1_fmt < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < String > {
110+ fn is_display_arg ( expr : & Expr < ' _ > ) -> bool {
151111 if_chain ! {
152- if let Some ( args) = match_function_call( cx, expr, & paths:: FMT_ARGUMENTS_NEW_V1_FORMATTED ) ;
153- if args. len( ) == 3 ;
154- if check_unformatted( & args[ 2 ] ) ;
155- // Argument 1 in `new_v1_formatted()`
156- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arr) = args[ 0 ] . kind;
157- if let ExprKind :: Array ( pieces) = arr. kind;
158- if pieces. len( ) == 1 ;
159- if let ExprKind :: Lit ( ref lit) = pieces[ 0 ] . kind;
160- if let LitKind :: Str ( ..) = lit. node;
161- // Argument 2 in `new_v1_formatted()`
162- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arg1) = args[ 1 ] . kind;
163- if let ExprKind :: Match ( matchee, arms, MatchSource :: Normal ) = arg1. kind;
164- if arms. len( ) == 1 ;
165- if let ExprKind :: Tup ( tup) = matchee. kind;
166- then {
167- return on_argumentv1_new( cx, & tup[ 0 ] , arms) ;
168- }
112+ if let ExprKind :: Call ( _, [ _, fmt] ) = expr. kind;
113+ if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = fmt. kind;
114+ if let [ .., t, _] = path. segments;
115+ if t. ident. name. as_str( ) == "Display" ;
116+ then { true } else { false }
169117 }
170- None
171118}
172119
173120/// Checks if the expression matches
@@ -184,10 +131,9 @@ fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<S
184131fn check_unformatted ( expr : & Expr < ' _ > ) -> bool {
185132 if_chain ! {
186133 if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, expr) = expr. kind;
187- if let ExprKind :: Array ( exprs) = expr. kind;
188- if exprs. len( ) == 1 ;
134+ if let ExprKind :: Array ( [ expr] ) = expr. kind;
189135 // struct `core::fmt::rt::v1::Argument`
190- if let ExprKind :: Struct ( _, fields, _) = exprs [ 0 ] . kind;
136+ if let ExprKind :: Struct ( _, fields, _) = expr . kind;
191137 if let Some ( format_field) = fields. iter( ) . find( |f| f. ident. name == sym:: format) ;
192138 // struct `core::fmt::rt::v1::FormatSpec`
193139 if let ExprKind :: Struct ( _, fields, _) = format_field. expr. kind;
0 commit comments