Skip to content

Commit e4683d2

Browse files
impl manual_noop_waker lint (#16687)
*[View all comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/rust-clippy/pull/16687)* Fixes #16639 ### Description This PR introduces a new lint manual\_noop\_waker that detects manual, empty implementations of the std::task::Wake trait. These can be replaced with std::task::Waker::noop(), which is more performant (avoids Arc allocation) and cleaner. ### Example ```rust struct MyWaker; impl Wake for MyWaker { fn wake(self: Arc<Self>) {} fn wake_by_ref(self: &Arc<Self>) {} } ```` Can be replaced with: ```rust let waker = Waker::noop(); ```` * \[x\] I've followed the [contribution guidelines](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md) * \[x\] I've added UI tests for the new lint * \[x\] I've run `cargo dev update_lints` changelog: `[manual_noop_waker]`: Added a lint to detect manual no-op Wake implementations.
2 parents fee9100 + 26740ea commit e4683d2

10 files changed

Lines changed: 154 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6797,6 +6797,7 @@ Released 2018-09-13
67976797
[`manual_midpoint`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_midpoint
67986798
[`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back
67996799
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
6800+
[`manual_noop_waker`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_noop_waker
68006801
[`manual_ok_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_err
68016802
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
68026803
[`manual_option_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_as_slice

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
311311
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
312312
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
313313
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
314+
crate::manual_noop_waker::MANUAL_NOOP_WAKER_INFO,
314315
crate::manual_option_as_slice::MANUAL_OPTION_AS_SLICE_INFO,
315316
crate::manual_pop_if::MANUAL_POP_IF_INFO,
316317
crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ mod manual_is_power_of_two;
209209
mod manual_let_else;
210210
mod manual_main_separator_str;
211211
mod manual_non_exhaustive;
212+
mod manual_noop_waker;
212213
mod manual_option_as_slice;
213214
mod manual_pop_if;
214215
mod manual_range_patterns;
@@ -865,6 +866,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
865866
Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))),
866867
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
867868
Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))),
869+
Box::new(|_| Box::new(manual_noop_waker::ManualNoopWaker)),
868870
// add late passes here, used by `cargo dev new_lint`
869871
];
870872
store.late_passes.extend(late_lints);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use clippy_utils::{is_empty_block, sym};
3+
use rustc_hir::{ImplItemKind, Item, ItemKind};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::declare_lint_pass;
6+
7+
declare_clippy_lint! {
8+
/// ### What it does
9+
/// Checks for manual implementations of `std::task::Wake` that are empty.
10+
///
11+
/// ### Why is this bad?
12+
/// `Waker::noop()` provides a more performant and cleaner way to create a
13+
/// waker that does nothing, avoiding unnecessary `Arc` allocations and
14+
/// reference count increments.
15+
///
16+
/// ### Example
17+
/// ```rust
18+
/// # use std::sync::Arc;
19+
/// # use std::task::Wake;
20+
/// struct MyWaker;
21+
/// impl Wake for MyWaker {
22+
/// fn wake(self: Arc<Self>) {}
23+
/// fn wake_by_ref(self: &Arc<Self>) {}
24+
/// }
25+
/// ```
26+
///
27+
/// Use instead:
28+
/// ```rust
29+
/// use std::task::Waker;
30+
/// let waker = Waker::noop();
31+
/// ```
32+
#[clippy::version = "1.96.0"]
33+
pub MANUAL_NOOP_WAKER,
34+
complexity,
35+
"manual implementations of noop wakers can be simplified using Waker::noop()"
36+
}
37+
38+
declare_lint_pass!(ManualNoopWaker => [MANUAL_NOOP_WAKER]);
39+
40+
impl<'tcx> LateLintPass<'tcx> for ManualNoopWaker {
41+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
42+
if let ItemKind::Impl(imp) = item.kind
43+
&& let Some(trait_ref) = imp.of_trait
44+
&& let Some(trait_id) = trait_ref.trait_ref.trait_def_id()
45+
&& cx.tcx.is_diagnostic_item(sym::Wake, trait_id)
46+
{
47+
for impl_item_ref in imp.items {
48+
let impl_item = cx
49+
.tcx
50+
.hir_node_by_def_id(impl_item_ref.owner_id.def_id)
51+
.expect_impl_item();
52+
53+
if let ImplItemKind::Fn(_, body_id) = &impl_item.kind {
54+
let body = cx.tcx.hir_body(*body_id);
55+
if !is_empty_block(body.value) {
56+
return;
57+
}
58+
}
59+
}
60+
61+
span_lint_and_help(
62+
cx,
63+
MANUAL_NOOP_WAKER,
64+
trait_ref.trait_ref.path.span,
65+
"manual implementation of a no-op waker",
66+
None,
67+
"use `std::task::Waker::noop()` instead",
68+
);
69+
}
70+
}
71+
}

clippy_lints/src/unit_types/unit_arg.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
44
use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
55
use clippy_utils::sugg::Sugg;
66
use clippy_utils::ty::expr_type_is_certain;
7-
use clippy_utils::{is_expr_default, is_from_proc_macro};
7+
use clippy_utils::{is_empty_block, is_expr_default, is_from_proc_macro};
88
use rustc_errors::Applicability;
99
use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind};
1010
use rustc_lint::LateContext;
@@ -205,20 +205,6 @@ fn is_block_with_no_expr(expr: &Expr<'_>) -> bool {
205205
matches!(expr.kind, ExprKind::Block(Block { expr: None, .. }, _))
206206
}
207207

208-
fn is_empty_block(expr: &Expr<'_>) -> bool {
209-
matches!(
210-
expr.kind,
211-
ExprKind::Block(
212-
Block {
213-
stmts: [],
214-
expr: None,
215-
..
216-
},
217-
_,
218-
)
219-
)
220-
}
221-
222208
fn fmt_stmts_and_call(
223209
cx: &LateContext<'_>,
224210
call_expr: &Expr<'_>,

clippy_utils/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,21 @@ pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Optio
311311
}
312312
}
313313

