Skip to content

Commit 7386856

Browse files
committed
Auto merge of #12172 - samueltardieu:issue-12166, r=Alexendoo
no_effect_underscore_binding: _ prefixed variables can be used Prefixing a variable with a `_` does not mean that it will not be used. If such a variable is used later, do not warn about the fact that its initialization does not have a side effect as this is fine. changelog: [`no_effect_underscore_binding`]: warn only if variable is unused Fix #12166
2 parents fe3e682 + 6267b6c commit 7386856

File tree

3 files changed

+109
-75
lines changed

3 files changed

+109
-75
lines changed

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
719719
store.register_late_pass(|_| Box::new(needless_update::NeedlessUpdate));
720720
store.register_late_pass(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
721721
store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef));
722-
store.register_late_pass(|_| Box::new(no_effect::NoEffect));
722+
store.register_late_pass(|_| Box::<no_effect::NoEffect>::default());
723723
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
724724
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
725725
store.register_late_pass(move |_| {

clippy_lints/src/no_effect.rs

+106-74
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
22
use clippy_utils::source::snippet_opt;
33
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};
55
use rustc_errors::Applicability;
66
use rustc_hir::def::{DefKind, Res};
77
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,
910
};
1011
use rustc_infer::infer::TyCtxtInferExt as _;
1112
use rustc_lint::{LateContext, LateLintPass, LintContext};
1213
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;
1416
use std::ops::Deref;
1517

1618
declare_clippy_lint! {
@@ -74,95 +76,125 @@ declare_clippy_lint! {
7476
"outer expressions with no effect"
7577
}
7678

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]);
7886

7987
impl<'tcx> LateLintPass<'tcx> for NoEffect {
8088
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) {
8290
return;
8391
}
8492
check_unnecessary_operation(cx, stmt);
8593
}
86-
}
8794

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+
}
93110
}
94-
let expr = peel_blocks(expr);
111+
}
95112

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);
100116
}
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+
}
123119

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
127148
{
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+
}
130164

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+
}
138173
}
139174
}
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+
}
163195
}
196+
false
164197
}
165-
false
166198
}
167199

168200
fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {

tests/ui/no_effect.rs

+2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ fn main() {
181181
//~^ ERROR: binding to `_` prefixed variable with no side-effect
182182
let _cat = [2, 4, 6, 8][2];
183183
//~^ ERROR: binding to `_` prefixed variable with no side-effect
184+
let _issue_12166 = 42;
185+
let underscore_variable_above_can_be_used_dont_lint = _issue_12166;
184186

185187
#[allow(clippy::no_effect)]
186188
0;

0 commit comments

Comments
 (0)