]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / matches / match_single_binding.rs
CommitLineData
5099ac24 1use clippy_utils::diagnostics::span_lint_and_sugg;
923072b8
FG
2use clippy_utils::macros::HirNode;
3use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
5099ac24
FG
4use clippy_utils::sugg::Sugg;
5use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
6use rustc_errors::Applicability;
923072b8 7use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
5099ac24 8use rustc_lint::LateContext;
923072b8 9use rustc_span::Span;
5099ac24
FG
10
11use super::MATCH_SINGLE_BINDING;
12
923072b8
FG
13enum AssignmentExpr {
14 Assign { span: Span, match_span: Span },
15 Local { span: Span, pat_span: Span },
16}
17
18#[expect(clippy::too_many_lines)]
19pub(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
145fn 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
168fn 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}