]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/no_effect.rs
Rollup merge of #89789 - jkugelman:must-use-thread-builder, r=joshtriplett
[rust.git] / src / tools / clippy / clippy_lints / src / no_effect.rs
1 use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
2 use clippy_utils::is_lint_allowed;
3 use clippy_utils::source::snippet_opt;
4 use clippy_utils::ty::has_drop;
5 use rustc_errors::Applicability;
6 use rustc_hir::def::{DefKind, Res};
7 use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, PatKind, Stmt, StmtKind, UnsafeSource};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 use std::ops::Deref;
11
12 declare_clippy_lint! {
13     /// ### What it does
14     /// Checks for statements which have no effect.
15     ///
16     /// ### Why is this bad?
17     /// Unlike dead code, these statements are actually
18     /// executed. However, as they have no effect, all they do is make the code less
19     /// readable.
20     ///
21     /// ### Example
22     /// ```rust
23     /// 0;
24     /// ```
25     pub NO_EFFECT,
26     complexity,
27     "statements with no effect"
28 }
29
30 declare_clippy_lint! {
31     /// ### What it does
32     /// Checks for binding to underscore prefixed variable without side-effects.
33     ///
34     /// ### Why is this bad?
35     /// Unlike dead code, these bindings are actually
36     /// executed. However, as they have no effect and shouldn't be used further on, all they
37     /// do is make the code less readable.
38     ///
39     /// ### Known problems
40     /// Further usage of this variable is not checked, which can lead to false positives if it is
41     /// used later in the code.
42     ///
43     /// ### Example
44     /// ```rust,ignore
45     /// let _i_serve_no_purpose = 1;
46     /// ```
47     pub NO_EFFECT_UNDERSCORE_BINDING,
48     pedantic,
49     "binding to `_` prefixed variable with no side-effect"
50 }
51
52 declare_clippy_lint! {
53     /// ### What it does
54     /// Checks for expression statements that can be reduced to a
55     /// sub-expression.
56     ///
57     /// ### Why is this bad?
58     /// Expressions by themselves often have no side-effects.
59     /// Having such expressions reduces readability.
60     ///
61     /// ### Example
62     /// ```rust,ignore
63     /// compute_array()[0];
64     /// ```
65     pub UNNECESSARY_OPERATION,
66     complexity,
67     "outer expressions with no effect"
68 }
69
70 declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]);
71
72 impl<'tcx> LateLintPass<'tcx> for NoEffect {
73     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
74         if check_no_effect(cx, stmt) {
75             return;
76         }
77         check_unnecessary_operation(cx, stmt);
78     }
79 }
80
81 fn check_no_effect(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> bool {
82     if let StmtKind::Semi(expr) = stmt.kind {
83         if has_no_effect(cx, expr) {
84             span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
85             return true;
86         }
87     } else if let StmtKind::Local(local) = stmt.kind {
88         if_chain! {
89             if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id);
90             if let Some(init) = local.init;
91             if !local.pat.span.from_expansion();
92             if has_no_effect(cx, init);
93             if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
94             if ident.name.to_ident_string().starts_with('_');
95             then {
96                 span_lint_hir(
97                     cx,
98                     NO_EFFECT_UNDERSCORE_BINDING,
99                     init.hir_id,
100                     stmt.span,
101                     "binding to `_` prefixed variable with no side-effect"
102                 );
103                 return true;
104             }
105         }
106     }
107     false
108 }
109
110 fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
111     if expr.span.from_expansion() {
112         return false;
113     }
114     match expr.kind {
115         ExprKind::Lit(..) | ExprKind::Closure(..) => true,
116         ExprKind::Path(..) => !has_drop(cx, cx.typeck_results().expr_ty(expr)),
117         ExprKind::Index(a, b) | ExprKind::Binary(_, a, b) => has_no_effect(cx, a) && has_no_effect(cx, b),
118         ExprKind::Array(v) | ExprKind::Tup(v) => v.iter().all(|val| has_no_effect(cx, val)),
119         ExprKind::Repeat(inner, _)
120         | ExprKind::Cast(inner, _)
121         | ExprKind::Type(inner, _)
122         | ExprKind::Unary(_, inner)
123         | ExprKind::Field(inner, _)
124         | ExprKind::AddrOf(_, _, inner)
125         | ExprKind::Box(inner) => has_no_effect(cx, inner),
126         ExprKind::Struct(_, fields, ref base) => {
127             !has_drop(cx, cx.typeck_results().expr_ty(expr))
128                 && fields.iter().all(|field| has_no_effect(cx, field.expr))
129                 && base.as_ref().map_or(true, |base| has_no_effect(cx, base))
130         },
131         ExprKind::Call(callee, args) => {
132             if let ExprKind::Path(ref qpath) = callee.kind {
133                 let res = cx.qpath_res(qpath, callee.hir_id);
134                 let def_matched = matches!(
135                     res,
136                     Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..)
137                 );
138                 if def_matched || is_range_literal(expr) {
139                     !has_drop(cx, cx.typeck_results().expr_ty(expr)) && args.iter().all(|arg| has_no_effect(cx, arg))
140                 } else {
141                     false
142                 }
143             } else {
144                 false
145             }
146         },
147         ExprKind::Block(block, _) => {
148             block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| has_no_effect(cx, expr))
149         },
150         _ => false,
151     }
152 }
153
154 fn check_unnecessary_operation(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
155     if_chain! {
156         if let StmtKind::Semi(expr) = stmt.kind;
157         if let Some(reduced) = reduce_expression(cx, expr);
158         if !&reduced.iter().any(|e| e.span.from_expansion());
159         then {
160             if let ExprKind::Index(..) = &expr.kind {
161                 let snippet;
162                 if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) {
163                     snippet = format!("assert!({}.len() > {});", &arr, &func);
164                 } else {
165                     return;
166                 }
167                 span_lint_hir_and_then(
168                     cx,
169                     UNNECESSARY_OPERATION,
170                     expr.hir_id,
171                     stmt.span,
172                     "unnecessary operation",
173                     |diag| {
174                         diag.span_suggestion(
175                             stmt.span,
176                             "statement can be written as",
177                             snippet,
178                             Applicability::MaybeIncorrect,
179                         );
180                     },
181                 );
182             } else {
183                 let mut snippet = String::new();
184                 for e in reduced {
185                     if let Some(snip) = snippet_opt(cx, e.span) {
186                         snippet.push_str(&snip);
187                         snippet.push(';');
188                     } else {
189                         return;
190                     }
191                 }
192                 span_lint_hir_and_then(
193                     cx,
194                     UNNECESSARY_OPERATION,
195                     expr.hir_id,
196                     stmt.span,
197                     "unnecessary operation",
198                     |diag| {
199                         diag.span_suggestion(
200                             stmt.span,
201                             "statement can be reduced to",
202                             snippet,
203                             Applicability::MachineApplicable,
204                         );
205                     },
206                 );
207             }
208         }
209     }
210 }
211
212 fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec<&'a Expr<'a>>> {
213     if expr.span.from_expansion() {
214         return None;
215     }
216     match expr.kind {
217         ExprKind::Index(a, b) => Some(vec![a, b]),
218         ExprKind::Binary(ref binop, a, b) if binop.node != BinOpKind::And && binop.node != BinOpKind::Or => {
219             Some(vec![a, b])
220         },
221         ExprKind::Array(v) | ExprKind::Tup(v) => Some(v.iter().collect()),
222         ExprKind::Repeat(inner, _)
223         | ExprKind::Cast(inner, _)
224         | ExprKind::Type(inner, _)
225         | ExprKind::Unary(_, inner)
226         | ExprKind::Field(inner, _)
227         | ExprKind::AddrOf(_, _, inner)
228         | ExprKind::Box(inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
229         ExprKind::Struct(_, fields, ref base) => {
230             if has_drop(cx, cx.typeck_results().expr_ty(expr)) {
231                 None
232             } else {
233                 Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
234             }
235         },
236         ExprKind::Call(callee, args) => {
237             if let ExprKind::Path(ref qpath) = callee.kind {
238                 let res = cx.qpath_res(qpath, callee.hir_id);
239                 match res {
240                     Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..)
241                         if !has_drop(cx, cx.typeck_results().expr_ty(expr)) =>
242                     {
243                         Some(args.iter().collect())
244                     },
245                     _ => None,
246                 }
247             } else {
248                 None
249             }
250         },
251         ExprKind::Block(block, _) => {
252             if block.stmts.is_empty() {
253                 block.expr.as_ref().and_then(|e| {
254                     match block.rules {
255                         BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,
256                         BlockCheckMode::DefaultBlock => Some(vec![&**e]),
257                         // in case of compiler-inserted signaling blocks
258                         BlockCheckMode::UnsafeBlock(_) => reduce_expression(cx, e),
259                     }
260                 })
261             } else {
262                 None
263             }
264         },
265         _ => None,
266     }
267 }