3
3
//! This lint is **warn** by default
4
4
5
5
use clippy_utils:: diagnostics:: span_lint_and_then;
6
- use clippy_utils:: is_automatically_derived ;
7
- use clippy_utils:: source :: snippet_opt ;
6
+ use clippy_utils:: source :: { snippet_opt , snippet_with_applicability , snippet_with_context } ;
7
+ use clippy_utils:: { get_parent_expr , in_macro , path_to_local } ;
8
8
use if_chain:: if_chain;
9
+ use rustc_ast:: util:: parser:: PREC_POSTFIX ;
10
+ use rustc_data_structures:: fx:: FxIndexMap ;
9
11
use rustc_errors:: Applicability ;
10
- use rustc_hir:: { BindingAnnotation , BorrowKind , Expr , ExprKind , Item , Mutability , Pat , PatKind } ;
12
+ use rustc_hir:: { BindingAnnotation , Body , BodyId , BorrowKind , Expr , ExprKind , HirId , Mutability , Pat , PatKind , UnOp } ;
11
13
use rustc_lint:: { LateContext , LateLintPass } ;
12
14
use rustc_middle:: ty;
13
15
use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
14
16
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
15
- use rustc_span:: def_id :: LocalDefId ;
17
+ use rustc_span:: Span ;
16
18
17
19
declare_clippy_lint ! {
18
20
/// **What it does:** Checks for address of operations (`&`) that are going to
@@ -36,16 +38,66 @@ declare_clippy_lint! {
36
38
"taking a reference that is going to be automatically dereferenced"
37
39
}
38
40
41
+ declare_clippy_lint ! {
42
+ /// **What it does:** Checks for `ref` bindings which create a reference to a reference.
43
+ ///
44
+ /// **Why is this bad?** The address-of operator at the use site is clearer about the need for a reference.
45
+ ///
46
+ /// **Known problems:** None.
47
+ ///
48
+ /// **Example:**
49
+ /// ```rust
50
+ /// // Bad
51
+ /// let x = Some("");
52
+ /// if let Some(ref x) = x {
53
+ /// // use `x` here
54
+ /// }
55
+ ///
56
+ /// // Good
57
+ /// let x = Some("");
58
+ /// if let Some(x) = x {
59
+ /// // use `&x` here
60
+ /// }
61
+ /// ```
62
+ pub REF_BINDING_TO_REFERENCE ,
63
+ pedantic,
64
+ "`ref` binding to a reference"
65
+ }
66
+
67
+ impl_lint_pass ! ( NeedlessBorrow => [ NEEDLESS_BORROW , REF_BINDING_TO_REFERENCE ] ) ;
39
68
#[ derive( Default ) ]
40
69
pub struct NeedlessBorrow {
41
- derived_item : Option < LocalDefId > ,
70
+ /// The body the first local was found in. Used to emit lints when the traversal of the body has
71
+ /// been finished. Note we can't lint at the end of every body as they can be nested within each
72
+ /// other.
73
+ current_body : Option < BodyId > ,
74
+ /// The list of locals currently being checked by the lint.
75
+ /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted.
76
+ /// This is needed for or patterns where one of the branches can be linted, but another can not
77
+ /// be.
78
+ ///
79
+ /// e.g. `m!(x) | Foo::Bar(ref x)`
80
+ ref_locals : FxIndexMap < HirId , Option < RefPat > > ,
42
81
}
43
82
44
- impl_lint_pass ! ( NeedlessBorrow => [ NEEDLESS_BORROW ] ) ;
83
+ struct RefPat {
84
+ /// Whether every usage of the binding is dereferenced.
85
+ always_deref : bool ,
86
+ /// The spans of all the ref bindings for this local.
87
+ spans : Vec < Span > ,
88
+ /// The applicability of this suggestion.
89
+ app : Applicability ,
90
+ /// All the replacements which need to be made.
91
+ replacements : Vec < ( Span , String ) > ,
92
+ }
45
93
46
94
impl < ' tcx > LateLintPass < ' tcx > for NeedlessBorrow {
47
95
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
48
- if e. span . from_expansion ( ) || self . derived_item . is_some ( ) {
96
+ if let Some ( local) = path_to_local ( e) {
97
+ self . check_local_usage ( cx, e, local) ;
98
+ }
99
+
100
+ if e. span . from_expansion ( ) {
49
101
return ;
50
102
}
51
103
if let ExprKind :: AddrOf ( BorrowKind :: Ref , Mutability :: Not , inner) = e. kind {
@@ -85,50 +137,131 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
85
137
}
86
138
}
87
139
}
140
+
88
141
fn check_pat ( & mut self , cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > ) {
89
- if pat. span . from_expansion ( ) || self . derived_item . is_some ( ) {
90
- return ;
142
+ if let PatKind :: Binding ( BindingAnnotation :: Ref , id, name, _) = pat. kind {
143
+ if let Some ( opt_prev_pat) = self . ref_locals . get_mut ( & id) {
144
+ // This binding id has been seen before. Add this pattern to the list of changes.
145
+ if let Some ( prev_pat) = opt_prev_pat {
146
+ if in_macro ( pat. span ) {
147
+ // Doesn't match the context of the previous pattern. Can't lint here.
148
+ * opt_prev_pat = None ;
149
+ } else {
150
+ prev_pat. spans . push ( pat. span ) ;
151
+ prev_pat. replacements . push ( (
152
+ pat. span ,
153
+ snippet_with_context ( cx, name. span , pat. span . ctxt ( ) , ".." , & mut prev_pat. app )
154
+ . 0
155
+ . into ( ) ,
156
+ ) ) ;
157
+ }
158
+ }
159
+ return ;
160
+ }
161
+
162
+ if_chain ! {
163
+ if !in_macro( pat. span) ;
164
+ if let ty:: Ref ( _, tam, _) = * cx. typeck_results( ) . pat_ty( pat) . kind( ) ;
165
+ // only lint immutable refs, because borrowed `&mut T` cannot be moved out
166
+ if let ty:: Ref ( _, _, Mutability :: Not ) = * tam. kind( ) ;
167
+ then {
168
+ let mut app = Applicability :: MachineApplicable ;
169
+ let snip = snippet_with_context( cx, name. span, pat. span. ctxt( ) , ".." , & mut app) . 0 ;
170
+ self . current_body = self . current_body. or( cx. enclosing_body) ;
171
+ self . ref_locals. insert(
172
+ id,
173
+ Some ( RefPat {
174
+ always_deref: true ,
175
+ spans: vec![ pat. span] ,
176
+ app,
177
+ replacements: vec![ ( pat. span, snip. into( ) ) ] ,
178
+ } ) ,
179
+ ) ;
180
+ }
181
+ }
91
182
}
92
- if_chain ! {
93
- if let PatKind :: Binding ( BindingAnnotation :: Ref , .., name, _) = pat. kind;
94
- if let ty:: Ref ( _, tam, mutbl) = * cx. typeck_results( ) . pat_ty( pat) . kind( ) ;
95
- if mutbl == Mutability :: Not ;
96
- if let ty:: Ref ( _, _, mutbl) = * tam. kind( ) ;
97
- // only lint immutable refs, because borrowed `&mut T` cannot be moved out
98
- if mutbl == Mutability :: Not ;
99
- then {
183
+ }
184
+
185
+ fn check_body_post ( & mut self , cx : & LateContext < ' tcx > , body : & ' tcx Body < ' _ > ) {
186
+ if Some ( body. id ( ) ) == self . current_body {
187
+ for pat in self . ref_locals . drain ( ..) . filter_map ( |( _, x) | x) {
188
+ let replacements = pat. replacements ;
189
+ let app = pat. app ;
100
190
span_lint_and_then (
101
191
cx,
102
- NEEDLESS_BORROW ,
103
- pat. span,
192
+ if pat. always_deref {
193
+ NEEDLESS_BORROW
194
+ } else {
195
+ REF_BINDING_TO_REFERENCE
196
+ } ,
197
+ pat. spans ,
104
198
"this pattern creates a reference to a reference" ,
105
199
|diag| {
106
- if let Some ( snippet) = snippet_opt( cx, name. span) {
107
- diag. span_suggestion(
108
- pat. span,
109
- "change this to" ,
110
- snippet,
111
- Applicability :: MachineApplicable ,
112
- ) ;
113
- }
114
- }
200
+ diag. multipart_suggestion ( "try this" , replacements, app) ;
201
+ } ,
115
202
)
116
203
}
204
+ self . current_body = None ;
117
205
}
118
206
}
119
-
120
- fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
121
- let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
122
- if is_automatically_derived ( attrs) {
123
- debug_assert ! ( self . derived_item. is_none( ) ) ;
124
- self . derived_item = Some ( item. def_id ) ;
125
- }
126
- }
127
-
128
- fn check_item_post ( & mut self , _: & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
129
- if let Some ( id) = self . derived_item {
130
- if item. def_id == id {
131
- self . derived_item = None ;
207
+ }
208
+ impl NeedlessBorrow {
209
+ fn check_local_usage ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > , local : HirId ) {
210
+ if let Some ( outer_pat) = self . ref_locals . get_mut ( & local) {
211
+ if let Some ( pat) = outer_pat {
212
+ // Check for auto-deref
213
+ if !matches ! (
214
+ cx. typeck_results( ) . expr_adjustments( e) ,
215
+ [
216
+ Adjustment {
217
+ kind: Adjust :: Deref ( _) ,
218
+ ..
219
+ } ,
220
+ Adjustment {
221
+ kind: Adjust :: Deref ( _) ,
222
+ ..
223
+ } ,
224
+ ..
225
+ ]
226
+ ) {
227
+ match get_parent_expr ( cx, e) {
228
+ // Field accesses are the same no matter the number of references.
229
+ Some ( Expr {
230
+ kind : ExprKind :: Field ( ..) ,
231
+ ..
232
+ } ) => ( ) ,
233
+ Some ( & Expr {
234
+ span,
235
+ kind : ExprKind :: Unary ( UnOp :: Deref , _) ,
236
+ ..
237
+ } ) if !in_macro ( span) => {
238
+ // Remove explicit deref.
239
+ let snip = snippet_with_context ( cx, e. span , span. ctxt ( ) , ".." , & mut pat. app ) . 0 ;
240
+ pat. replacements . push ( ( span, snip. into ( ) ) ) ;
241
+ } ,
242
+ Some ( parent) if !in_macro ( parent. span ) => {
243
+ // Double reference might be needed at this point.
244
+ if parent. precedence ( ) . order ( ) == PREC_POSTFIX {
245
+ // Parentheses would be needed here, don't lint.
246
+ * outer_pat = None ;
247
+ } else {
248
+ pat. always_deref = false ;
249
+ let snip = snippet_with_context ( cx, e. span , parent. span . ctxt ( ) , ".." , & mut pat. app ) . 0 ;
250
+ pat. replacements . push ( ( e. span , format ! ( "&{}" , snip) ) ) ;
251
+ }
252
+ } ,
253
+ _ if !in_macro ( e. span ) => {
254
+ // Double reference might be needed at this point.
255
+ pat. always_deref = false ;
256
+ let snip = snippet_with_applicability ( cx, e. span , ".." , & mut pat. app ) ;
257
+ pat. replacements . push ( ( e. span , format ! ( "&{}" , snip) ) ) ;
258
+ } ,
259
+ // Edge case for macros. The span of the identifier will usually match the context of the
260
+ // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
261
+ // macros
262
+ _ => * outer_pat = None ,
263
+ }
264
+ }
132
265
}
133
266
}
134
267
}
0 commit comments