Skip to content

Commit 418179d

Browse files
committed
new lint: Unintentional return of unit from closures expecting Ord
This lint catches cases where the last statement of a closure expecting an instance of Ord has a trailing semi-colon. It compiles since the closure ends up return () which also implements Ord but causes unexpected results in cases such as sort_by_key. Fixes #5080
1 parent 0e5e2c4 commit 418179d

File tree

7 files changed

+106
-2
lines changed

7 files changed

+106
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,7 @@ Released 2018-09-13
15031503
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
15041504
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
15051505
[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
1506+
[`unintentional_unit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#unintentional_unit_return
15061507
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
15071508
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
15081509
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are 361 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are 362 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1111

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ pub mod trivially_copy_pass_by_ref;
307307
pub mod try_err;
308308
pub mod types;
309309
pub mod unicode;
310+
pub mod unintentional_unit_return;
310311
pub mod unsafe_removed_from_name;
311312
pub mod unused_io_amount;
312313
pub mod unused_self;
@@ -812,6 +813,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
812813
&unicode::NON_ASCII_LITERAL,
813814
&unicode::UNICODE_NOT_NFC,
814815
&unicode::ZERO_WIDTH_SPACE,
816+
&unintentional_unit_return::UNINTENTIONAL_UNIT_RETURN,
815817
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
816818
&unused_io_amount::UNUSED_IO_AMOUNT,
817819
&unused_self::UNUSED_SELF,
@@ -1021,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10211023
store.register_late_pass(|| box wildcard_imports::WildcardImports);
10221024
store.register_early_pass(|| box macro_use::MacroUseImports);
10231025
store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
1026+
store.register_late_pass(|| box unintentional_unit_return::UnintentionalUnitReturn);
10241027

10251028
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10261029
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1670,6 +1673,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16701673
LintId::of(&mutex_atomic::MUTEX_INTEGER),
16711674
LintId::of(&needless_borrow::NEEDLESS_BORROW),
16721675
LintId::of(&path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
1676+
LintId::of(&unintentional_unit_return::UNINTENTIONAL_UNIT_RETURN),
16731677
LintId::of(&use_self::USE_SELF),
16741678
]);
16751679
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use crate::utils::span_lint;
2+
use if_chain::if_chain;
3+
use rustc_data_structures::fx::FxHashMap;
4+
use rustc_hir::{Expr, ExprKind, StmtKind};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// **What it does:** Checks for functions that expect closures of type
10+
/// Fn(...) -> Ord where the implemented closure has a semi-colon
11+
/// at the end of the last statement.
12+
///
13+
/// **Why is this bad?** Likely the semi-colon is unintentional which
14+
/// returns () instead of the result of last statement. Since () implements Ord
15+
/// it doesn't cause a compilation error
16+
///
17+
/// **Known problems:** If returning unit is intentional, then there is no
18+
/// way of specifying this without triggering needless_return lint
19+
///
20+
/// **Example:**
21+
///
22+
/// ```rust
23+
/// let mut twins = vec!((1,1), (2,2));
24+
/// twins.sort_by_key(|x| { x.1; });
25+
/// ```
26+
pub UNINTENTIONAL_UNIT_RETURN,
27+
nursery,
28+
"fn arguments of type Fn(...) -> Once having last statements with a semi-colon, suggesting to remove the semi-colon if it is unintentional."
29+
}
30+
31+
declare_lint_pass!(UnintentionalUnitReturn => [UNINTENTIONAL_UNIT_RETURN]);
32+
33+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnintentionalUnitReturn {
34+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
35+
// Whitelist of function_name -> [argument_index] which are checked
36+
let mut func_args_to_lint = FxHashMap::default();
37+
func_args_to_lint.insert("sort_by_key".to_string(), vec![1]);
38+
39+
if_chain! {
40+
if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind;
41+
if let Some(arg_indices) = func_args_to_lint.get(&path.ident.to_string());
42+
43+
then {
44+
for i in arg_indices {
45+
if_chain! {
46+
if *i < args.len();
47+
if let ExprKind::Closure(_, _fn_decl, body_id, _span, _) = args[*i].kind;
48+
let body = cx.tcx.hir().body(body_id);
49+
if let ExprKind::Block(block, _) = body.value.kind;
50+
if let Some(stmt) = block.stmts.last();
51+
if let StmtKind::Semi(_) = stmt.kind;
52+
then {
53+
//TODO : Maybe only filter the closures where the last statement return type also is an unit
54+
span_lint(cx,
55+
UNINTENTIONAL_UNIT_RETURN,
56+
stmt.span,
57+
"Semi-colon on the last line of this closure returns \
58+
the unit type which also implements Ord.");
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 361] = [
9+
pub const ALL_LINTS: [Lint; 362] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -2191,6 +2191,13 @@ pub const ALL_LINTS: [Lint; 361] = [
21912191
deprecation: None,
21922192
module: "methods",
21932193
},
2194+
Lint {
2195+
name: "unintentional_unit_return",
2196+
group: "nursery",
2197+
desc: "fn arguments of type Fn(...) -> Once having last statements with a semi-colon, suggesting to remove the semi-colon if it is unintentional.",
2198+
deprecation: None,
2199+
module: "unintentional_unit_return",
2200+
},
21942201
Lint {
21952202
name: "unit_arg",
21962203
group: "complexity",

tests/ui/unintentional_unit_return.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![warn(clippy::unintentional_unit_return)]
2+
3+
struct Struct {
4+
field: isize,
5+
}
6+
7+
fn double(i: isize) -> isize {
8+
i * 2
9+
}
10+
11+
fn main() {
12+
let mut structs = vec![Struct { field: 2 }, Struct { field: 1 }];
13+
structs.sort_by_key(|s| {
14+
double(s.field);
15+
});
16+
structs.sort_by_key(|s| double(s.field));
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: Semi-colon on the last line of this closure returns the unit type which also implements Ord.
2+
--> $DIR/unintentional_unit_return.rs:14:9
3+
|
4+
LL | double(s.field);
5+
| ^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::unintentional-unit-return` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)