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