]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::{contains_name, higher, iter_input_pats, snippet, span_lint_and_then}; |
2 | use rustc_hir::intravisit::FnKind; | |
3 | use rustc_hir::{ | |
4 | Block, Body, Expr, ExprKind, FnDecl, Guard, HirId, Local, MutTy, Pat, PatKind, Path, QPath, StmtKind, Ty, TyKind, | |
5 | UnOp, | |
6 | }; | |
7 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
8 | use rustc_middle::lint::in_external_macro; | |
9 | use rustc_middle::ty; | |
10 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
11 | use rustc_span::source_map::Span; | |
12 | use rustc_span::symbol::Symbol; | |
13 | ||
14 | declare_clippy_lint! { | |
15 | /// **What it does:** Checks for bindings that shadow other bindings already in | |
16 | /// scope, while just changing reference level or mutability. | |
17 | /// | |
18 | /// **Why is this bad?** Not much, in fact it's a very common pattern in Rust | |
19 | /// code. Still, some may opt to avoid it in their code base, they can set this | |
20 | /// lint to `Warn`. | |
21 | /// | |
22 | /// **Known problems:** This lint, as the other shadowing related lints, | |
23 | /// currently only catches very simple patterns. | |
24 | /// | |
25 | /// **Example:** | |
26 | /// ```rust | |
27 | /// # let x = 1; | |
28 | /// // Bad | |
29 | /// let x = &x; | |
30 | /// | |
31 | /// // Good | |
32 | /// let y = &x; // use different variable name | |
33 | /// ``` | |
34 | pub SHADOW_SAME, | |
35 | restriction, | |
36 | "rebinding a name to itself, e.g., `let mut x = &mut x`" | |
37 | } | |
38 | ||
39 | declare_clippy_lint! { | |
40 | /// **What it does:** Checks for bindings that shadow other bindings already in | |
41 | /// scope, while reusing the original value. | |
42 | /// | |
43 | /// **Why is this bad?** Not too much, in fact it's a common pattern in Rust | |
44 | /// code. Still, some argue that name shadowing like this hurts readability, | |
45 | /// because a value may be bound to different things depending on position in | |
46 | /// the code. | |
47 | /// | |
48 | /// **Known problems:** This lint, as the other shadowing related lints, | |
49 | /// currently only catches very simple patterns. | |
50 | /// | |
51 | /// **Example:** | |
52 | /// ```rust | |
53 | /// let x = 2; | |
54 | /// let x = x + 1; | |
55 | /// ``` | |
56 | /// use different variable name: | |
57 | /// ```rust | |
58 | /// let x = 2; | |
59 | /// let y = x + 1; | |
60 | /// ``` | |
61 | pub SHADOW_REUSE, | |
62 | restriction, | |
63 | "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`" | |
64 | } | |
65 | ||
66 | declare_clippy_lint! { | |
67 | /// **What it does:** Checks for bindings that shadow other bindings already in | |
68 | /// scope, either without a initialization or with one that does not even use | |
69 | /// the original value. | |
70 | /// | |
71 | /// **Why is this bad?** Name shadowing can hurt readability, especially in | |
72 | /// large code bases, because it is easy to lose track of the active binding at | |
73 | /// any place in the code. This can be alleviated by either giving more specific | |
74 | /// names to bindings or introducing more scopes to contain the bindings. | |
75 | /// | |
76 | /// **Known problems:** This lint, as the other shadowing related lints, | |
77 | /// currently only catches very simple patterns. Note that | |
78 | /// `allow`/`warn`/`deny`/`forbid` attributes only work on the function level | |
79 | /// for this lint. | |
80 | /// | |
81 | /// **Example:** | |
82 | /// ```rust | |
83 | /// # let y = 1; | |
84 | /// # let z = 2; | |
85 | /// let x = y; | |
86 | /// | |
87 | /// // Bad | |
88 | /// let x = z; // shadows the earlier binding | |
89 | /// | |
90 | /// // Good | |
91 | /// let w = z; // use different variable name | |
92 | /// ``` | |
93 | pub SHADOW_UNRELATED, | |
94 | pedantic, | |
95 | "rebinding a name without even using the original value" | |
96 | } | |
97 | ||
98 | declare_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]); | |
99 | ||
100 | impl<'tcx> LateLintPass<'tcx> for Shadow { | |
101 | fn check_fn( | |
102 | &mut self, | |
103 | cx: &LateContext<'tcx>, | |
104 | _: FnKind<'tcx>, | |
105 | decl: &'tcx FnDecl<'_>, | |
106 | body: &'tcx Body<'_>, | |
107 | _: Span, | |
108 | _: HirId, | |
109 | ) { | |
110 | if in_external_macro(cx.sess(), body.value.span) { | |
111 | return; | |
112 | } | |
113 | check_fn(cx, decl, body); | |
114 | } | |
115 | } | |
116 | ||
117 | fn check_fn<'tcx>(cx: &LateContext<'tcx>, decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>) { | |
118 | let mut bindings = Vec::with_capacity(decl.inputs.len()); | |
119 | for arg in iter_input_pats(decl, body) { | |
120 | if let PatKind::Binding(.., ident, _) = arg.pat.kind { | |
121 | bindings.push((ident.name, ident.span)) | |
122 | } | |
123 | } | |
124 | check_expr(cx, &body.value, &mut bindings); | |
125 | } | |
126 | ||
127 | fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>, bindings: &mut Vec<(Symbol, Span)>) { | |
128 | let len = bindings.len(); | |
129 | for stmt in block.stmts { | |
130 | match stmt.kind { | |
131 | StmtKind::Local(ref local) => check_local(cx, local, bindings), | |
132 | StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => check_expr(cx, e, bindings), | |
133 | StmtKind::Item(..) => {}, | |
134 | } | |
135 | } | |
136 | if let Some(ref o) = block.expr { | |
137 | check_expr(cx, o, bindings); | |
138 | } | |
139 | bindings.truncate(len); | |
140 | } | |
141 | ||
142 | fn check_local<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>, bindings: &mut Vec<(Symbol, Span)>) { | |
143 | if in_external_macro(cx.sess(), local.span) { | |
144 | return; | |
145 | } | |
146 | if higher::is_from_for_desugar(local) { | |
147 | return; | |
148 | } | |
149 | let Local { | |
150 | ref pat, | |
151 | ref ty, | |
152 | ref init, | |
153 | span, | |
154 | .. | |
155 | } = *local; | |
156 | if let Some(ref t) = *ty { | |
157 | check_ty(cx, t, bindings) | |
158 | } | |
159 | if let Some(ref o) = *init { | |
160 | check_expr(cx, o, bindings); | |
161 | check_pat(cx, pat, Some(o), span, bindings); | |
162 | } else { | |
163 | check_pat(cx, pat, None, span, bindings); | |
164 | } | |
165 | } | |
166 | ||
167 | fn is_binding(cx: &LateContext<'_>, pat_id: HirId) -> bool { | |
168 | let var_ty = cx.typeck_results().node_type_opt(pat_id); | |
169 | var_ty.map_or(false, |var_ty| !matches!(var_ty.kind(), ty::Adt(..))) | |
170 | } | |
171 | ||
172 | fn check_pat<'tcx>( | |
173 | cx: &LateContext<'tcx>, | |
174 | pat: &'tcx Pat<'_>, | |
175 | init: Option<&'tcx Expr<'_>>, | |
176 | span: Span, | |
177 | bindings: &mut Vec<(Symbol, Span)>, | |
178 | ) { | |
179 | // TODO: match more stuff / destructuring | |
180 | match pat.kind { | |
181 | PatKind::Binding(.., ident, ref inner) => { | |
182 | let name = ident.name; | |
183 | if is_binding(cx, pat.hir_id) { | |
184 | let mut new_binding = true; | |
185 | for tup in bindings.iter_mut() { | |
186 | if tup.0 == name { | |
187 | lint_shadow(cx, name, span, pat.span, init, tup.1); | |
188 | tup.1 = ident.span; | |
189 | new_binding = false; | |
190 | break; | |
191 | } | |
192 | } | |
193 | if new_binding { | |
194 | bindings.push((name, ident.span)); | |
195 | } | |
196 | } | |
197 | if let Some(ref p) = *inner { | |
198 | check_pat(cx, p, init, span, bindings); | |
199 | } | |
200 | }, | |
201 | PatKind::Struct(_, pfields, _) => { | |
202 | if let Some(init_struct) = init { | |
203 | if let ExprKind::Struct(_, ref efields, _) = init_struct.kind { | |
204 | for field in pfields { | |
205 | let name = field.ident.name; | |
206 | let efield = efields | |
207 | .iter() | |
208 | .find_map(|f| if f.ident.name == name { Some(&*f.expr) } else { None }); | |
209 | check_pat(cx, &field.pat, efield, span, bindings); | |
210 | } | |
211 | } else { | |
212 | for field in pfields { | |
213 | check_pat(cx, &field.pat, init, span, bindings); | |
214 | } | |
215 | } | |
216 | } else { | |
217 | for field in pfields { | |
218 | check_pat(cx, &field.pat, None, span, bindings); | |
219 | } | |
220 | } | |
221 | }, | |
222 | PatKind::Tuple(inner, _) => { | |
223 | if let Some(init_tup) = init { | |
224 | if let ExprKind::Tup(ref tup) = init_tup.kind { | |
225 | for (i, p) in inner.iter().enumerate() { | |
226 | check_pat(cx, p, Some(&tup[i]), p.span, bindings); | |
227 | } | |
228 | } else { | |
229 | for p in inner { | |
230 | check_pat(cx, p, init, span, bindings); | |
231 | } | |
232 | } | |
233 | } else { | |
234 | for p in inner { | |
235 | check_pat(cx, p, None, span, bindings); | |
236 | } | |
237 | } | |
238 | }, | |
239 | PatKind::Box(ref inner) => { | |
240 | if let Some(initp) = init { | |
241 | if let ExprKind::Box(ref inner_init) = initp.kind { | |
242 | check_pat(cx, inner, Some(&**inner_init), span, bindings); | |
243 | } else { | |
244 | check_pat(cx, inner, init, span, bindings); | |
245 | } | |
246 | } else { | |
247 | check_pat(cx, inner, init, span, bindings); | |
248 | } | |
249 | }, | |
250 | PatKind::Ref(ref inner, _) => check_pat(cx, inner, init, span, bindings), | |
251 | // PatVec(Vec<P<Pat>>, Option<P<Pat>>, Vec<P<Pat>>), | |
252 | _ => (), | |
253 | } | |
254 | } | |
255 | ||
256 | fn lint_shadow<'tcx>( | |
257 | cx: &LateContext<'tcx>, | |
258 | name: Symbol, | |
259 | span: Span, | |
260 | pattern_span: Span, | |
261 | init: Option<&'tcx Expr<'_>>, | |
262 | prev_span: Span, | |
263 | ) { | |
264 | if let Some(expr) = init { | |
265 | if is_self_shadow(name, expr) { | |
266 | span_lint_and_then( | |
267 | cx, | |
268 | SHADOW_SAME, | |
269 | span, | |
270 | &format!( | |
271 | "`{}` is shadowed by itself in `{}`", | |
272 | snippet(cx, pattern_span, "_"), | |
273 | snippet(cx, expr.span, "..") | |
274 | ), | |
275 | |diag| { | |
276 | diag.span_note(prev_span, "previous binding is here"); | |
277 | }, | |
278 | ); | |
279 | } else if contains_name(name, expr) { | |
280 | span_lint_and_then( | |
281 | cx, | |
282 | SHADOW_REUSE, | |
283 | pattern_span, | |
284 | &format!( | |
285 | "`{}` is shadowed by `{}` which reuses the original value", | |
286 | snippet(cx, pattern_span, "_"), | |
287 | snippet(cx, expr.span, "..") | |
288 | ), | |
289 | |diag| { | |
290 | diag.span_note(expr.span, "initialization happens here"); | |
291 | diag.span_note(prev_span, "previous binding is here"); | |
292 | }, | |
293 | ); | |
294 | } else { | |
295 | span_lint_and_then( | |
296 | cx, | |
297 | SHADOW_UNRELATED, | |
298 | pattern_span, | |
299 | &format!("`{}` is being shadowed", snippet(cx, pattern_span, "_")), | |
300 | |diag| { | |
301 | diag.span_note(expr.span, "initialization happens here"); | |
302 | diag.span_note(prev_span, "previous binding is here"); | |
303 | }, | |
304 | ); | |
305 | } | |
306 | } else { | |
307 | span_lint_and_then( | |
308 | cx, | |
309 | SHADOW_UNRELATED, | |
310 | span, | |
311 | &format!("`{}` shadows a previous declaration", snippet(cx, pattern_span, "_")), | |
312 | |diag| { | |
313 | diag.span_note(prev_span, "previous binding is here"); | |
314 | }, | |
315 | ); | |
316 | } | |
317 | } | |
318 | ||
319 | fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, bindings: &mut Vec<(Symbol, Span)>) { | |
320 | if in_external_macro(cx.sess(), expr.span) { | |
321 | return; | |
322 | } | |
323 | match expr.kind { | |
324 | ExprKind::Unary(_, ref e) | |
325 | | ExprKind::Field(ref e, _) | |
326 | | ExprKind::AddrOf(_, _, ref e) | |
327 | | ExprKind::Box(ref e) => check_expr(cx, e, bindings), | |
328 | ExprKind::Block(ref block, _) | ExprKind::Loop(ref block, ..) => check_block(cx, block, bindings), | |
329 | // ExprKind::Call | |
330 | // ExprKind::MethodCall | |
331 | ExprKind::Array(v) | ExprKind::Tup(v) => { | |
332 | for e in v { | |
333 | check_expr(cx, e, bindings) | |
334 | } | |
335 | }, | |
336 | ExprKind::If(ref cond, ref then, ref otherwise) => { | |
337 | check_expr(cx, cond, bindings); | |
338 | check_expr(cx, &**then, bindings); | |
339 | if let Some(ref o) = *otherwise { | |
340 | check_expr(cx, o, bindings); | |
341 | } | |
342 | }, | |
343 | ExprKind::Match(ref init, arms, _) => { | |
344 | check_expr(cx, init, bindings); | |
345 | let len = bindings.len(); | |
346 | for arm in arms { | |
347 | check_pat(cx, &arm.pat, Some(&**init), arm.pat.span, bindings); | |
348 | // This is ugly, but needed to get the right type | |
349 | if let Some(ref guard) = arm.guard { | |
350 | match guard { | |
351 | Guard::If(if_expr) => check_expr(cx, if_expr, bindings), | |
352 | Guard::IfLet(guard_pat, guard_expr) => { | |
353 | check_pat(cx, guard_pat, Some(*guard_expr), guard_pat.span, bindings); | |
354 | check_expr(cx, guard_expr, bindings); | |
355 | }, | |
356 | } | |
357 | } | |
358 | check_expr(cx, &arm.body, bindings); | |
359 | bindings.truncate(len); | |
360 | } | |
361 | }, | |
362 | _ => (), | |
363 | } | |
364 | } | |
365 | ||
366 | fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: &'tcx Ty<'_>, bindings: &mut Vec<(Symbol, Span)>) { | |
367 | match ty.kind { | |
368 | TyKind::Slice(ref sty) => check_ty(cx, sty, bindings), | |
369 | TyKind::Array(ref fty, ref anon_const) => { | |
370 | check_ty(cx, fty, bindings); | |
371 | check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings); | |
372 | }, | |
373 | TyKind::Ptr(MutTy { ty: ref mty, .. }) | TyKind::Rptr(_, MutTy { ty: ref mty, .. }) => { | |
374 | check_ty(cx, mty, bindings) | |
375 | }, | |
376 | TyKind::Tup(tup) => { | |
377 | for t in tup { | |
378 | check_ty(cx, t, bindings) | |
379 | } | |
380 | }, | |
381 | TyKind::Typeof(ref anon_const) => check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings), | |
382 | _ => (), | |
383 | } | |
384 | } | |
385 | ||
386 | fn is_self_shadow(name: Symbol, expr: &Expr<'_>) -> bool { | |
387 | match expr.kind { | |
388 | ExprKind::Box(ref inner) | ExprKind::AddrOf(_, _, ref inner) => is_self_shadow(name, inner), | |
389 | ExprKind::Block(ref block, _) => { | |
390 | block.stmts.is_empty() && block.expr.as_ref().map_or(false, |e| is_self_shadow(name, e)) | |
391 | }, | |
392 | ExprKind::Unary(op, ref inner) => (UnOp::Deref == op) && is_self_shadow(name, inner), | |
393 | ExprKind::Path(QPath::Resolved(_, ref path)) => path_eq_name(name, path), | |
394 | _ => false, | |
395 | } | |
396 | } | |
397 | ||
398 | fn path_eq_name(name: Symbol, path: &Path<'_>) -> bool { | |
399 | !path.is_global() && path.segments.len() == 1 && path.segments[0].ident.name == name | |
400 | } |