Skip to content

Commit a51cadf

Browse files
committed
fix: unused_unit suggests wrongly when unit never type fallback
1 parent ac88357 commit a51cadf

File tree

6 files changed

+167
-82
lines changed

6 files changed

+167
-82
lines changed

clippy_lints/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
771771
store.register_early_pass(|| Box::new(formatting::Formatting));
772772
store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints));
773773
store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall));
774-
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
774+
store.register_early_pass(move || Box::new(unused_unit::UnusedUnit::new(conf)));
775+
store.register_late_pass(move |_| Box::new(unused_unit::UnusedUnit::new(conf)));
775776
store.register_late_pass(|_| Box::new(returns::Return));
776777
store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf)));
777778
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));

clippy_lints/src/unused_unit.rs

Lines changed: 106 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1+
use clippy_config::Conf;
12
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::msrvs::{self, Msrv};
24
use clippy_utils::source::{SpanRangeExt, position_before_rarrow};
3-
use rustc_ast::visit::FnKind;
4-
use rustc_ast::{ClosureBinder, ast};
5+
use clippy_utils::{is_never_expr, is_unit_expr};
6+
use rustc_ast::{Block, StmtKind};
57
use rustc_errors::Applicability;
6-
use rustc_lint::{EarlyContext, EarlyLintPass};
7-
use rustc_session::declare_lint_pass;
8-
use rustc_span::{BytePos, Span};
8+
use rustc_hir::def_id::LocalDefId;
9+
use rustc_hir::intravisit::FnKind;
10+
use rustc_hir::{
11+
AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, Node, PolyTraitRef, Term,
12+
Ty, TyKind,
13+
};
14+
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
15+
use rustc_session::impl_lint_pass;
16+
use rustc_span::{BytePos, Span, sym};
917

1018
declare_clippy_lint! {
1119
/// ### What it does
@@ -32,29 +40,101 @@ declare_clippy_lint! {
3240
"needless unit expression"
3341
}
3442

35-
declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
43+
pub struct UnusedUnit {
44+
msrv: Msrv,
45+
}
3646

37-
impl EarlyLintPass for UnusedUnit {
38-
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
39-
if let ast::FnRetTy::Ty(ref ty) = kind.decl().output
40-
&& let ast::TyKind::Tup(ref vals) = ty.kind
41-
&& vals.is_empty()
42-
&& !ty.span.from_expansion()
43-
&& get_def(span) == get_def(ty.span)
47+
impl_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
48+
49+
impl UnusedUnit {
50+
pub fn new(conf: &'static Conf) -> Self {
51+
Self { msrv: conf.msrv }
52+
}
53+
}
54+
55+
impl<'tcx> LateLintPass<'tcx> for UnusedUnit {
56+
fn check_fn(
57+
&mut self,
58+
cx: &LateContext<'tcx>,
59+
kind: FnKind<'tcx>,
60+
decl: &'tcx FnDecl<'tcx>,
61+
body: &'tcx Body<'tcx>,
62+
span: Span,
63+
def_id: LocalDefId,
64+
) {
65+
if let FnRetTy::Return(hir_ty) = decl.output
66+
&& is_unit_ty(hir_ty)
67+
&& !hir_ty.span.from_expansion()
68+
&& get_def(span) == get_def(hir_ty.span)
4469
{
4570
// implicit types in closure signatures are forbidden when `for<...>` is present
46-
if let FnKind::Closure(&ClosureBinder::For { .. }, ..) = kind {
71+
if let FnKind::Closure = kind
72+
&& let Node::Expr(expr) = cx.tcx.hir_node_by_def_id(def_id)
73+
&& let ExprKind::Closure(closure) = expr.kind
74+
&& !closure.bound_generic_params.is_empty()
75+
{
4776
return;
4877
}
4978

50-
lint_unneeded_unit_return(cx, ty, span);
79+
// unit never type fallback is no longer supported since Rust 2024. For more information,
80+
// see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/never-type-fallback.html>
81+
if self.msrv.meets(cx, msrvs::UNIT_NEVER_TYPE_FALLBACK)
82+
&& let ExprKind::Block(block, _) = body.value.kind
83+
&& let Some(expr) = block.expr
84+
&& is_never_expr(cx, expr).is_some()
85+
{
86+
return;
87+
}
88+
89+
lint_unneeded_unit_return(cx, hir_ty.span, span);
5190
}
5291
}
5392

54-
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
55-
if let Some(stmt) = block.stmts.last()
56-
&& let ast::StmtKind::Expr(ref expr) = stmt.kind
93+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
94+
if let ExprKind::Ret(Some(expr)) | ExprKind::Break(_, Some(expr)) = expr.kind
5795
&& is_unit_expr(expr)
96+
&& !expr.span.from_expansion()
97+
{
98+
span_lint_and_sugg(
99+
cx,
100+
UNUSED_UNIT,
101+
expr.span,
102+
"unneeded `()`",
103+
"remove the `()`",
104+
String::new(),
105+
Applicability::MachineApplicable,
106+
);
107+
}
108+
}
109+
110+
fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) {
111+
let segments = &poly.trait_ref.path.segments;
112+
113+
if segments.len() == 1
114+
&& ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str())
115+
&& let Some(args) = segments[0].args
116+
&& args.parenthesized == GenericArgsParentheses::ParenSugar
117+
&& let constraints = &args.constraints
118+
&& constraints.len() == 1
119+
&& constraints[0].ident.name == sym::Output
120+
&& let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind
121+
&& args.span_ext.hi() != poly.span.hi()
122+
&& !hir_ty.span.from_expansion()
123+
&& is_unit_ty(hir_ty)
124+
{
125+
lint_unneeded_unit_return(cx, hir_ty.span, poly.span);
126+
}
127+
}
128+
}
129+
130+
impl EarlyLintPass for UnusedUnit {
131+
/// Check for unit expressions in blocks. This is left in the early pass because some macros
132+
/// expand its inputs as-is, making it invisible to the late pass. See #4076.
133+
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
134+
if let Some(stmt) = block.stmts.last()
135+
&& let StmtKind::Expr(expr) = &stmt.kind
136+
&& let rustc_ast::ExprKind::Tup(inner) = &expr.kind
137+
&& inner.is_empty()
58138
&& let ctxt = block.span.ctxt()
59139
&& stmt.span.ctxt() == ctxt
60140
&& expr.span.ctxt() == ctxt
@@ -72,39 +152,10 @@ impl EarlyLintPass for UnusedUnit {
72152
);
73153
}
74154
}
155+
}
75156

