11use std:: ops:: ControlFlow ;
22
3- use clippy_utils:: diagnostics:: span_lint_and_then ;
3+ use clippy_utils:: diagnostics:: span_lint ;
44use clippy_utils:: ty:: is_type_diagnostic_item;
55use clippy_utils:: visitors:: for_each_expr;
66use clippy_utils:: { higher, peel_hir_expr_while, SpanlessEq } ;
77use rustc_hir:: { Expr , ExprKind , UnOp } ;
88use rustc_lint:: { LateContext , LateLintPass } ;
99use rustc_session:: declare_lint_pass;
10+ use rustc_span:: symbol:: Symbol ;
1011use rustc_span:: { sym, Span } ;
1112
1213declare_clippy_lint ! {
@@ -17,6 +18,11 @@ declare_clippy_lint! {
1718 /// ### Why is this bad?
1819 /// Using just `insert` and checking the returned `bool` is more efficient.
1920 ///
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+ ///
2026 /// ### Example
2127 /// ```rust
2228 /// use std::collections::HashSet;
@@ -37,12 +43,12 @@ declare_clippy_lint! {
3743 /// }
3844 /// ```
3945 #[ clippy:: version = "1.80.0" ]
40- pub HASHSET_INSERT_AFTER_CONTAINS ,
46+ pub SET_CONTAINS_OR_INSERT ,
4147 nursery,
42- "unnecessary call to `HashSet::contains` followed by `HashSet::insert`"
48+ "call to `HashSet::contains` followed by `HashSet::insert`"
4349}
4450
45- declare_lint_pass ! ( HashsetInsertAfterContains => [ HASHSET_INSERT_AFTER_CONTAINS ] ) ;
51+ declare_lint_pass ! ( HashsetInsertAfterContains => [ SET_CONTAINS_OR_INSERT ] ) ;
4652
4753impl < ' tcx > LateLintPass < ' tcx > for HashsetInsertAfterContains {
4854 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
@@ -52,56 +58,35 @@ impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains {
5258 then : then_expr,
5359 ..
5460 } ) = 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)
5763 {
58- span_lint_and_then (
64+ span_lint (
5965 cx,
60- HASHSET_INSERT_AFTER_CONTAINS ,
61- expr . span ,
66+ SET_CONTAINS_OR_INSERT ,
67+ vec ! [ contains_expr . span, insert_expr . span ] ,
6268 "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- } ,
6769 ) ;
6870 }
6971 }
7072}
7173
72- struct ContainsExpr < ' tcx > {
74+ struct OpExpr < ' tcx > {
7375 receiver : & ' tcx Expr < ' tcx > ,
7476 value : & ' tcx Expr < ' tcx > ,
7577 span : Span ,
7678}
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 > > {
7881 let expr = peel_hir_expr_while ( expr, |e| {
7982 if let ExprKind :: Unary ( UnOp :: Not , e) = e. kind {
8083 Some ( e)
8184 } else {
8285 None
8386 }
8487 } ) ;
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- }
9888
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 {
10590 let value = value. peel_borrows ( ) ;
10691 let value = peel_hir_expr_while ( value, |e| {
10792 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
11095 None
11196 }
11297 } ) ;
113-
98+ let receiver = receiver . peel_borrows ( ) ;
11499 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 } ) ;
119105 }
120- } else {
121- None
122106 }
107+ None
123108}
124109
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 > > {
126115 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 ) )
128117 && SpanlessEq :: new ( cx) . eq_expr ( contains_expr. receiver , insert_expr. receiver )
129118 && SpanlessEq :: new ( cx) . eq_expr ( contains_expr. value , insert_expr. value )
130119 {
131- ControlFlow :: Break ( ( ) )
120+ ControlFlow :: Break ( insert_expr )
132121 } else {
133122 ControlFlow :: Continue ( ( ) )
134123 }
135124 } )
136- . is_some ( )
137125}
0 commit comments