Skip to content

Commit 8d8d56f

Browse files
committed
fix: unused_unit suggests wrongly when unit never type fallback
1 parent 781fdab commit 8d8d56f

File tree

5 files changed

+152
-78
lines changed

5 files changed

+152
-78
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
729729
store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints));
730730
store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall));
731731
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
732+
store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit));
732733
store.register_late_pass(|_| Box::new(returns::Return));
733734
store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf)));
734735
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));

clippy_lints/src/unused_unit.rs

Lines changed: 93 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::{SpanRangeExt, position_before_rarrow};
3-
use rustc_ast::visit::FnKind;
4-
use rustc_ast::{ClosureBinder, ast};
3+
use clippy_utils::{is_never_expr, is_unit_expr};
4+
use rustc_ast::{Block, StmtKind};
55
use rustc_errors::Applicability;
6-
use rustc_lint::{EarlyContext, EarlyLintPass};
6+
use rustc_hir::def_id::LocalDefId;
7+
use rustc_hir::intravisit::FnKind;
8+
use rustc_hir::{
9+
AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, Node, PolyTraitRef, Term,
10+
Ty, TyKind,
11+
};
12+
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
713
use rustc_session::declare_lint_pass;
8-
use rustc_span::{BytePos, Span};
14+
use rustc_span::edition::Edition;
15+
use rustc_span::{BytePos, Span, sym};
916

1017
declare_clippy_lint! {
1118
/// ### What it does
@@ -34,27 +41,89 @@ declare_clippy_lint! {
3441

3542
declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
3643

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)
44+
impl<'tcx> LateLintPass<'tcx> for UnusedUnit {
45+
fn check_fn(
46+
&mut self,
47+
cx: &LateContext<'tcx>,
48+
kind: FnKind<'tcx>,
49+
decl: &'tcx FnDecl<'tcx>,
50+
body: &'tcx Body<'tcx>,
51+
span: Span,
52+
def_id: LocalDefId,
53+
) {
54+
if let FnRetTy::Return(hir_ty) = decl.output
55+
&& is_unit_ty(hir_ty)
56+
&& !hir_ty.span.from_expansion()
57+
&& get_def(span) == get_def(hir_ty.span)
4458
{
4559
// implicit types in closure signatures are forbidden when `for<...>` is present
46-
if let FnKind::Closure(&ClosureBinder::For { .. }, ..) = kind {
60+
if let FnKind::Closure = kind
61+
&& let Node::Expr(expr) = cx.tcx.hir_node_by_def_id(def_id)
62+
&& let ExprKind::Closure(closure) = expr.kind
63+
&& !closure.bound_generic_params.is_empty()
64+
{
65+
return;
66+
}
67+
68+
// unit never type fallback is no longer supported since Rust 2024. For more information,
69+
// see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/never-type-fallback.html>
70+
if cx.tcx.sess.edition() >= Edition::Edition2024
71+
&& let ExprKind::Block(block, _) = body.value.kind
72+
&& let Some(expr) = block.expr
73+
&& is_never_expr(cx, expr).is_some()
74+
{
4775
return;
4876
}
4977

50-
lint_unneeded_unit_return(cx, ty, span);
78+
lint_unneeded_unit_return(cx, hir_ty.span, span);
5179
}
5280
}
5381

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
82+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
83+
if let ExprKind::Ret(Some(expr)) | ExprKind::Break(_, Some(expr)) = expr.kind
5784
&& is_unit_expr(expr)
85+
&& !expr.span.from_expansion()
86+
{
87+
span_lint_and_sugg(
88+
cx,
89+
UNUSED_UNIT,
90+
expr.span,
91+
"unneeded `()`",
92+
"remove the `()`",
93+
String::new(),
94+
Applicability::MachineApplicable,
95+
);
96+
}
97+
}
98+
99+
fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) {
100+
let segments = &poly.trait_ref.path.segments;
101+
102+
if segments.len() == 1
103+
&& ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str())
104+
&& let Some(args) = segments[0].args
105+
&& args.parenthesized == GenericArgsParentheses::ParenSugar
106+
&& let constraints = &args.constraints
107+
&& constraints.len() == 1
108+
&& constraints[0].ident.name == sym::Output
109+
&& let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind
110+
&& args.span_ext.hi() != poly.span.hi()
111+
&& !hir_ty.span.from_expansion()
112+
&& is_unit_ty(hir_ty)
113+
{
114+
lint_unneeded_unit_return(cx, hir_ty.span, poly.span);
115+
}
116+
}
117+
}
118+
119+
impl EarlyLintPass for UnusedUnit {
120+
/// Check for unit expressions in blocks. This is left in the early pass because some macros
121+
/// expand its inputs as-is, making it invisible to the late pass. See #4076.
122+
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
123+
if let Some(stmt) = block.stmts.last()
124+
&& let StmtKind::Expr(expr) = &stmt.kind
125+
&& let rustc_ast::ExprKind::Tup(inner) = &expr.kind
126+
&& inner.is_empty()
58127
&& let ctxt = block.span.ctxt()
59128
&& stmt.span.ctxt() == ctxt
60129
&& expr.span.ctxt() == ctxt
@@ -72,39 +141,10 @@ impl EarlyLintPass for UnusedUnit {
72141
);
73142
}
74143
}
144+
}
75145

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-
}
146+
fn is_unit_ty(ty: &Ty<'_>) -> bool {
147+
matches!(ty.kind, TyKind::Tup([]))
108148
}
109149

110150
// get the def site
@@ -117,24 +157,15 @@ fn get_def(span: Span) -> Option<Span> {
117157
}
118158
}
119159

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) {
160+
fn lint_unneeded_unit_return(cx: &LateContext<'_>, ty_span: Span, span: Span) {
130161
let (ret_span, appl) =
131-
span.with_hi(ty.span.hi())
162+
span.with_hi(ty_span.hi())
132163
.get_source_text(cx)
133-
.map_or((ty.span, Applicability::MaybeIncorrect), |src| {
134-
position_before_rarrow(&src).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
164+
.map_or((ty_span, Applicability::MaybeIncorrect), |src| {
165+
position_before_rarrow(&src).map_or((ty_span, Applicability::MaybeIncorrect), |rpos| {
135166
(
136167
#[expect(clippy::cast_possible_truncation)]
137-
ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
168+
ty_span.with_lo(BytePos(span.lo().0 + rpos as u32)),
138169
Applicability::MachineApplicable,
139170
)
140171
})

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)