Skip to content

Commit 3c562f9

Browse files
committed
New lint: unit_as_impl_trait
1 parent a8b1782 commit 3c562f9

12 files changed

+270
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6158,6 +6158,7 @@ Released 2018-09-13
61586158
[`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec
61596159
[`uninlined_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
61606160
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
6161+
[`unit_as_impl_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_as_impl_trait
61616162
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
61626163
[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
61636164
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
757757
crate::unicode::UNICODE_NOT_NFC_INFO,
758758
crate::uninhabited_references::UNINHABITED_REFERENCES_INFO,
759759
crate::uninit_vec::UNINIT_VEC_INFO,
760+
crate::unit_as_impl_trait::UNIT_AS_IMPL_TRAIT_INFO,
760761
crate::unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD_INFO,
761762
crate::unit_types::LET_UNIT_VALUE_INFO,
762763
crate::unit_types::UNIT_ARG_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ mod undocumented_unsafe_blocks;
368368
mod unicode;
369369
mod uninhabited_references;
370370
mod uninit_vec;
371+
mod unit_as_impl_trait;
371372
mod unit_return_expecting_ord;
372373
mod unit_types;
373374
mod unnecessary_box_returns;
@@ -980,5 +981,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
980981
store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf)));
981982
store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf)));
982983
store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap));
984+
store.register_late_pass(|_| Box::new(unit_as_impl_trait::UnitAsImplTrait));
983985
// add lints here, do not remove this comment, it's used in `new_lint`
984986
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_hir::def_id::LocalDefId;
3+
use rustc_hir::intravisit::FnKind;
4+
use rustc_hir::{Body, ExprKind, FnDecl, FnRetTy, TyKind};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::declare_lint_pass;
7+
use rustc_span::{BytePos, Span};
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
/// Checks for function whose return type is an `impl` trait
12+
/// implemented by `()`, and whose `()` return value is implicit.
13+
///
14+
/// ### Why is this bad?
15+
/// Since the required trait is implemented by `()`, adding an extra `;`
16+
/// at the end of the function last expression will compile with no
17+
/// indication that the expected value may have been silently swallowed.
18+
///
19+
/// ### Example
20+
/// ```no_run
21+
/// fn key() -> impl Eq {
22+
/// [1, 10, 2, 0].sort_unstable()
23+
/// }
24+
/// ```
25+
/// Use instead:
26+
/// ```no_run
27+
/// fn key() -> impl Eq {
28+
/// let mut arr = [1, 10, 2, 0];
29+
/// arr.sort_unstable();
30+
/// arr
31+
/// }
32+
/// ```
33+
/// or
34+
/// ```no_run
35+
/// fn key() -> impl Eq {
36+
/// [1, 10, 2, 0].sort_unstable();
37+
/// ()
38+
/// }
39+
/// ```
40+
/// if returning `()` is intentional.
41+
#[clippy::version = "1.86.0"]
42+
pub UNIT_AS_IMPL_TRAIT,
43+
suspicious,
44+
"`()` which implements the required trait might be returned unexpectedly"
45+
}
46+
47+
declare_lint_pass!(UnitAsImplTrait => [UNIT_AS_IMPL_TRAIT]);
48+
49+
impl<'tcx> LateLintPass<'tcx> for UnitAsImplTrait {
50+
fn check_fn(
51+
&mut self,
52+
cx: &LateContext<'tcx>,
53+
_: FnKind<'tcx>,
54+
decl: &'tcx FnDecl<'tcx>,
55+
body: &'tcx Body<'tcx>,
56+
span: Span,
57+
_: LocalDefId,
58+
) {
59+
if !span.from_expansion()
60+
&& let FnRetTy::Return(ty) = decl.output
61+
&& let TyKind::OpaqueDef(_) = ty.kind
62+
&& cx.typeck_results().expr_ty(body.value).is_unit()
63+
&& let ExprKind::Block(block, None) = body.value.kind
64+
&& block.expr.is_none_or(|e| !matches!(e.kind, ExprKind::Tup([])))
65+
{
66+
let block_end_span = block.span.with_hi(block.span.hi() - BytePos(1)).shrink_to_hi();
67+
68+
span_lint_and_then(
69+
cx,
70+
UNIT_AS_IMPL_TRAIT,
71+
ty.span,
72+
"this function returns `()` which implements the required trait",
73+
|diag| {
74+
if let Some(expr) = block.expr {
75+
diag.span_note(expr.span, "this expression evaluates to `()`")
76+
.span_help(
77+
expr.span.shrink_to_hi(),
78+
"consider being explicit, and terminate the body with `()`",
79+
);
80+
} else if let Some(last) = block.stmts.last() {
81+
diag.span_note(last.span, "this statement evaluates to `()` because it ends with `;`")
82+
.span_note(block.span, "therefore the function body evaluates to `()`")
83+
.span_help(
84+
block_end_span,
85+
"if this is intentional, consider being explicit, and terminate the body with `()`",
86+
);
87+
} else {
88+
diag.span_note(block.span, "the empty body evaluates to `()`")
89+
.span_help(block_end_span, "consider being explicit and use `()` in the body");
90+
}
91+
},
92+
);
93+
}
94+
}
95+
}

tests/ui/crashes/ice-11422.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::implied_bounds_in_impls)]
2+
#![allow(clippy::unit_as_impl_trait)]
23

34
use std::fmt::Debug;
45
use std::ops::*;

tests/ui/crashes/ice-11422.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::implied_bounds_in_impls)]
2+
#![allow(clippy::unit_as_impl_trait)]
23

34
use std::fmt::Debug;
45
use std::ops::*;

tests/ui/crashes/ice-11422.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this bound is already specified as the supertrait of `PartialOrd`
2-
--> tests/ui/crashes/ice-11422.rs:6:33
2+
--> tests/ui/crashes/ice-11422.rs:7:33
33
|
44
LL | fn r#gen() -> impl PartialOrd + PartialEq + Debug {}
55
| ^^^^^^^^^

tests/ui/implied_bounds_in_impls.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::implied_bounds_in_impls)]
2-
#![allow(dead_code)]
2+
#![allow(dead_code, clippy::unit_as_impl_trait)]
33
#![feature(impl_trait_in_assoc_type, type_alias_impl_trait)]
44

55
use std::ops::{Deref, DerefMut};

tests/ui/implied_bounds_in_impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::implied_bounds_in_impls)]
2-
#![allow(dead_code)]
2+
#![allow(dead_code, clippy::unit_as_impl_trait)]
33
#![feature(impl_trait_in_assoc_type, type_alias_impl_trait)]
44

55
use std::ops::{Deref, DerefMut};

tests/ui/new_ret_no_self.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![feature(type_alias_impl_trait)]
22
#![warn(clippy::new_ret_no_self)]
3-
#![allow(dead_code)]
3+
#![allow(dead_code, clippy::unit_as_impl_trait)]
44

55
fn main() {}
66

0 commit comments

Comments
 (0)