314+
/// Check if the given `Expr` is an empty block (i.e. `{}`) or not.
315+
pub fn is_empty_block(expr: &Expr<'_>) -> bool {
316+
matches!(
317+
expr.kind,
318+
ExprKind::Block(
319+
Block {
320+
stmts: [],
321+
expr: None,
322+
..
323+
},
324+
_,
325+
)
326+
)
327+
}
328+
314329
/// Checks if `expr` is an empty block or an empty tuple.
315330
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
316331
matches!(

clippy_utils/src/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ path_macros! {
133133
macro_path: PathNS::Macro,
134134
}
135135

136+
// Paths in the standard library missing a diagnostic item
137+
136138
// Paths in external crates
137139
pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt);
138140
pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt);

clippy_utils/src/sym.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ generate! {
122122
V6,
123123
VecDeque,
124124
Visitor,
125+
Wake,
125126
Waker,
126127
Weak,
127128
Wrapping,

tests/ui/manual_noop_waker.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![warn(clippy::manual_noop_waker)]
2+
use std::sync::Arc;
3+
use std::task::Wake;
4+
5+
struct PartialWaker;
6+
impl Wake for PartialWaker {
7+
//~^ ERROR: manual implementation of a no-op waker
8+
fn wake(self: Arc<Self>) {}
9+
}
10+
11+
struct MyWakerPartial;
12+
impl Wake for MyWakerPartial {
13+
//~^ manual_noop_waker
14+
fn wake(self: Arc<Self>) {}
15+
// wake_by_ref not implemented, uses default
16+
}
17+
18+
trait CustomWake {
19+
fn wake(self);
20+
}
21+
22+
impl CustomWake for () {
23+
fn wake(self) {}
24+
}
25+
26+
mod custom_module {
27+
use std::sync::Arc;
28+
29+
// Custom Wake trait that should NOT trigger the lint
30+
pub trait Wake {
31+
fn wake(self: Arc<Self>);
32+
fn wake_by_ref(self: &Arc<Self>);
33+
}
34+
35+
pub struct CustomWaker;
36+
impl Wake for CustomWaker {
37+
fn wake(self: Arc<Self>) {}
38+
fn wake_by_ref(self: &Arc<Self>) {}
39+
}
40+
}

tests/ui/manual_noop_waker.stderr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: manual implementation of a no-op waker
2+
--> tests/ui/manual_noop_waker.rs:6:6
3+
|
4+
LL | impl Wake for PartialWaker {
5+
| ^^^^
6+
|
7+
= help: use `std::task::Waker::noop()` instead
8+
= note: `-D clippy::manual-noop-waker` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::manual_noop_waker)]`
10+
11+
error: manual implementation of a no-op waker
12+
--> tests/ui/manual_noop_waker.rs:12:6
13+
|
14+
LL | impl Wake for MyWakerPartial {
15+
| ^^^^
16+
|
17+
= help: use `std::task::Waker::noop()` instead
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)