]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
Auto merge of #105592 - matthiaskrgr:rollup-1cazogq, r=matthiaskrgr
[rust.git] / src / tools / clippy / clippy_lints / src / matches / match_single_binding.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::macros::HirNode;
3 use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
4 use clippy_utils::sugg::Sugg;
5 use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
6 use rustc_errors::Applicability;
7 use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
8 use rustc_lint::LateContext;
9 use rustc_span::Span;
10
11 use super::MATCH_SINGLE_BINDING;
12
13 enum AssignmentExpr {
14     Assign { span: Span, match_span: Span },
15     Local { span: Span, pat_span: Span },
16 }
17
18 #[expect(clippy::too_many_lines)]
19 pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) {
20     if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
21         return;
22     }
23
24     let matched_vars = ex.span;
25     let bind_names = arms[0].pat.span;
26     let match_body = peel_blocks(arms[0].body);
27     let mut snippet_body = if match_body.span.from_expansion() {
28         Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
29     } else {
30         snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
31     };
32
33     // Do we need to add ';' to suggestion ?
34     match match_body.kind {
35         ExprKind::Block(block, _) => {
36             // macro + expr_ty(body) == ()
37             if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
38                 snippet_body.push(';');
39             }
40         },
41         _ => {
42             // expr_ty(body) == ()
43             if cx.typeck_results().expr_ty(match_body).is_unit() {
44                 snippet_body.push(';');
45             }
46         },
47     }
48
49     let mut applicability = Applicability::MaybeIncorrect;
50     match arms[0].pat.kind {
51         PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
52             let (target_span, sugg) = match opt_parent_assign_span(cx, ex) {
53                 Some(AssignmentExpr::Assign { span, match_span }) => {
54                     let sugg = sugg_with_curlies(
55                         cx,
56                         (ex, expr),
57                         (bind_names, matched_vars),
58                         &snippet_body,
59                         &mut applicability,
60                         Some(span),
61                         true,
62                     );
63
64                     span_lint_and_sugg(
65                         cx,
66                         MATCH_SINGLE_BINDING,
67                         span.to(match_span),
68                         "this assignment could be simplified",
69                         "consider removing the `match` expression",
70                         sugg,
71                         applicability,
72                     );
73
74                     return;
75                 },
76                 Some(AssignmentExpr::Local { span, pat_span }) => (
77                     span,
78                     format!(
79                         "let {} = {};\n{}let {} = {snippet_body};",
80                         snippet_with_applicability(cx, bind_names, "..", &mut applicability),
81                         snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
82                         " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
83                         snippet_with_applicability(cx, pat_span, "..", &mut applicability)
84                     ),
85                 ),
86                 None => {
87                     let sugg = sugg_with_curlies(
88                         cx,
89                         (ex, expr),
90                         (bind_names, matched_vars),
91                         &snippet_body,
92                         &mut applicability,
93                         None,
94                         true,
95                     );
96                     (expr.span, sugg)
97                 },
98             };
99
100             span_lint_and_sugg(
101                 cx,
102                 MATCH_SINGLE_BINDING,
103                 target_span,
104                 "this match could be written as a `let` statement",
105                 "consider using a `let` statement",
106                 sugg,
107                 applicability,
108             );
109         },
110         PatKind::Wild => {
111             if ex.can_have_side_effects() {
112                 let sugg = sugg_with_curlies(
113                     cx,
114                     (ex, expr),
115                     (bind_names, matched_vars),
116                     &snippet_body,
117                     &mut applicability,
118                     None,
119                     false,
120                 );
121
122                 span_lint_and_sugg(
123                     cx,
124                     MATCH_SINGLE_BINDING,
125                     expr.span,
126                     "this match could be replaced by its scrutinee and body",
127                     "consider using the scrutinee and body instead",
128                     sugg,
129                     applicability,
130                 );
131             } else {
132                 span_lint_and_sugg(
133                     cx,
134                     MATCH_SINGLE_BINDING,
135                     expr.span,
136                     "this match could be replaced by its body itself",
137                     "consider using the match body instead",
138                     snippet_body,
139                     Applicability::MachineApplicable,
140                 );
141             }
142         },
143         _ => (),
144     }
145 }
146
147 /// Returns true if the `ex` match expression is in a local (`let`) or assign expression
148 fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
149     let map = &cx.tcx.hir();
150
151     if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) {
152         return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) {
153             Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local {
154                 span: parent_let_expr.span,
155                 pat_span: parent_let_expr.pat.span(),
156             }),
157             Some(Node::Expr(Expr {
158                 kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
159                 ..
160             })) => Some(AssignmentExpr::Assign {
161                 span: parent_assign_expr.span,
162                 match_span: match_expr.span,
163             }),
164             _ => None,
165         };
166     }
167
168     None
169 }
170
171 fn sugg_with_curlies<'a>(
172     cx: &LateContext<'a>,
173     (ex, match_expr): (&Expr<'a>, &Expr<'a>),
174     (bind_names, matched_vars): (Span, Span),
175     snippet_body: &str,
176     applicability: &mut Applicability,
177     assignment: Option<Span>,
178     needs_var_binding: bool,
179 ) -> String {
180     let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
181
182     let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
183     if let Some(parent_expr) = get_parent_expr(cx, match_expr) {
184         if let ExprKind::Closure { .. } = parent_expr.kind {
185             cbrace_end = format!("\n{indent}}}");
186             // Fix body indent due to the closure
187             indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
188             cbrace_start = format!("{{\n{indent}");
189         }
190     }
191
192     // If the parent is already an arm, and the body is another match statement,
193     // we need curly braces around suggestion
194     let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id);
195     if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
196         if let ExprKind::Match(..) = arm.body.kind {
197             cbrace_end = format!("\n{indent}}}");
198             // Fix body indent due to the match
199             indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
200             cbrace_start = format!("{{\n{indent}");
201         }
202     }
203
204     let assignment_str = assignment.map_or_else(String::new, |span| {
205         let mut s = snippet(cx, span, "..").to_string();
206         s.push_str(" = ");
207         s
208     });
209
210     let scrutinee = if needs_var_binding {
211         format!(
212             "let {} = {}",
213             snippet_with_applicability(cx, bind_names, "..", applicability),
214             snippet_with_applicability(cx, matched_vars, "..", applicability)
215         )
216     } else {
217         snippet_with_applicability(cx, matched_vars, "..", applicability).to_string()
218     };
219
220     format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")
221 }