]>
Commit | Line | Data |
---|---|---|
5099ac24 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
923072b8 FG |
2 | use clippy_utils::macros::HirNode; |
3 | use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability}; | |
5099ac24 FG |
4 | use clippy_utils::sugg::Sugg; |
5 | use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; | |
6 | use rustc_errors::Applicability; | |
923072b8 | 7 | use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind}; |
5099ac24 | 8 | use rustc_lint::LateContext; |
923072b8 | 9 | use rustc_span::Span; |
5099ac24 FG |
10 | |
11 | use super::MATCH_SINGLE_BINDING; | |
12 | ||
923072b8 FG |
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>) { | |
5099ac24 FG |
20 | if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { |
21 | return; | |
22 | } | |
23 | ||
5099ac24 FG |
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(..) => { | |
923072b8 FG |
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, | |
5099ac24 FG |
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)), | |
923072b8 | 82 | snippet_with_applicability(cx, pat_span, "..", &mut applicability), |
5099ac24 FG |
83 | snippet_body |
84 | ), | |
923072b8 FG |
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 | }, | |
5099ac24 | 97 | }; |
923072b8 | 98 | |
5099ac24 FG |
99 | span_lint_and_sugg( |
100 | cx, | |
101 | MATCH_SINGLE_BINDING, | |
102 | target_span, | |
103 | "this match could be written as a `let` statement", | |
923072b8 | 104 | "consider using a `let` statement", |
5099ac24 FG |
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 | ); | |
923072b8 | 118 | |
5099ac24 FG |
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 | ||
923072b8 FG |
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> { | |
5099ac24 | 146 | let map = &cx.tcx.hir(); |
923072b8 FG |
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 | }; | |
5099ac24 | 163 | } |
923072b8 | 164 | |
5099ac24 FG |
165 | None |
166 | } | |
923072b8 FG |
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 | } |