Skip to content

Commit 508183e

Browse files
Add block_scrutinee lint
1 parent 84bb31a commit 508183e

9 files changed

Lines changed: 292 additions & 0 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6561,6 +6561,7 @@ Released 2018-09-13
65616561
[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
65626562
[`block_in_if_condition_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_expr
65636563
[`block_in_if_condition_stmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_stmt
6564+
[`block_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_scrutinee
65646565
[`blocks_in_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_conditions
65656566
[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
65666567
[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::source::snippet;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, ExprKind};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::declare_lint_pass;
7+
use rustc_span::edition::Edition;
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
/// Warns when a match, if let, or while let scrutinee is wrapped in a block.
12+
/// This lint only triggers on the 2021 edition and older.
13+
///
14+
/// ### Why is this bad?
15+
/// It is unusual to write `{ expr }` when you could just have written
16+
/// `expr`, and it is unlikely that anyone would write that for any reason
17+
/// other than wanting temporaries in `expr` to be dropped before executing
18+
/// the body of the `match`/`if let`/`while` statement. However, prior to
19+
/// the 2024 edition, wrapping the scrutinee in a block did not drop
20+
/// temporaries before the body executes.
21+
///
22+
/// ### Example
23+
/// ```rust,ignore
24+
/// if let Some(x) = { my_function() } { .. }
25+
/// ```
26+
#[clippy::version = "1.80.0"]
27+
pub BLOCK_SCRUTINEE,
28+
correctness,
29+
"warns when the scrutinee is wrapped in a block in older editions"
30+
}
31+
32+
declare_lint_pass!(BlockScrutinee => [BLOCK_SCRUTINEE]);
33+
34+
impl<'tcx> LateLintPass<'tcx> for BlockScrutinee {
35+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
36+
if cx.tcx.sess.edition() >= Edition::Edition2024 {
37+
return;
38+
}
39+
40+
let scrutinee = match expr.kind {
41+
ExprKind::Match(scrutinee, _, _) => scrutinee,
42+
ExprKind::Let(let_expr) => let_expr.init,
43+
_ => return,
44+
};
45+
46+
if let ExprKind::Block(block, _) = scrutinee.kind
47+
&& block.stmts.is_empty()
48+
&& let Some(inner_expr) = block.expr
49+
{
50+
let inner_snippet = snippet(cx, inner_expr.span, "..");
51+
52+
span_lint_and_then(
53+
cx,
54+
BLOCK_SCRUTINEE,
55+
scrutinee.span,
56+
"temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement",
57+
|diag| {
58+
diag.note("this behavior is specific to Rust editions prior to 2024");
59+
diag.note("in Rust 2024, temporaries in a block scrutinee drop immediately before the match arm or block body");
60+
diag.help("if you want the temporaries to be dropped early, you can update your `Cargo.toml` to the 2024 edition");
61+
62+
diag.span_suggestion(
63+
scrutinee.span,
64+
"remove the block to yield the same behavior but with cleaner code",
65+
inner_snippet.to_string(),
66+
Applicability::MachineApplicable,
67+
);
68+
69+
diag.span_suggestion(
70+
scrutinee.span,
71+
"if you intended to drop temporaries early, move the expression to a separate local binding",
72+
format!("{{ let res = {inner_snippet}; res }}"),
73+
Applicability::MaybeIncorrect,
74+
);
75+
},
76+
);
77+
}
78+
}
79+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
3333
crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO,
3434
crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO,
3535
crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO,
36+
crate::block_scrutinee::BLOCK_SCRUTINEE_INFO,
3637
crate::blocks_in_conditions::BLOCKS_IN_CONDITIONS_INFO,
3738
crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO,
3839
crate::bool_comparison::BOOL_COMPARISON_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod assigning_clones;
7272
mod async_yields_async;
7373
mod attrs;
7474
mod await_holding_invalid;
75+
mod block_scrutinee;
7576
mod blocks_in_conditions;
7677
mod bool_assert_comparison;
7778
mod bool_comparison;
@@ -869,6 +870,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
869870
Box::new(move |_| Box::new(manual_noop_waker::ManualNoopWaker::new(conf))),
870871
Box::new(|_| Box::new(byte_char_slices::ByteCharSlice)),
871872
Box::new(|_| Box::new(manual_assert_eq::ManualAssertEq)),
873+
Box::new(|_| Box::new(block_scrutinee::BlockScrutinee)),
872874
// add late passes here, used by `cargo dev new_lint`
873875
];
874876
store.late_passes.extend(late_lints);

tests/ui/block_scrutinee.1.fixed

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//@ edition: 2021
2+
#![warn(clippy::block_scrutinee)]
3+
#![allow(clippy::blocks_in_conditions)]
4+
#![allow(clippy::let_and_return)]
5+
6+
fn my_function() -> Option<i32> {
7+
Some(1)
8+
}
9+
10+
fn main() {
11+
if let Some(x) = my_function() {
12+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
13+
let _ = x;
14+
}
15+
16+
match my_function() {
17+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
18+
Some(1) => println!("one"),
19+
Some(_) => println!("other"),
20+
None => println!("none"),
21+
}
22+
23+
let mut v = vec![1, 2, 3];
24+
while let Some(x) = v.pop() {
25+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
26+
let _ = x;
27+
}
28+
29+
if let Some(x) = my_function() {
30+
let _ = x;
31+
}
32+
33+
if let Some(x) = {
34+
let _y = 2;
35+
my_function()
36+
} {
37+
let _ = x;
38+
}
39+
}

tests/ui/block_scrutinee.2.fixed

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//@ edition: 2021
2+
#![warn(clippy::block_scrutinee)]
3+
#![allow(clippy::blocks_in_conditions)]
4+
#![allow(clippy::let_and_return)]
5+
6+
fn my_function() -> Option<i32> {
7+
Some(1)
8+
}
9+
10+
fn main() {
11+
if let Some(x) = { let res = my_function(); res } {
12+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
13+
let _ = x;
14+
}
15+
16+
match { let res = my_function(); res } {
17+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
18+
Some(1) => println!("one"),
19+
Some(_) => println!("other"),
20+
None => println!("none"),
21+
}
22+
23+
let mut v = vec![1, 2, 3];
24+
while let Some(x) = { let res = v.pop(); res } {
25+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
26+
let _ = x;
27+
}
28+
29+
if let Some(x) = my_function() {
30+
let _ = x;
31+
}
32+
33+
if let Some(x) = {
34+
let _y = 2;
35+
my_function()
36+
} {
37+
let _ = x;
38+
}
39+
}

tests/ui/block_scrutinee.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//@ edition: 2021
2+
#![warn(clippy::block_scrutinee)]
3+
#![allow(clippy::blocks_in_conditions)]
4+
#![allow(clippy::let_and_return)]
5+
6+
fn my_function() -> Option<i32> {
7+
Some(1)
8+
}
9+
10+
fn main() {
11+
if let Some(x) = { my_function() } {
12+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
13+
let _ = x;
14+
}
15+
16+
match { my_function() } {
17+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
18+
Some(1) => println!("one"),
19+
Some(_) => println!("other"),
20+
None => println!("none"),
21+
}
22+
23+
let mut v = vec![1, 2, 3];
24+
while let Some(x) = { v.pop() } {
25+
//~^ ERROR: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
26+
let _ = x;
27+
}
28+
29+
if let Some(x) = my_function() {
30+
let _ = x;
31+
}
32+
33+
if let Some(x) = {
34+
let _y = 2;
35+
my_function()
36+
} {
37+
let _ = x;
38+
}
39+
}

tests/ui/block_scrutinee.stderr

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
error: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
2+
--> tests/ui/block_scrutinee.rs:11:22
3+
|
4+
LL | if let Some(x) = { my_function() } {
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: this behavior is specific to Rust editions prior to 2024
8+
= note: in Rust 2024, temporaries in a block scrutinee drop immediately before the match arm or block body
9+
= help: if you want the temporaries to be dropped early, you can update your `Cargo.toml` to the 2024 edition
10+
= note: `-D clippy::block-scrutinee` implied by `-D warnings`
11+
= help: to override `-D warnings` add `#[allow(clippy::block_scrutinee)]`
12+
help: remove the block to yield the same behavior but with cleaner code
13+
|
14+
LL - if let Some(x) = { my_function() } {
15+
LL + if let Some(x) = my_function() {
16+
|
17+
help: if you intended to drop temporaries early, move the expression to a separate local binding
18+
|
19+
LL - if let Some(x) = { my_function() } {
20+
LL + if let Some(x) = { let res = my_function(); res } {
21+
|
22+
23+
error: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
24+
--> tests/ui/block_scrutinee.rs:16:11
25+
|
26+
LL | match { my_function() } {
27+
| ^^^^^^^^^^^^^^^^^
28+
|
29+
= note: this behavior is specific to Rust editions prior to 2024
30+
= note: in Rust 2024, temporaries in a block scrutinee drop immediately before the match arm or block body
31+
= help: if you want the temporaries to be dropped early, you can update your `Cargo.toml` to the 2024 edition
32+
help: remove the block to yield the same behavior but with cleaner code
33+
|
34+
LL - match { my_function() } {
35+
LL + match my_function() {
36+
|
37+
help: if you intended to drop temporaries early, move the expression to a separate local binding
38+
|
39+
LL - match { my_function() } {
40+
LL + match { let res = my_function(); res } {
41+
|
42+
43+
error: temporary values in this block-wrapped scrutinee will not be dropped until the end of the statement
44+
--> tests/ui/block_scrutinee.rs:24:25
45+
|
46+
LL | while let Some(x) = { v.pop() } {
47+
| ^^^^^^^^^^^
48+
|
49+
= note: this behavior is specific to Rust editions prior to 2024
50+
= note: in Rust 2024, temporaries in a block scrutinee drop immediately before the match arm or block body
51+
= help: if you want the temporaries to be dropped early, you can update your `Cargo.toml` to the 2024 edition
52+
help: remove the block to yield the same behavior but with cleaner code
53+
|
54+
LL - while let Some(x) = { v.pop() } {
55+
LL + while let Some(x) = v.pop() {
56+
|
57+
help: if you intended to drop temporaries early, move the expression to a separate local binding
58+
|
59+
LL - while let Some(x) = { v.pop() } {
60+
LL + while let Some(x) = { let res = v.pop(); res } {
61+
|
62+
63+
error: aborting due to 3 previous errors
64+

tests/ui/block_scrutinee_2024.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ edition: 2024
2+
//@ check-pass
3+
#![warn(clippy::block_scrutinee)]
4+
#![allow(clippy::blocks_in_conditions)]
5+
6+
fn my_function() -> Option<i32> {
7+
Some(1)
8+
}
9+
10+
fn main() {
11+
// This should NOT trigger the lint on the 2024 edition
12+
if let Some(x) = { my_function() } {
13+
let _ = x;
14+
}
15+
16+
// This should NOT trigger the lint on the 2024 edition
17+
match { my_function() } {
18+
Some(1) => println!("one"),
19+
Some(_) => println!("other"),
20+
None => println!("none"),
21+
}
22+
23+
// This should NOT trigger the lint on the 2024 edition
24+
let mut v = vec![1, 2, 3];
25+
while let Some(x) = { v.pop() } {
26+
let _ = x;
27+
}
28+
}

0 commit comments

Comments
 (0)