76-
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
77-
match e.kind {
78-
ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
79-
if is_unit_expr(expr) && !expr.span.from_expansion() {
80-
span_lint_and_sugg(
81-
cx,
82-
UNUSED_UNIT,
83-
expr.span,
84-
"unneeded `()`",
85-
"remove the `()`",
86-
String::new(),
87-
Applicability::MachineApplicable,
88-
);
89-
}
90-
},
91-
_ => (),
92-
}
93-
}
94-
95-
fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef) {
96-
let segments = &poly.trait_ref.path.segments;
97-
98-
if segments.len() == 1
99-
&& ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str())
100-
&& let Some(args) = &segments[0].args
101-
&& let ast::GenericArgs::Parenthesized(generic_args) = &**args
102-
&& let ast::FnRetTy::Ty(ty) = &generic_args.output
103-
&& ty.kind.is_unit()
104-
{
105-
lint_unneeded_unit_return(cx, ty, generic_args.span);
106-
}
107-
}
157+
fn is_unit_ty(ty: &Ty<'_>) -> bool {
158+
matches!(ty.kind, TyKind::Tup([]))
108159
}
109160

110161
// get the def site
@@ -117,24 +168,15 @@ fn get_def(span: Span) -> Option<Span> {
117168
}
118169
}
119170

