Skip to content

Commit 542762e

Browse files
authored
fix: unused_unit suggests wrongly when unit never type fallback (#14609)
Closes rust-lang/rust-clippy#14577. Migrate this lint to late pass and avoids `unit_never_type_fallback` since it is no longer permitted in Rust 2024. changelog: [`unused_unit`] fix wrong suggestions when unit never type fallback
2 parents 0dd9722 + 5d8fb77 commit 542762e

9 files changed

+698
-79
lines changed

clippy_lints/src/lib.rs

+1
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

+93-62
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
})
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//@revisions: edition2021 edition2024
2+
//@[edition2021] edition:2021
3+
//@[edition2024] edition:2024
4+
5+
// The output for humans should just highlight the whole span without showing
6+
// the suggested replacement, but we also want to test that suggested
7+
// replacement only removes one set of parentheses, rather than naïvely
8+
// stripping away any starting or ending parenthesis characters—hence this
9+
// test of the JSON error format.
10+
11+
#![feature(custom_inner_attributes)]
12+
#![feature(closure_lifetime_binder)]
13+
#![rustfmt::skip]
14+
15+
#![deny(clippy::unused_unit)]
16+
#![allow(dead_code)]
17+
#![allow(clippy::from_over_into)]
18+
19+
struct Unitter;
20+
impl Unitter {
21+
#[allow(clippy::no_effect)]
22+
pub fn get_unit<F: Fn(), G>(&self, f: F, _g: G)
23+
//~^ unused_unit
24+
//~| unused_unit
25+
where G: Fn() {
26+
//~^ unused_unit
27+
let _y: &dyn Fn() = &f;
28+
//~^ unused_unit
29+
(); // this should not lint, as it's not in return type position
30+
}
31+
}
32+
33+
impl Into<()> for Unitter {
34+
#[rustfmt::skip]
35+
fn into(self) {
36+
//~^ unused_unit
37+
38+
//~^ unused_unit
39+
}
40+
}
41+
42+
trait Trait {
43+
fn redundant<F: FnOnce(), G, H>(&self, _f: F, _g: G, _h: H)
44+
//~^ unused_unit
45+
where
46+
G: FnMut(),
47+
//~^ unused_unit
48+
H: Fn();
49+
//~^ unused_unit
50+
}
51+
52+
impl Trait for Unitter {
53+
fn redundant<F: FnOnce(), G, H>(&self, _f: F, _g: G, _h: H)
54+
//~^ unused_unit
55+
where
56+
G: FnMut(),
57+
//~^ unused_unit
58+
H: Fn() {}
59+
//~^ unused_unit
60+
}
61+
62+
fn return_unit() { }
63+
//~^ unused_unit
64+
//~| unused_unit
65+
66+
#[allow(clippy::needless_return)]
67+
#[allow(clippy::never_loop)]
68+
#[allow(clippy::unit_cmp)]
69+
fn main() {
70+
let u = Unitter;
71+
assert_eq!(u.get_unit(|| {}, return_unit), u.into());
72+
return_unit();
73+
loop {
74+
break;
75+
//~^ unused_unit
76+
}
77+
return;
78+
//~^ unused_unit
79+
}
80+
81+
// https://github.com/rust-lang/rust-clippy/issues/4076
82+
fn foo() {
83+
macro_rules! foo {
84+
(recv($r:expr) -> $res:pat => $body:expr) => {
85+
$body
86+
}
87+
}
88+
89+
foo! {
90+
recv(rx) -> _x => ()
91+
}
92+
}
93+
94+
#[rustfmt::skip]
95+
fn test(){}
96+
//~^ unused_unit
97+
98+
#[rustfmt::skip]
99+
fn test2(){}
100+
//~^ unused_unit
101+
102+
#[rustfmt::skip]
103+
fn test3(){}
104+
//~^ unused_unit
105+
106+
fn macro_expr() {
107+
macro_rules! e {
108+
() => (());
109+
}
110+
e!()
111+
}
112+
113+
mod issue9748 {
114+
fn main() {
115+
let _ = for<'a> |_: &'a u32| -> () {};
116+
}
117+
}
118+
119+
mod issue9949 {
120+
fn main() {
121+
#[doc = "documentation"]
122+
()
123+
}
124+
}
125+
126+
mod issue14577 {
127+
trait Unit {}
128+
impl Unit for () {}
129+
130+
fn run<R: Unit>(f: impl FnOnce() -> R) {
131+
f();
132+
}
133+
134+
#[allow(dependency_on_unit_never_type_fallback)]
135+
fn bar() {
136+
run(|| { todo!() });
137+
//~[edition2021]^ unused_unit
138+
}
139+
140+
struct UnitStruct;
141+
impl UnitStruct {
142+
fn apply<F: for<'c> Fn(&'c mut Self)>(&mut self, f: F) {
143+
todo!()
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)