11use clippy_utils:: diagnostics:: span_lint_and_sugg;
22use 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 } ;
55use 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 } ;
713use 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
1017declare_clippy_lint ! {
1118 /// ### What it does
@@ -34,27 +41,89 @@ declare_clippy_lint! {
3441
3542declare_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 } )
0 commit comments