]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
New upstream version 1.70.0+dfsg1
[rustc.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_with_context, snippet_with_applicability};
4 use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
5 use rustc_errors::Applicability;
6 use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind};
7 use rustc_lint::LateContext;
8 use rustc_span::Span;
9
10 use super::MATCH_SINGLE_BINDING;
11
12 enum AssignmentExpr {
13 Assign { span: Span, match_span: Span },
14 Local { span: Span, pat_span: Span },
15 }
16
17 #[expect(clippy::too_many_lines)]
18 pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) {
19 if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
20 return;
21 }
22
23 let matched_vars = ex.span;
24 let bind_names = arms[0].pat.span;
25 let match_body = peel_blocks(arms[0].body);
26 let mut app = Applicability::MaybeIncorrect;
27 let mut snippet_body = snippet_block_with_context(
28 cx,
29 match_body.span,
30 arms[0].span.ctxt(),
31 "..",
32 Some(expr.span),
33 &mut app,
34 )
35 .0
36 .to_string();
37
38 // Do we need to add ';' to suggestion ?
39 if let Node::Stmt(stmt) = cx.tcx.hir().get_parent(expr.hir_id)
40 && let StmtKind::Expr(_) = stmt.kind
41 && match match_body.kind {
42 // We don't need to add a ; to blocks, unless that block is from a macro expansion
43 ExprKind::Block(block, _) => block.span.from_expansion(),
44 _ => true,
45 }
46 {
47 snippet_body.push(';');
48 }
49
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 app,
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 app,
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 app),
81 snippet_with_applicability(cx, matched_vars, "..", &mut app),
82 " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
83 snippet_with_applicability(cx, pat_span, "..", &mut app)
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 app,
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 app,
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 app,
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 app,
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_parent(ex.hir_id) {
152 return match map.find_parent(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 if let Node::Arm(arm) = &cx.tcx.hir().get_parent(match_expr.hir_id) {
195 if let ExprKind::Match(..) = arm.body.kind {
196 cbrace_end = format!("\n{indent}}}");
197 // Fix body indent due to the match
198 indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
199 cbrace_start = format!("{{\n{indent}");
200 }
201 }
202
203 let assignment_str = assignment.map_or_else(String::new, |span| {
204 let mut s = snippet(cx, span, "..").to_string();
205 s.push_str(" = ");
206 s
207 });
208
209 let scrutinee = if needs_var_binding {
210 format!(
211 "let {} = {}",
212 snippet_with_applicability(cx, bind_names, "..", applicability),
213 snippet_with_applicability(cx, matched_vars, "..", applicability)
214 )
215 } else {
216 snippet_with_applicability(cx, matched_vars, "..", applicability).to_string()
217 };
218
219 format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")
220 }