Skip to content

Commit 650e0c8

Browse files
authored
Fix shadow_unrelated's behaviour with closures (rust-lang#13677)
Fixes rust-lang/rust-clippy#10780 We correctly no longer give a warning when a closure is passed to a method, where one of the arguments to that method uses the variable which would be shadowed by an argument to that closure. Uses is defined loosely as any expression used in the calling expression mentions the shadowee binding (except for the closure itself): ```rust #![deny(clippy::shadow_unrelated)] let x = Some(1); let y = x.map(|x| x + 1); ``` will now succeed. See linebender/xilem#745 - without this change, all of the `expect(shadow_unrelated)` in the repository are met; with it, none of them are. changelog: [`shadow_unrelated`]: Don't treat closures arguments as unrelated when the calling function uses them
2 parents af1f78a + d1cab08 commit 650e0c8

File tree

3 files changed

+89
-9
lines changed

3 files changed

+89
-9
lines changed

clippy_lints/src/shadow.rs

+50-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::span_lint_and_then;
4+
use clippy_utils::path_to_local_id;
25
use clippy_utils::source::snippet;
3-
use clippy_utils::visitors::is_local_used;
6+
use clippy_utils::visitors::{Descend, Visitable, for_each_expr};
47
use rustc_data_structures::fx::FxHashMap;
58
use rustc_hir::def::Res;
69
use rustc_hir::def_id::LocalDefId;
@@ -175,17 +178,39 @@ fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second
175178
false
176179
}
177180

181+
/// Checks if the given local is used, except for in child expression of `except`.
182+
///
183+
/// This is a version of [`is_local_used`](clippy_utils::visitors::is_local_used), used to
184+
/// implement the fix for <https://github.com/rust-lang/rust-clippy/issues/10780>.
185+
pub fn is_local_used_except<'tcx>(
186+
cx: &LateContext<'tcx>,
187+
visitable: impl Visitable<'tcx>,
188+
id: HirId,
189+
except: Option<HirId>,
190+
) -> bool {
191+
for_each_expr(cx, visitable, |e| {
192+
if except.is_some_and(|it| it == e.hir_id) {
193+
ControlFlow::Continue(Descend::No)
194+
} else if path_to_local_id(e, id) {
195+
ControlFlow::Break(())
196+
} else {
197+
ControlFlow::Continue(Descend::Yes)
198+
}
199+
})
200+
.is_some()
201+
}
202+
178203
fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span) {
179204
let (lint, msg) = match find_init(cx, pat.hir_id) {
180-
Some(expr) if is_self_shadow(cx, pat, expr, shadowed) => {
205+
Some((expr, _)) if is_self_shadow(cx, pat, expr, shadowed) => {
181206
let msg = format!(
182207
"`{}` is shadowed by itself in `{}`",
183208
snippet(cx, pat.span, "_"),
184209
snippet(cx, expr.span, "..")
185210
);
186211
(SHADOW_SAME, msg)
187212
},
188-
Some(expr) if is_local_used(cx, expr, shadowed) => {
213+
Some((expr, except)) if is_local_used_except(cx, expr, shadowed, except) => {
189214
let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
190215
(SHADOW_REUSE, msg)
191216
},
@@ -232,15 +257,32 @@ fn is_self_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, mut expr: &Expr<'_>, hir_
232257

233258
/// Finds the "init" expression for a pattern: `let <pat> = <init>;` (or `if let`) or
234259
/// `match <init> { .., <pat> => .., .. }`
235-
fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
236-
for (_, node) in cx.tcx.hir().parent_iter(hir_id) {
260+
///
261+
/// For closure arguments passed to a method call, returns the method call, and the `HirId` of the
262+
/// closure (which will later be skipped). This is for <https://github.com/rust-lang/rust-clippy/issues/10780>
263+
fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<(&'tcx Expr<'tcx>, Option<HirId>)> {
264+
for (hir_id, node) in cx.tcx.hir().parent_iter(hir_id) {
237265
let init = match node {
238-
Node::Arm(_) | Node::Pat(_) => continue,
266+
Node::Arm(_) | Node::Pat(_) | Node::Param(_) => continue,
239267
Node::Expr(expr) => match expr.kind {
240-
ExprKind::Match(e, _, _) | ExprKind::Let(&LetExpr { init: e, .. }) => Some(e),
268+
ExprKind::Match(e, _, _) | ExprKind::Let(&LetExpr { init: e, .. }) => Some((e, None)),
269+
// If we're a closure argument, then a parent call is also an associated item.
270+
ExprKind::Closure(_) => {
271+
if let Some((_, node)) = cx.tcx.hir().parent_iter(hir_id).next() {
272+
match node {
273+
Node::Expr(expr) => match expr.kind {
274+
ExprKind::MethodCall(_, _, _, _) | ExprKind::Call(_, _) => Some((expr, Some(hir_id))),
275+
_ => None,
276+
},
277+
_ => None,
278+
}
279+
} else {
280+
None
281+
}
282+
},
241283
_ => None,
242284
},
243-
Node::LetStmt(local) => local.init,
285+
Node::LetStmt(local) => local.init.map(|init| (init, None)),
244286
_ => None,
245287
};
246288
return init;

tests/ui/shadow.rs

+14
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,18 @@ fn ice_8748() {
119119
}];
120120
}
121121

122+
// https://github.com/rust-lang/rust-clippy/issues/10780
123+
fn shadow_closure() {
124+
// These are not shadow_unrelated; but they are correctly shadow_reuse
125+
let x = Some(1);
126+
#[allow(clippy::shadow_reuse)]
127+
let y = x.map(|x| x + 1);
128+
let z = x.map(|x| x + 1);
129+
let a: Vec<Option<u8>> = [100u8, 120, 140]
130+
.iter()
131+
.map(|i| i.checked_mul(2))
132+
.map(|i| i.map(|i| i - 10))
133+
.collect();
134+
}
135+
122136
fn main() {}

tests/ui/shadow.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,29 @@ note: previous binding is here
280280
LL | let x = 1;
281281
| ^
282282

283-
error: aborting due to 23 previous errors
283+
error: `x` is shadowed
284+
--> tests/ui/shadow.rs:128:20
285+
|
286+
LL | let z = x.map(|x| x + 1);
287+
| ^
288+
|
289+
note: previous binding is here
290+
--> tests/ui/shadow.rs:125:9
291+
|
292+
LL | let x = Some(1);
293+
| ^
294+
295+
error: `i` is shadowed
296+
--> tests/ui/shadow.rs:132:25
297+
|
298+
LL | .map(|i| i.map(|i| i - 10))
299+
| ^
300+
|
301+
note: previous binding is here
302+
--> tests/ui/shadow.rs:132:15
303+
|
304+
LL | .map(|i| i.map(|i| i - 10))
305+
| ^
306+
307+
error: aborting due to 25 previous errors
284308

0 commit comments

Comments
 (0)