120-
// is this expr a `()` unit?
121-
fn is_unit_expr(expr: &ast::Expr) -> bool {
122-
if let ast::ExprKind::Tup(ref vals) = expr.kind {
123-
vals.is_empty()
124-
} else {
125-
false
126-
}
127-
}
128-
129-
fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
171+
fn lint_unneeded_unit_return(cx: &LateContext<'_>, ty_span: Span, span: Span) {
130172
let (ret_span, appl) =
131-
span.with_hi(ty.span.hi())
173+
span.with_hi(ty_span.hi())
132174
.get_source_text(cx)
133-
.map_or((ty.span, Applicability::MaybeIncorrect), |src| {
134-
position_before_rarrow(&src).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
175+
.map_or((ty_span, Applicability::MaybeIncorrect), |src| {
176+
position_before_rarrow(&src).map_or((ty_span, Applicability::MaybeIncorrect), |rpos| {
135177
(
136178
#[expect(clippy::cast_possible_truncation)]
137-
ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
179+
ty_span.with_lo(BytePos(span.lo().0 + rpos as u32)),
138180
Applicability::MachineApplicable,
139181
)
140182
})

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ macro_rules! msrv_aliases {
2323
// names may refer to stabilized feature flags or library items
2424
msrv_aliases! {
2525
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT }
26-
1,85,0 { UINT_FLOAT_MIDPOINT }
26+
1,85,0 { UINT_FLOAT_MIDPOINT, UNIT_NEVER_TYPE_FALLBACK }
2727
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
2828
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
2929
1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP }

tests/ui/unused_unit.fixed

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,24 @@ mod issue9949 {
120120
()
121121
}
122122
}
123+
124+
#[clippy::msrv = "1.85"]
125+
mod issue14577 {
126+
trait Unit {}
127+
impl Unit for () {}
128+
129+
fn run<R: Unit>(f: impl FnOnce() -> R) {
130+
f();
131+
}
132+
133+
fn bar() {
134+
run(|| -> () { todo!() });
135+
}
136+
137+
struct UnitStruct;
138+
impl UnitStruct {
139+
fn apply<F: for<'c> Fn(&'c mut Self)>(&mut self, f: F) {
140+
todo!()
141+
}
142+
}
143+
}

tests/ui/unused_unit.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,24 @@ mod issue9949 {
120120
()
121121
}
122122
}
123+
124+
#[clippy::msrv = "1.85"]
125+
mod issue14577 {
126+
trait Unit {}
127+
impl Unit for () {}
128+
129+
fn run<R: Unit>(f: impl FnOnce() -> R) {
130+
f();
131+
}
132+
133+
fn bar() {
134+
run(|| -> () { todo!() });
135+
}
136+
137+
struct UnitStruct;
138+
impl UnitStruct {
139+
fn apply<F: for<'c> Fn(&'c mut Self)>(&mut self, f: F) {
140+
todo!()
141+
}
142+
}
143+
}

tests/ui/unused_unit.stderr

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
error: unneeded unit return type
2-
--> tests/ui/unused_unit.rs:20:58
1+
error: unneeded unit expression
2+
--> tests/ui/unused_unit.rs:35:9
33
|
4-
LL | pub fn get_unit<F: Fn() -> (), G>(&self, f: F, _g: G) -> ()
5-
| ^^^^^^ help: remove the `-> ()`
4+
LL | ()
5+
| ^^ help: remove the final `()`
66
|
77
note: the lint level is defined here
88
--> tests/ui/unused_unit.rs:13:9
99
|
1010
LL | #![deny(clippy::unused_unit)]
1111
| ^^^^^^^^^^^^^^^^^^^
1212

13+
error: unneeded unit expression
14+
--> tests/ui/unused_unit.rs:60:26
15+
|
16+
LL | fn return_unit() -> () { () }
17+
| ^^ help: remove the final `()`
18+
1319
error: unneeded unit return type
1420
--> tests/ui/unused_unit.rs:20:28
1521
|
@@ -22,6 +28,12 @@ error: unneeded unit return type
2228
LL | where G: Fn() -> () {
2329
| ^^^^^^ help: remove the `-> ()`
2430

31+
error: unneeded unit return type
32+
--> tests/ui/unused_unit.rs:20:58
33+
|
34+
LL | pub fn get_unit<F: Fn() -> (), G>(&self, f: F, _g: G) -> ()
35+
| ^^^^^^ help: remove the `-> ()`
36+
2537
error: unneeded unit return type
2638
--> tests/ui/unused_unit.rs:25:26
2739
|
@@ -34,12 +46,6 @@ error: unneeded unit return type
3446
LL | fn into(self) -> () {
3547
| ^^^^^^ help: remove the `-> ()`
3648

37-
error: unneeded unit expression
38-
--> tests/ui/unused_unit.rs:35:9
39-
|
40-
LL | ()
41-
| ^^ help: remove the final `()`
42-
4349
error: unneeded unit return type
4450
--> tests/ui/unused_unit.rs:41:29
4551
|
@@ -82,12 +88,6 @@ error: unneeded unit return type
8288
LL | fn return_unit() -> () { () }
8389
| ^^^^^^ help: remove the `-> ()`
8490

85-
error: unneeded unit expression
86-
--> tests/ui/unused_unit.rs:60:26
87-
|
88-
LL | fn return_unit() -> () { () }
89-
| ^^ help: remove the final `()`
90-
9191
error: unneeded `()`
9292
--> tests/ui/unused_unit.rs:72:14
9393
|

0 commit comments

Comments
 (0)