]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
94222f64 | 2 | use clippy_utils::source::{position_before_rarrow, snippet_opt}; |
f20569fa | 3 | use if_chain::if_chain; |
487cf647 | 4 | use rustc_ast::{ast, visit::FnKind, ClosureBinder}; |
f20569fa | 5 | use rustc_errors::Applicability; |
94222f64 | 6 | use rustc_lint::{EarlyContext, EarlyLintPass}; |
f20569fa XL |
7 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
8 | use rustc_span::source_map::Span; | |
9 | use rustc_span::BytePos; | |
10 | ||
f20569fa | 11 | declare_clippy_lint! { |
94222f64 XL |
12 | /// ### What it does |
13 | /// Checks for unit (`()`) expressions that can be removed. | |
f20569fa | 14 | /// |
94222f64 XL |
15 | /// ### Why is this bad? |
16 | /// Such expressions add no value, but can make the code | |
f20569fa XL |
17 | /// less readable. Depending on formatting they can make a `break` or `return` |
18 | /// statement look like a function call. | |
19 | /// | |
94222f64 | 20 | /// ### Example |
f20569fa XL |
21 | /// ```rust |
22 | /// fn return_unit() -> () { | |
23 | /// () | |
24 | /// } | |
25 | /// ``` | |
136023e0 XL |
26 | /// is equivalent to |
27 | /// ```rust | |
28 | /// fn return_unit() {} | |
29 | /// ``` | |
a2a8927a | 30 | #[clippy::version = "1.31.0"] |
f20569fa XL |
31 | pub UNUSED_UNIT, |
32 | style, | |
33 | "needless unit expression" | |
34 | } | |
35 | ||
36 | declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]); | |
37 | ||
38 | impl EarlyLintPass for UnusedUnit { | |
39 | fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { | |
40 | if_chain! { | |
41 | if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; | |
42 | if let ast::TyKind::Tup(ref vals) = ty.kind; | |
43 | if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span); | |
44 | then { | |
487cf647 FG |
45 | // implicit types in closure signatures are forbidden when `for<...>` is present |
46 | if let FnKind::Closure(&ClosureBinder::For { .. }, ..) = kind { | |
47 | return; | |
48 | } | |
49 | ||
f20569fa XL |
50 | lint_unneeded_unit_return(cx, ty, span); |
51 | } | |
52 | } | |
53 | } | |
54 | ||
55 | fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { | |
56 | if_chain! { | |
cdc7bbd5 | 57 | if let Some(stmt) = block.stmts.last(); |
f20569fa | 58 | if let ast::StmtKind::Expr(ref expr) = stmt.kind; |
17df50a5 XL |
59 | if is_unit_expr(expr); |
60 | let ctxt = block.span.ctxt(); | |
61 | if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt; | |
f20569fa XL |
62 | then { |
63 | let sp = expr.span; | |
64 | span_lint_and_sugg( | |
65 | cx, | |
66 | UNUSED_UNIT, | |
67 | sp, | |
68 | "unneeded unit expression", | |
69 | "remove the final `()`", | |
70 | String::new(), | |
71 | Applicability::MachineApplicable, | |
72 | ); | |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { | |
78 | match e.kind { | |
79 | ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => { | |
80 | if is_unit_expr(expr) && !expr.span.from_expansion() { | |
81 | span_lint_and_sugg( | |
82 | cx, | |
83 | UNUSED_UNIT, | |
84 | expr.span, | |
85 | "unneeded `()`", | |
86 | "remove the `()`", | |
87 | String::new(), | |
88 | Applicability::MachineApplicable, | |
89 | ); | |
90 | } | |
91 | }, | |
92 | _ => (), | |
93 | } | |
94 | } | |
95 | ||
f2b60f7d | 96 | fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef) { |
f20569fa XL |
97 | let segments = &poly.trait_ref.path.segments; |
98 | ||
99 | if_chain! { | |
100 | if segments.len() == 1; | |
a2a8927a | 101 | if ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str()); |
f20569fa XL |
102 | if let Some(args) = &segments[0].args; |
103 | if let ast::GenericArgs::Parenthesized(generic_args) = &**args; | |
104 | if let ast::FnRetTy::Ty(ty) = &generic_args.output; | |
105 | if ty.kind.is_unit(); | |
106 | then { | |
107 | lint_unneeded_unit_return(cx, ty, generic_args.span); | |
108 | } | |
109 | } | |
110 | } | |
111 | } | |
112 | ||
113 | // get the def site | |
114 | #[must_use] | |
115 | fn get_def(span: Span) -> Option<Span> { | |
116 | if span.from_expansion() { | |
117 | Some(span.ctxt().outer_expn_data().def_site) | |
118 | } else { | |
119 | None | |
120 | } | |
121 | } | |
122 | ||
123 | // is this expr a `()` unit? | |
124 | fn is_unit_expr(expr: &ast::Expr) -> bool { | |
125 | if let ast::ExprKind::Tup(ref vals) = expr.kind { | |
126 | vals.is_empty() | |
127 | } else { | |
128 | false | |
129 | } | |
130 | } | |
131 | ||
132 | fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) { | |
94222f64 XL |
133 | let (ret_span, appl) = |
134 | snippet_opt(cx, span.with_hi(ty.span.hi())).map_or((ty.span, Applicability::MaybeIncorrect), |fn_source| { | |
135 | position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| { | |
136 | ( | |
923072b8 | 137 | #[expect(clippy::cast_possible_truncation)] |
94222f64 XL |
138 | ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)), |
139 | Applicability::MachineApplicable, | |
140 | ) | |
141 | }) | |
142 | }); | |
f20569fa XL |
143 | span_lint_and_sugg( |
144 | cx, | |
145 | UNUSED_UNIT, | |
146 | ret_span, | |
147 | "unneeded unit return type", | |
148 | "remove the `-> ()`", | |
149 | String::new(), | |
150 | appl, | |
151 | ); | |
152 | } |