]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
Rollup merge of #101266 - LuisCardosoOliveira:translation-rustcsession-pt3, r=davidtwco
[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                     );
62
63                     span_lint_and_sugg(
64                         cx,
65                         MATCH_SINGLE_BINDING,
66                         span.to(match_span),
67                         "this assignment could be simplified",
68                         "consider removing the `match` expression",
69                         sugg,
70                         applicability,
71                     );
72
73                     return;
74                 },
75                 Some(AssignmentExpr::Local { span, pat_span }) => (
76                     span,
77                     format!(
78                         "let {} = {};\n{}let {} = {};",
79                         snippet_with_applicability(cx, bind_names, "..", &mut applicability),
80                         snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
81                         " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
82                         snippet_with_applicability(cx, pat_span, "..", &mut applicability),
83                         snippet_body
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                     );
95                     (expr.span, sugg)
96                 },
97             };
98
99             span_lint_and_sugg(
100                 cx,
101                 MATCH_SINGLE_BINDING,
102                 target_span,
103                 "this match could be written as a `let` statement",
104                 "consider using a `let` statement",
105                 sugg,
106                 applicability,
107             );
108         },
109         PatKind::Wild => {
110             if ex.can_have_side_effects() {
111                 let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
112                 let sugg = format!(
113                     "{};\n{}{}",
114                     snippet_with_applicability(cx, ex.span, "..", &mut applicability),
115                     indent,
116                     snippet_body
117                 );
118
119                 span_lint_and_sugg(
120                     cx,
121                     MATCH_SINGLE_BINDING,
122                     expr.span,
123                     "this match could be replaced by its scrutinee and body",
124                     "consider using the scrutinee and body instead",
125                     sugg,
126                     applicability,
127                 );
128             } else {
129                 span_lint_and_sugg(
130                     cx,
131                     MATCH_SINGLE_BINDING,
132                     expr.span,
133                     "this match could be replaced by its body itself",
134                     "consider using the match body instead",
135                     snippet_body,
136                     Applicability::MachineApplicable,
137                 );
138             }
139         },
140         _ => (),
141     }
142 }
143
144 /// Returns true if the `ex` match expression is in a local (`let`) or assign expression
145 fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
146     let map = &cx.tcx.hir();
147
148     if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) {
149         return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) {
150             Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local {
151                 span: parent_let_expr.span,
152                 pat_span: parent_let_expr.pat.span(),
153             }),
154             Some(Node::Expr(Expr {
155                 kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
156                 ..
157             })) => Some(AssignmentExpr::Assign {
158                 span: parent_assign_expr.span,
159                 match_span: match_expr.span,
160             }),
161             _ => None,
162         };
163     }
164
165     None
166 }
167
168 fn sugg_with_curlies<'a>(
169     cx: &LateContext<'a>,
170     (ex, match_expr): (&Expr<'a>, &Expr<'a>),
171     (bind_names, matched_vars): (Span, Span),
172     snippet_body: &str,
173     applicability: &mut Applicability,
174     assignment: Option<Span>,
175 ) -> String {
176     let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
177
178     let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
179     if let Some(parent_expr) = get_parent_expr(cx, match_expr) {
180         if let ExprKind::Closure { .. } = parent_expr.kind {
181             cbrace_end = format!("\n{}}}", indent);
182             // Fix body indent due to the closure
183             indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
184             cbrace_start = format!("{{\n{}", indent);
185         }
186     }
187
188     // If the parent is already an arm, and the body is another match statement,
189     // we need curly braces around suggestion
190     let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id);
191     if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
192         if let ExprKind::Match(..) = arm.body.kind {
193             cbrace_end = format!("\n{}}}", indent);
194             // Fix body indent due to the match
195             indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
196             cbrace_start = format!("{{\n{}", indent);
197         }
198     }
199
200     let assignment_str = assignment.map_or_else(String::new, |span| {
201         let mut s = snippet(cx, span, "..").to_string();
202         s.push_str(" = ");
203         s
204     });
205
206     format!(
207         "{}let {} = {};\n{}{}{}{}",
208         cbrace_start,
209         snippet_with_applicability(cx, bind_names, "..", applicability),
210         snippet_with_applicability(cx, matched_vars, "..", applicability),
211         indent,
212         assignment_str,
213         snippet_body,
214         cbrace_end
215     )
216 }