1
1
use std:: ops:: ControlFlow ;
2
2
3
- use clippy_utils:: diagnostics:: span_lint_and_then ;
3
+ use clippy_utils:: diagnostics:: span_lint ;
4
4
use clippy_utils:: ty:: is_type_diagnostic_item;
5
5
use clippy_utils:: visitors:: for_each_expr;
6
6
use clippy_utils:: { higher, peel_hir_expr_while, SpanlessEq } ;
7
7
use rustc_hir:: { Expr , ExprKind , UnOp } ;
8
8
use rustc_lint:: { LateContext , LateLintPass } ;
9
9
use rustc_session:: declare_lint_pass;
10
+ use rustc_span:: symbol:: Symbol ;
10
11
use rustc_span:: { sym, Span } ;
11
12
12
13
declare_clippy_lint ! {
@@ -17,6 +18,11 @@ declare_clippy_lint! {
17
18
/// ### Why is this bad?
18
19
/// Using just `insert` and checking the returned `bool` is more efficient.
19
20
///
21
+ /// ### Known problems
22
+ /// In case the value that wants to be inserted is borrowed and also expensive or impossible
23
+ /// to clone. In such scenario, the developer might want to check with `contain` before inserting,
24
+ /// to avoid the clone. In this case, it will report a false positive.
25
+ ///
20
26
/// ### Example
21
27
/// ```rust
22
28
/// use std::collections::HashSet;
@@ -37,12 +43,12 @@ declare_clippy_lint! {
37
43
/// }
38
44
/// ```
39
45
#[ clippy:: version = "1.80.0" ]
40
- pub HASHSET_INSERT_AFTER_CONTAINS ,
46
+ pub SET_CONTAINS_OR_INSERT ,
41
47
nursery,
42
- "unnecessary call to `HashSet::contains` followed by `HashSet::insert`"
48
+ "call to `HashSet::contains` followed by `HashSet::insert`"
43
49
}
44
50
45
- declare_lint_pass ! ( HashsetInsertAfterContains => [ HASHSET_INSERT_AFTER_CONTAINS ] ) ;
51
+ declare_lint_pass ! ( HashsetInsertAfterContains => [ SET_CONTAINS_OR_INSERT ] ) ;
46
52
47
53
impl < ' tcx > LateLintPass < ' tcx > for HashsetInsertAfterContains {
48
54
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
@@ -52,56 +58,35 @@ impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains {
52
58
then : then_expr,
53
59
..
54
60
} ) = higher:: If :: hir ( expr)
55
- && let Some ( contains_expr) = try_parse_contains ( cx, cond_expr)
56
- && find_insert_calls ( cx, & contains_expr, then_expr)
61
+ && let Some ( contains_expr) = try_parse_op_call ( cx , cond_expr , sym ! ( contains ) ) // try_parse_contains(cx, cond_expr)
62
+ && let Some ( insert_expr ) = find_insert_calls ( cx, & contains_expr, then_expr)
57
63
{
58
- span_lint_and_then (
64
+ span_lint (
59
65
cx,
60
- HASHSET_INSERT_AFTER_CONTAINS ,
61
- expr . span ,
66
+ SET_CONTAINS_OR_INSERT ,
67
+ vec ! [ contains_expr . span, insert_expr . span ] ,
62
68
"usage of `HashSet::insert` after `HashSet::contains`" ,
63
- |diag| {
64
- diag. note ( "`HashSet::insert` returns whether it was inserted" )
65
- . span_help ( contains_expr. span , "remove the `HashSet::contains` call" ) ;
66
- } ,
67
69
) ;
68
70
}
69
71
}
70
72
}
71
73
72
- struct ContainsExpr < ' tcx > {
74
+ struct OpExpr < ' tcx > {
73
75
receiver : & ' tcx Expr < ' tcx > ,
74
76
value : & ' tcx Expr < ' tcx > ,
75
77
span : Span ,
76
78
}
77
- fn try_parse_contains < ' tcx > ( cx : & LateContext < ' _ > , expr : & ' tcx Expr < ' _ > ) -> Option < ContainsExpr < ' tcx > > {
79
+
80
+ fn try_parse_op_call < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , symbol : Symbol ) -> Option < OpExpr < ' tcx > > {
78
81
let expr = peel_hir_expr_while ( expr, |e| {
79
82
if let ExprKind :: Unary ( UnOp :: Not , e) = e. kind {
80
83
Some ( e)
81
84
} else {
82
85
None
83
86
}
84
87
} ) ;
85
- if let ExprKind :: MethodCall ( path, receiver, [ value] , span) = expr. kind {
86
- let value = value. peel_borrows ( ) ;
87
- let receiver = receiver. peel_borrows ( ) ;
88
- let receiver_ty = cx. typeck_results ( ) . expr_ty ( receiver) . peel_refs ( ) ;
89
- if value. span . eq_ctxt ( expr. span )
90
- && is_type_diagnostic_item ( cx, receiver_ty, sym:: HashSet )
91
- && path. ident . name == sym ! ( contains)
92
- {
93
- return Some ( ContainsExpr { receiver, value, span } ) ;
94
- }
95
- }
96
- None
97
- }
98
88
99
- struct InsertExpr < ' tcx > {
100
- receiver : & ' tcx Expr < ' tcx > ,
101
- value : & ' tcx Expr < ' tcx > ,
102
- }
103
- fn try_parse_insert < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < InsertExpr < ' tcx > > {
104
- if let ExprKind :: MethodCall ( path, receiver, [ value] , _) = expr. kind {
89
+ if let ExprKind :: MethodCall ( path, receiver, [ value] , span) = expr. kind {
105
90
let value = value. peel_borrows ( ) ;
106
91
let value = peel_hir_expr_while ( value, |e| {
107
92
if let ExprKind :: Unary ( UnOp :: Deref , e) = e. kind {
@@ -110,28 +95,31 @@ fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio
110
95
None
111
96
}
112
97
} ) ;
113
-
98
+ let receiver = receiver . peel_borrows ( ) ;
114
99
let receiver_ty = cx. typeck_results ( ) . expr_ty ( receiver) . peel_refs ( ) ;
115
- if is_type_diagnostic_item ( cx, receiver_ty, sym:: HashSet ) && path. ident . name == sym ! ( insert) {
116
- Some ( InsertExpr { receiver, value } )
117
- } else {
118
- None
100
+ if value. span . eq_ctxt ( expr. span )
101
+ && is_type_diagnostic_item ( cx, receiver_ty, sym:: HashSet )
102
+ && path. ident . name == symbol
103
+ {
104
+ return Some ( OpExpr { receiver, value, span } ) ;
119
105
}
120
- } else {
121
- None
122
106
}
107
+ None
123
108
}
124
109
125
- fn find_insert_calls < ' tcx > ( cx : & LateContext < ' tcx > , contains_expr : & ContainsExpr < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> bool {
110
+ fn find_insert_calls < ' tcx > (
111
+ cx : & LateContext < ' tcx > ,
112
+ contains_expr : & OpExpr < ' tcx > ,
113
+ expr : & ' tcx Expr < ' _ > ,
114
+ ) -> Option < OpExpr < ' tcx > > {
126
115
for_each_expr ( expr, |e| {
127
- if let Some ( insert_expr) = try_parse_insert ( cx, e)
116
+ if let Some ( insert_expr) = try_parse_op_call ( cx, e, sym ! ( insert ) )
128
117
&& SpanlessEq :: new ( cx) . eq_expr ( contains_expr. receiver , insert_expr. receiver )
129
118
&& SpanlessEq :: new ( cx) . eq_expr ( contains_expr. value , insert_expr. value )
130
119
{
131
- ControlFlow :: Break ( ( ) )
120
+ ControlFlow :: Break ( insert_expr )
132
121
} else {
133
122
ControlFlow :: Continue ( ( ) )
134
123
}
135
124
} )
136
- . is_some ( )
137
125
}
0 commit comments