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;
1512use rustc_span:: symbol:: kw;
13+ use rustc_span:: { sym, Span } ;
1614
1715declare_clippy_lint ! {
1816 /// **What it does:** Checks for the use of `format!("string literal with no
@@ -45,131 +43,78 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
4543
4644impl < ' tcx > LateLintPass < ' tcx > for UselessFormat {
4745 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
48- let span = match is_expn_of ( expr. span , "format" ) {
49- 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 ,
5048 _ => return ,
5149 } ;
5250
53- // Operate on the only argument of `alloc::fmt::format`.
54- if let Some ( sugg) = on_new_v1 ( cx, expr) {
55- span_useless_format ( cx, span, "consider using `.to_string()`" , sugg) ;
56- } else if let Some ( sugg) = on_new_v1_fmt ( cx, expr) {
57- span_useless_format ( cx, span, "consider using `.to_string()`" , sugg) ;
58- }
59- }
60- }
61-
62- fn span_useless_format < T : LintContext > ( cx : & T , span : Span , help : & str , mut sugg : String ) {
63- let to_replace = span. source_callsite ( ) ;
64-
65- // The callsite span contains the statement semicolon for some reason.
66- let snippet = snippet ( cx, to_replace, ".." ) ;
67- if snippet. ends_with ( ';' ) {
68- sugg. push ( ';' ) ;
69- }
70-
71- span_lint_and_then ( cx, USELESS_FORMAT , span, "useless use of `format!`" , |diag| {
72- diag. span_suggestion (
73- to_replace,
74- help,
75- sugg,
76- Applicability :: MachineApplicable , // snippet
77- ) ;
78- } ) ;
79- }
80-
81- fn on_argumentv1_new < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , arms : & ' tcx [ Arm < ' _ > ] ) -> Option < String > {
82- if_chain ! {
83- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, format_args) = expr. kind;
84- if let ExprKind :: Array ( elems) = arms[ 0 ] . body. kind;
85- if elems. len( ) == 1 ;
86- if let Some ( args) = match_function_call( cx, & elems[ 0 ] , & paths:: FMT_ARGUMENTV1_NEW ) ;
87- // matches `core::fmt::Display::fmt`
88- if args. len( ) == 2 ;
89- if let ExprKind :: Path ( ref qpath) = args[ 1 ] . kind;
90- if let Some ( did) = cx. qpath_res( qpath, args[ 1 ] . hir_id) . opt_def_id( ) ;
91- if match_def_path( cx, did, & paths:: DISPLAY_FMT_METHOD ) ;
92- // check `(arg0,)` in match block
93- if let PatKind :: Tuple ( pats, None ) = arms[ 0 ] . pat. kind;
94- if pats. len( ) == 1 ;
95- then {
96- let ty = cx. typeck_results( ) . pat_ty( pats[ 0 ] ) . peel_refs( ) ;
97- if * ty. kind( ) != rustc_middle:: ty:: Str && !is_type_diagnostic_item( cx, ty, sym:: string_type) {
98- return None ;
99- }
100- if let ExprKind :: Lit ( ref lit) = format_args. kind {
101- if let LitKind :: Str ( ref s, _) = lit. node {
102- 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) ;
10362 }
104- } else {
105- let sugg = Sugg :: hir( cx, format_args, "<arg>" ) ;
106- if let ExprKind :: MethodCall ( path, _, _, _) = format_args. kind {
107- if path. ident. name == sym!( to_string) {
108- return Some ( format!( "{}" , sugg) ) ;
109- }
110- } else if let ExprKind :: Binary ( ..) = format_args. kind {
111- 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) ;
11287 }
113- return Some ( format!( "{}.to_string()" , sugg. maybe_par( ) ) ) ;
11488 }
115- }
89+ } ;
11690 }
117- None
11891}
11992
120- fn on_new_v1 < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < String > {
121- if_chain ! {
122- if let Some ( args) = match_function_call( cx, expr, & paths:: FMT_ARGUMENTS_NEW_V1 ) ;
123- if args. len( ) == 2 ;
124- // Argument 1 in `new_v1()`
125- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arr) = args[ 0 ] . kind;
126- if let ExprKind :: Array ( pieces) = arr. kind;
127- if pieces. len( ) == 1 ;
128- if let ExprKind :: Lit ( ref lit) = pieces[ 0 ] . kind;
129- if let LitKind :: Str ( ref s, _) = lit. node;
130- // Argument 2 in `new_v1()`
131- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arg1) = args[ 1 ] . kind;
132- if let ExprKind :: Match ( matchee, arms, MatchSource :: Normal ) = arg1. kind;
133- if arms. len( ) == 1 ;
134- if let ExprKind :: Tup ( tup) = matchee. kind;
135- then {
136- // `format!("foo")` expansion contains `match () { () => [], }`
137- if tup. is_empty( ) {
138- if let Some ( s_src) = snippet_opt( cx, lit. span) {
139- // Simulate macro expansion, converting {{ and }} to { and }.
140- let s_expand = s_src. replace( "{{" , "{" ) . replace( "}}" , "}" ) ;
141- return Some ( format!( "{}.to_string()" , s_expand) ) ;
142- }
143- } else if s. as_str( ) . is_empty( ) {
144- return on_argumentv1_new( cx, & tup[ 0 ] , arms) ;
145- }
146- }
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 ( ';' ) ;
14797 }
148- 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+ ) ;
149108}
150109
151- fn on_new_v1_fmt < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < String > {
110+ fn is_display_arg ( expr : & Expr < ' _ > ) -> bool {
152111 if_chain ! {
153- if let Some ( args) = match_function_call( cx, expr, & paths:: FMT_ARGUMENTS_NEW_V1_FORMATTED ) ;
154- if args. len( ) == 3 ;
155- if check_unformatted( & args[ 2 ] ) ;
156- // Argument 1 in `new_v1_formatted()`
157- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arr) = args[ 0 ] . kind;
158- if let ExprKind :: Array ( pieces) = arr. kind;
159- if pieces. len( ) == 1 ;
160- if let ExprKind :: Lit ( ref lit) = pieces[ 0 ] . kind;
161- if let LitKind :: Str ( symbol, _) = lit. node;
162- if symbol == kw:: Empty ;
163- // Argument 2 in `new_v1_formatted()`
164- if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, arg1) = args[ 1 ] . kind;
165- if let ExprKind :: Match ( matchee, arms, MatchSource :: Normal ) = arg1. kind;
166- if arms. len( ) == 1 ;
167- if let ExprKind :: Tup ( tup) = matchee. kind;
168- then {
169- return on_argumentv1_new( cx, & tup[ 0 ] , arms) ;
170- }
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 }
171117 }
172- None
173118}
174119
175120/// Checks if the expression matches
@@ -186,10 +131,9 @@ fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<S
186131fn check_unformatted ( expr : & Expr < ' _ > ) -> bool {
187132 if_chain ! {
188133 if let ExprKind :: AddrOf ( BorrowKind :: Ref , _, expr) = expr. kind;
189- if let ExprKind :: Array ( exprs) = expr. kind;
190- if exprs. len( ) == 1 ;
134+ if let ExprKind :: Array ( [ expr] ) = expr. kind;
191135 // struct `core::fmt::rt::v1::Argument`
192- if let ExprKind :: Struct ( _, fields, _) = exprs [ 0 ] . kind;
136+ if let ExprKind :: Struct ( _, fields, _) = expr . kind;
193137 if let Some ( format_field) = fields. iter( ) . find( |f| f. ident. name == sym:: format) ;
194138 // struct `core::fmt::rt::v1::FormatSpec`
195139 if let ExprKind :: Struct ( _, fields, _) = format_field. expr. kind;
0 commit comments