3
3
//! This lint is **warn** by default
4
4
5
5
use clippy_utils:: diagnostics:: span_lint_and_then;
6
- 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} ;
7
8
use if_chain:: if_chain;
9
+ use rustc_ast:: util:: parser:: PREC_POSTFIX ;
10
+ use rustc_data_structures:: fx:: FxIndexMap ;
8
11
use rustc_errors:: Applicability ;
9
- use rustc_hir:: { BindingAnnotation , BorrowKind , Expr , ExprKind , Mutability , Pat , PatKind } ;
12
+ use rustc_hir:: { BindingAnnotation , Body , BodyId , BorrowKind , Expr , ExprKind , HirId , Mutability , Pat , PatKind , UnOp } ;
10
13
use rustc_lint:: { LateContext , LateLintPass } ;
11
14
use rustc_middle:: ty;
12
15
use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
13
16
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
17
+ use rustc_span:: Span ;
14
18
15
19
declare_clippy_lint ! {
16
20
/// **What it does:** Checks for address of operations (`&`) that are going to
@@ -34,13 +38,65 @@ declare_clippy_lint! {
34
38
"taking a reference that is going to be automatically dereferenced"
35
39
}
36
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 ] ) ;
37
68
#[ derive( Default ) ]
38
- pub struct NeedlessBorrow ;
69
+ pub struct NeedlessBorrow {
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 > > ,
81
+ }
39
82
40
- 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
+ }
41
93
42
94
impl < ' tcx > LateLintPass < ' tcx > for NeedlessBorrow {
43
95
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
96
+ if let Some ( local) = path_to_local ( e) {
97
+ self . check_local_usage ( cx, e, local) ;
98
+ }
99
+
44
100
if e. span . from_expansion ( ) {
45
101
return ;
46
102
}
@@ -81,35 +137,132 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
81
137
}
82
138
}
83
139
}
140
+
84
141
fn check_pat ( & mut self , cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > ) {
85
- if pat. span . from_expansion ( ) {
86
- 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
+ }
87
182
}
88
- if_chain ! {
89
- if let PatKind :: Binding ( BindingAnnotation :: Ref , .., name, _) = pat. kind;
90
- if let ty:: Ref ( _, tam, mutbl) = * cx. typeck_results( ) . pat_ty( pat) . kind( ) ;
91
- if mutbl == Mutability :: Not ;
92
- if let ty:: Ref ( _, _, mutbl) = * tam. kind( ) ;
93
- // only lint immutable refs, because borrowed `&mut T` cannot be moved out
94
- if mutbl == Mutability :: Not ;
95
- 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 ;
96
190
span_lint_and_then (
97
191
cx,
98
- NEEDLESS_BORROW ,
99
- pat. span,
192
+ if pat. always_deref {
193
+ NEEDLESS_BORROW
194
+ } else {
195
+ REF_BINDING_TO_REFERENCE
196
+ } ,
197
+ pat. spans ,
100
198
"this pattern creates a reference to a reference" ,
101
199
|diag| {
102
- if let Some ( snippet) = snippet_opt( cx, name. span) {
103
- diag. span_suggestion(
104
- pat. span,
105
- "change this to" ,
106
- snippet,
107
- Applicability :: MachineApplicable ,
108
- ) ;
109
- }
110
- }
200
+ diag. multipart_suggestion ( "try this" , replacements, app) ;
201
+ } ,
111
202
)
112
203
}
204
+ self . current_body = None ;
205
+ }
206
+ }
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
+ }
265
+ }
113
266
}
114
267
}
115
268
}
0 commit comments