|
1 | 1 | use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
2 | 2 | use clippy_utils::source::snippet_opt;
|
3 | 3 | use clippy_utils::ty::has_drop;
|
4 |
| -use clippy_utils::{any_parent_is_automatically_derived, get_parent_node, is_lint_allowed, peel_blocks}; |
| 4 | +use clippy_utils::{any_parent_is_automatically_derived, get_parent_node, is_lint_allowed, path_to_local, peel_blocks}; |
5 | 5 | use rustc_errors::Applicability;
|
6 | 6 | use rustc_hir::def::{DefKind, Res};
|
7 | 7 | use rustc_hir::{
|
8 |
| - is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, ItemKind, Node, PatKind, Stmt, StmtKind, UnsafeSource, |
| 8 | + is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, Node, PatKind, Stmt, |
| 9 | + StmtKind, UnsafeSource, |
9 | 10 | };
|
10 | 11 | use rustc_infer::infer::TyCtxtInferExt as _;
|
11 | 12 | use rustc_lint::{LateContext, LateLintPass, LintContext};
|
12 | 13 | use rustc_middle::lint::in_external_macro;
|
13 |
| -use rustc_session::declare_lint_pass; |
| 14 | +use rustc_session::impl_lint_pass; |
| 15 | +use rustc_span::Span; |
14 | 16 | use std::ops::Deref;
|
15 | 17 |
|
16 | 18 | declare_clippy_lint! {
|
@@ -74,95 +76,125 @@ declare_clippy_lint! {
|
74 | 76 | "outer expressions with no effect"
|
75 | 77 | }
|
76 | 78 |
|
77 |
| -declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]); |
| 79 | +#[derive(Default)] |
| 80 | +pub struct NoEffect { |
| 81 | + underscore_bindings: HirIdMap<Span>, |
| 82 | + local_bindings: Vec<Vec<HirId>>, |
| 83 | +} |
| 84 | + |
| 85 | +impl_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]); |
78 | 86 |
|
79 | 87 | impl<'tcx> LateLintPass<'tcx> for NoEffect {
|
80 | 88 | fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
81 |
| - if check_no_effect(cx, stmt) { |
| 89 | + if self.check_no_effect(cx, stmt) { |
82 | 90 | return;
|
83 | 91 | }
|
84 | 92 | check_unnecessary_operation(cx, stmt);
|
85 | 93 | }
|
86 |
| -} |
87 | 94 |
|
88 |
| -fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { |
89 |
| - if let StmtKind::Semi(expr) = stmt.kind { |
90 |
| - // move `expr.span.from_expansion()` ahead |
91 |
| - if expr.span.from_expansion() { |
92 |
| - return false; |
| 95 | + fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) { |
| 96 | + self.local_bindings.push(Vec::default()); |
| 97 | + } |
| 98 | + |
| 99 | + fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) { |
| 100 | + for hir_id in self.local_bindings.pop().unwrap() { |
| 101 | + if let Some(span) = self.underscore_bindings.remove(&hir_id) { |
| 102 | + span_lint_hir( |
| 103 | + cx, |
| 104 | + NO_EFFECT_UNDERSCORE_BINDING, |
| 105 | + hir_id, |
| 106 | + span, |
| 107 | + "binding to `_` prefixed variable with no side-effect", |
| 108 | + ); |
| 109 | + } |
93 | 110 | }
|
94 |
| - let expr = peel_blocks(expr); |
| 111 | + } |
95 | 112 |
|
96 |
| - if is_operator_overridden(cx, expr) { |
97 |
| - // Return `true`, to prevent `check_unnecessary_operation` from |
98 |
| - // linting on this statement as well. |
99 |
| - return true; |
| 113 | + fn check_expr(&mut self, _: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { |
| 114 | + if let Some(def_id) = path_to_local(expr) { |
| 115 | + self.underscore_bindings.remove(&def_id); |
100 | 116 | }
|
101 |
| - if has_no_effect(cx, expr) { |
102 |
| - span_lint_hir_and_then( |
103 |
| - cx, |
104 |
| - NO_EFFECT, |
105 |
| - expr.hir_id, |
106 |
| - stmt.span, |
107 |
| - "statement with no effect", |
108 |
| - |diag| { |
109 |
| - for parent in cx.tcx.hir().parent_iter(stmt.hir_id) { |
110 |
| - if let Node::Item(item) = parent.1 |
111 |
| - && let ItemKind::Fn(..) = item.kind |
112 |
| - && let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) |
113 |
| - && let [.., final_stmt] = block.stmts |
114 |
| - && final_stmt.hir_id == stmt.hir_id |
115 |
| - { |
116 |
| - let expr_ty = cx.typeck_results().expr_ty(expr); |
117 |
| - let mut ret_ty = cx |
118 |
| - .tcx |
119 |
| - .fn_sig(item.owner_id) |
120 |
| - .instantiate_identity() |
121 |
| - .output() |
122 |
| - .skip_binder(); |
| 117 | + } |
| 118 | +} |
123 | 119 |
|
124 |
| - // Remove `impl Future<Output = T>` to get `T` |
125 |
| - if cx.tcx.ty_is_opaque_future(ret_ty) |
126 |
| - && let Some(true_ret_ty) = cx.tcx.infer_ctxt().build().get_impl_future_output_ty(ret_ty) |
| 120 | +impl NoEffect { |
| 121 | + fn check_no_effect(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { |
| 122 | + if let StmtKind::Semi(expr) = stmt.kind { |
| 123 | + // move `expr.span.from_expansion()` ahead |
| 124 | + if expr.span.from_expansion() { |
| 125 | + return false; |
| 126 | + } |
| 127 | + let expr = peel_blocks(expr); |
| 128 | + |
| 129 | + if is_operator_overridden(cx, expr) { |
| 130 | + // Return `true`, to prevent `check_unnecessary_operation` from |
| 131 | + // linting on this statement as well. |
| 132 | + return true; |
| 133 | + } |
| 134 | + if has_no_effect(cx, expr) { |
| 135 | + span_lint_hir_and_then( |
| 136 | + cx, |
| 137 | + NO_EFFECT, |
| 138 | + expr.hir_id, |
| 139 | + stmt.span, |
| 140 | + "statement with no effect", |
| 141 | + |diag| { |
| 142 | + for parent in cx.tcx.hir().parent_iter(stmt.hir_id) { |
| 143 | + if let Node::Item(item) = parent.1 |
| 144 | + && let ItemKind::Fn(..) = item.kind |
| 145 | + && let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) |
| 146 | + && let [.., final_stmt] = block.stmts |
| 147 | + && final_stmt.hir_id == stmt.hir_id |
127 | 148 | {
|
128 |
| - ret_ty = true_ret_ty; |
129 |
| - } |
| 149 | + let expr_ty = cx.typeck_results().expr_ty(expr); |
| 150 | + let mut ret_ty = cx |
| 151 | + .tcx |
| 152 | + .fn_sig(item.owner_id) |
| 153 | + .instantiate_identity() |
| 154 | + .output() |
| 155 | + .skip_binder(); |
| 156 | + |
| 157 | + // Remove `impl Future<Output = T>` to get `T` |
| 158 | + if cx.tcx.ty_is_opaque_future(ret_ty) |
| 159 | + && let Some(true_ret_ty) = |
| 160 | + cx.tcx.infer_ctxt().build().get_impl_future_output_ty(ret_ty) |
| 161 | + { |
| 162 | + ret_ty = true_ret_ty; |
| 163 | + } |
130 | 164 |
|
131 |
| - if !ret_ty.is_unit() && ret_ty == expr_ty { |
132 |
| - diag.span_suggestion( |
133 |
| - stmt.span.shrink_to_lo(), |
134 |
| - "did you mean to return it?", |
135 |
| - "return ", |
136 |
| - Applicability::MaybeIncorrect, |
137 |
| - ); |
| 165 | + if !ret_ty.is_unit() && ret_ty == expr_ty { |
| 166 | + diag.span_suggestion( |
| 167 | + stmt.span.shrink_to_lo(), |
| 168 | + "did you mean to return it?", |
| 169 | + "return ", |
| 170 | + Applicability::MaybeIncorrect, |
| 171 | + ); |
| 172 | + } |
138 | 173 | }
|
139 | 174 | }
|
140 |
| - } |
141 |
| - }, |
142 |
| - ); |
143 |
| - return true; |
144 |
| - } |
145 |
| - } else if let StmtKind::Local(local) = stmt.kind { |
146 |
| - if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id) |
147 |
| - && let Some(init) = local.init |
148 |
| - && local.els.is_none() |
149 |
| - && !local.pat.span.from_expansion() |
150 |
| - && has_no_effect(cx, init) |
151 |
| - && let PatKind::Binding(_, _, ident, _) = local.pat.kind |
152 |
| - && ident.name.to_ident_string().starts_with('_') |
153 |
| - && !any_parent_is_automatically_derived(cx.tcx, local.hir_id) |
154 |
| - { |
155 |
| - span_lint_hir( |
156 |
| - cx, |
157 |
| - NO_EFFECT_UNDERSCORE_BINDING, |
158 |
| - init.hir_id, |
159 |
| - ident.span, |
160 |
| - "binding to `_` prefixed variable with no side-effect", |
161 |
| - ); |
162 |
| - return true; |
| 175 | + }, |
| 176 | + ); |
| 177 | + return true; |
| 178 | + } |
| 179 | + } else if let StmtKind::Local(local) = stmt.kind { |
| 180 | + if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id) |
| 181 | + && let Some(init) = local.init |
| 182 | + && local.els.is_none() |
| 183 | + && !local.pat.span.from_expansion() |
| 184 | + && has_no_effect(cx, init) |
| 185 | + && let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind |
| 186 | + && ident.name.to_ident_string().starts_with('_') |
| 187 | + && !any_parent_is_automatically_derived(cx.tcx, local.hir_id) |
| 188 | + { |
| 189 | + if let Some(l) = self.local_bindings.last_mut() { |
| 190 | + l.push(hir_id); |
| 191 | + self.underscore_bindings.insert(hir_id, ident.span); |
| 192 | + } |
| 193 | + return true; |
| 194 | + } |
163 | 195 | }
|
| 196 | + false |
164 | 197 | }
|
165 |
| - false |
166 | 198 | }
|
167 | 199 |
|
168 | 200 | fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|
0 commit comments