1
1
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
2
use clippy_utils:: eager_or_lazy:: switch_to_lazy_eval;
3
3
use clippy_utils:: source:: { snippet, snippet_with_macro_callsite} ;
4
- use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item} ;
5
- use clippy_utils:: { contains_return, is_trait_item , last_path_segment} ;
4
+ use clippy_utils:: ty:: { expr_type_is_certain , implements_trait, is_type_diagnostic_item} ;
5
+ use clippy_utils:: { contains_return, is_default_equivalent , is_default_equivalent_call , last_path_segment} ;
6
6
use if_chain:: if_chain;
7
+ use rustc_ast as ast;
7
8
use rustc_errors:: Applicability ;
8
9
use rustc_hir as hir;
9
10
use rustc_lint:: LateContext ;
11
+ use rustc_middle:: ty:: { self , EarlyBinder } ;
10
12
use rustc_span:: source_map:: Span ;
11
- use rustc_span:: symbol:: { kw , sym, Symbol } ;
13
+ use rustc_span:: symbol:: { self , sym, Symbol } ;
12
14
use std:: borrow:: Cow ;
13
15
14
- use super :: OR_FUN_CALL ;
16
+ use super :: { OR_FUN_CALL , UNWRAP_OR_DEFAULT } ;
15
17
16
18
/// Checks for the `OR_FUN_CALL` lint.
17
19
#[ allow( clippy:: too_many_lines) ]
@@ -25,45 +27,67 @@ pub(super) fn check<'tcx>(
25
27
) {
26
28
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
27
29
/// `or_insert(T::new())` or `or_insert(T::default())`.
30
+ /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
31
+ /// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
28
32
#[ allow( clippy:: too_many_arguments) ]
29
33
fn check_unwrap_or_default (
30
34
cx : & LateContext < ' _ > ,
31
35
name : & str ,
36
+ receiver : & hir:: Expr < ' _ > ,
32
37
fun : & hir:: Expr < ' _ > ,
33
- arg : & hir:: Expr < ' _ > ,
34
- or_has_args : bool ,
38
+ call_expr : Option < & hir:: Expr < ' _ > > ,
35
39
span : Span ,
36
40
method_span : Span ,
37
41
) -> bool {
38
- let is_default_default = || is_trait_item ( cx, fun, sym:: Default ) ;
42
+ if !expr_type_is_certain ( cx, receiver) {
43
+ return false ;
44
+ }
45
+
46
+ let is_new = |fun : & hir:: Expr < ' _ > | {
47
+ if let hir:: ExprKind :: Path ( ref qpath) = fun. kind {
48
+ let path = last_path_segment ( qpath) . ident . name ;
49
+ matches ! ( path, sym:: new)
50
+ } else {
51
+ false
52
+ }
53
+ } ;
39
54
40
- let implements_default = |arg, default_trait_id| {
41
- let arg_ty = cx. typeck_results ( ) . expr_ty ( arg) ;
42
- implements_trait ( cx, arg_ty, default_trait_id, & [ ] )
55
+ let output_type_implements_default = |fun| {
56
+ let fun_ty = cx. typeck_results ( ) . expr_ty ( fun) ;
57
+ if let ty:: FnDef ( def_id, substs) = fun_ty. kind ( ) {
58
+ let output_ty = EarlyBinder ( cx. tcx . fn_sig ( def_id) . output ( ) )
59
+ . subst ( cx. tcx , substs)
60
+ . skip_binder ( ) ;
61
+ cx. tcx
62
+ . get_diagnostic_item ( sym:: Default )
63
+ . map_or ( false , |default_trait_id| {
64
+ implements_trait ( cx, output_ty, default_trait_id, substs)
65
+ } )
66
+ } else {
67
+ false
68
+ }
43
69
} ;
44
70
45
71
if_chain ! {
46
- if !or_has_args;
47
- if let Some ( sugg) = match name {
48
- "unwrap_or" => Some ( "unwrap_or_default" ) ,
49
- "or_insert" => Some ( "or_default" ) ,
72
+ if let Some ( sugg) = match ( name, call_expr. is_some( ) ) {
73
+ ( "unwrap_or" , true ) | ( "unwrap_or_else" , false ) => Some ( "unwrap_or_default" ) ,
74
+ ( "or_insert" , true ) | ( "or_insert_with" , false ) => Some ( "or_default" ) ,
50
75
_ => None ,
51
76
} ;
52
- if let hir:: ExprKind :: Path ( ref qpath) = fun. kind;
53
- if let Some ( default_trait_id) = cx. tcx. get_diagnostic_item( sym:: Default ) ;
54
- let path = last_path_segment( qpath) . ident. name;
55
77
// needs to target Default::default in particular or be *::new and have a Default impl
56
78
// available
57
- if ( matches!( path, kw:: Default ) && is_default_default( ) )
58
- || ( matches!( path, sym:: new) && implements_default( arg, default_trait_id) ) ;
59
-
79
+ if ( is_new( fun) && output_type_implements_default( fun) )
80
+ || match call_expr {
81
+ Some ( call_expr) => is_default_equivalent( cx, call_expr) ,
82
+ None => is_default_equivalent_call( cx, fun) || closure_body_returns_empty_to_string( cx, fun) ,
83
+ } ;
60
84
then {
61
85
span_lint_and_sugg(
62
86
cx,
63
- OR_FUN_CALL ,
87
+ UNWRAP_OR_DEFAULT ,
64
88
method_span. with_hi( span. hi( ) ) ,
65
- & format!( "use of `{name}` followed by a call to `{path}` " ) ,
66
- "try this " ,
89
+ & format!( "use of `{name}` to construct default value " ) ,
90
+ "try" ,
67
91
format!( "{sugg}()" ) ,
68
92
Applicability :: MachineApplicable ,
69
93
) ;
@@ -153,7 +177,7 @@ pub(super) fn check<'tcx>(
153
177
OR_FUN_CALL ,
154
178
span_replace_word,
155
179
& format!( "use of `{name}` followed by a function call" ) ,
156
- "try this " ,
180
+ "try" ,
157
181
format!( "{name}_{suffix}({sugg})" ) ,
158
182
Applicability :: HasPlaceholders ,
159
183
) ;
@@ -182,11 +206,16 @@ pub(super) fn check<'tcx>(
182
206
match inner_arg. kind {
183
207
hir:: ExprKind :: Call ( fun, or_args) => {
184
208
let or_has_args = !or_args. is_empty ( ) ;
185
- if !check_unwrap_or_default ( cx, name, fun, arg, or_has_args, expr. span , method_span) {
209
+ if or_has_args
210
+ || !check_unwrap_or_default ( cx, name, receiver, fun, Some ( inner_arg) , expr. span , method_span)
211
+ {
186
212
let fun_span = if or_has_args { None } else { Some ( fun. span ) } ;
187
213
check_general_case ( cx, name, method_span, receiver, arg, None , expr. span , fun_span) ;
188
214
}
189
215
} ,
216
+ hir:: ExprKind :: Path ( ..) | hir:: ExprKind :: Closure ( ..) => {
217
+ check_unwrap_or_default ( cx, name, receiver, inner_arg, None , expr. span , method_span) ;
218
+ } ,
190
219
hir:: ExprKind :: Index ( ..) | hir:: ExprKind :: MethodCall ( ..) => {
191
220
check_general_case ( cx, name, method_span, receiver, arg, None , expr. span , None ) ;
192
221
} ,
@@ -203,3 +232,22 @@ pub(super) fn check<'tcx>(
203
232
}
204
233
}
205
234
}
235
+
236
+ fn closure_body_returns_empty_to_string ( cx : & LateContext < ' _ > , e : & hir:: Expr < ' _ > ) -> bool {
237
+ if let hir:: ExprKind :: Closure ( & hir:: Closure { body, .. } ) = e. kind {
238
+ let body = cx. tcx . hir ( ) . body ( body) ;
239
+
240
+ if body. params . is_empty ( )
241
+ && let hir:: Expr { kind, .. } = & body. value
242
+ && let hir:: ExprKind :: MethodCall ( hir:: PathSegment { ident, ..} , self_arg, _, _) = kind
243
+ && ident == & symbol:: Ident :: from_str ( "to_string" )
244
+ && let hir:: Expr { kind, .. } = self_arg
245
+ && let hir:: ExprKind :: Lit ( lit) = kind
246
+ && let ast:: LitKind :: Str ( symbol:: kw:: Empty , _) = lit. node
247
+ {
248
+ return true ;
249
+ }
250
+ }
251
+
252
+ false
253
+ }
0 commit comments