Skip to content

Commit a66be9d

Browse files
committed
feat: needless_move lint
An implementation for the lint described in rust-lang#11721
1 parent 789bc73 commit a66be9d

File tree

5 files changed

+190
-0
lines changed

5 files changed

+190
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5274,6 +5274,7 @@ Released 2018-09-13
52745274
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
52755275
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
52765276
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
5277+
[`needless_move`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_move
52775278
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
52785279
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
52795280
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
496496
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
497497
crate::needless_if::NEEDLESS_IF_INFO,
498498
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
499+
crate::needless_move::NEEDLESS_MOVE_INFO,
499500
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
500501
crate::needless_pass_by_ref_mut::NEEDLESS_PASS_BY_REF_MUT_INFO,
501502
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ mod needless_else;
234234
mod needless_for_each;
235235
mod needless_if;
236236
mod needless_late_init;
237+
mod needless_move;
237238
mod needless_parens_on_range_literals;
238239
mod needless_pass_by_ref_mut;
239240
mod needless_pass_by_value;
@@ -1066,6 +1067,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10661067
});
10671068
store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
10681069
store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter));
1070+
store.register_late_pass(|_| Box::new(needless_move::NeedlessMove));
10691071
// add lints here, do not remove this comment, it's used in `new_lint`
10701072
}
10711073

clippy_lints/src/needless_move.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::sugg::DiagnosticExt;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::*;
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_middle::ty::UpvarCapture;
7+
use rustc_session::{declare_lint_pass, declare_tool_lint};
8+
use rustc_span::{BytePos, Pos};
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Checks for closures and `async` blocks where the `move` is not necessary.
13+
/// E.g. all the values are captured by value into the closure / `async` block.
14+
///
15+
/// ### Why is this bad?
16+
/// ?
17+
/// ### Example
18+
/// ```no_run
19+
/// let a = String::new();
20+
/// let closure = move || {
21+
/// drop(a);
22+
/// };
23+
/// ```
24+
/// Use instead:
25+
/// ```no_run
26+
/// let a = String::new();
27+
/// let closure = || {
28+
/// drop(a);
29+
/// };
30+
/// ```
31+
#[clippy::version = "1.75.0"]
32+
pub NEEDLESS_MOVE,
33+
pedantic,
34+
"checks for needless `move`s on closures / `async` blocks"
35+
}
36+
37+
declare_lint_pass!(NeedlessMove => [NEEDLESS_MOVE]);
38+
39+
impl NeedlessMove {
40+
fn check_closure<'tcx>(
41+
&self,
42+
cx: &LateContext<'tcx>,
43+
expr: &'tcx Expr<'tcx>,
44+
closure: &'tcx Closure<'tcx>,
45+
) {
46+
let CaptureBy::Value = closure.capture_clause else {
47+
return;
48+
};
49+
50+
let captured_places = cx.tcx.closure_captures(closure.def_id);
51+
for captured_place in captured_places {
52+
if let UpvarCapture::ByRef(_) = captured_place.info.capture_kind {
53+
return;
54+
}
55+
}
56+
57+
let span_move_kw = unimplemented!(); // FIXME: unsure of where to get the span of the `move` kw from.
58+
59+
span_lint_and_then(
60+
cx,
61+
NEEDLESS_MOVE,
62+
expr.span,
63+
"you seem to use `move`, but the `move` is unnecessary",
64+
|diag| {
65+
diag.suggest_remove_item(cx, span_move_kw, "Remove the `move`", Applicability::MachineApplicable);
66+
diag.note("There are no variables captured by reference, so everything ends up moved anyway");
67+
},
68+
);
69+
}
70+
}
71+
72+
impl<'tcx> LateLintPass<'tcx> for NeedlessMove {
73+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
74+
if expr.span.from_expansion() {
75+
return;
76+
}
77+
78+
let ExprKind::Closure(closure) = &expr.kind else {
79+
return;
80+
};
81+
82+
self.check_closure(cx, expr, closure);
83+
}
84+
}

tests/ui/needless_move.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#![warn(clippy::needless_move)]
2+
3+
#[derive(Copy, Clone)]
4+
struct Copy;
5+
6+
struct NonCopy;
7+
8+
fn with_owned<T>(_: T) {}
9+
fn with_ref<T>(_: &T) {}
10+
fn with_ref_mut<T>(_: &mut T) {}
11+
12+
fn main() {
13+
// doesn't trigger on non-move closures
14+
let a = NonCopy;
15+
let b = Copy;
16+
let closure = || {
17+
with_owned(a);
18+
with_owned(b);
19+
};
20+
21+
// owned + NonCopy
22+
let a = NonCopy;
23+
let closure = move || {
24+
with_owned(a);
25+
};
26+
27+
// owned + Copy
28+
let a = Copy;
29+
let closure = move || {
30+
with_owned(a);
31+
};
32+
33+
// ref + NonCopy
34+
let a = NonCopy;
35+
let closure = move || {
36+
with_ref(&a);
37+
};
38+
39+
// ref + Copy
40+
let a = Copy;
41+
let closure = move || {
42+
with_ref(&a);
43+
};
44+
45+
// ref mut + NonCopy
46+
let mut a = NonCopy;
47+
let closure = move || {
48+
with_ref_mut(&mut a);
49+
};
50+
51+
// ref mut + Copy
52+
let mut a = Copy;
53+
let closure = move || {
54+
with_ref_mut(&mut a);
55+
};
56+
57+
// with async
58+
59+
// doesn't trigger if not capturing with `move`
60+
let a = NonCopy;
61+
let b = Copy;
62+
let fut = async {
63+
with_owned(a);
64+
with_owned(b);
65+
};
66+
67+
// owned + non-copy
68+
let a = NonCopy;
69+
let fut = async move {
70+
with_owned(a);
71+
};
72+
73+
// owned + copy
74+
let a = Copy;
75+
let fut = async move {
76+
with_owned(a);
77+
};
78+
79+
// ref + non-copy
80+
let a = NonCopy;
81+
let fut = async move {
82+
with_ref(&a);
83+
};
84+
85+
// ref + copy
86+
let a = Copy;
87+
let fut = async move {
88+
with_ref(&a);
89+
};
90+
91+
// ref mut + non-copy
92+
let mut a = NonCopy;
93+
let fut = async move {
94+
with_ref_mut(&mut a);
95+
};
96+
97+
// ref mut + copy
98+
let mut a = Copy;
99+
let fut = async move {
100+
with_ref_mut(&mut a);
101+
};
102+
}

0 commit comments

Comments
 (0)