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;
11 use super::MATCH_SINGLE_BINDING;
14 Assign { span: Span, match_span: Span },
15 Local { span: Span, pat_span: Span },
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) {
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()
30 snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
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(';');
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(
49 (bind_names, matched_vars),
60 "this assignment could be simplified",
61 "consider removing the `match` expression",
68 Some(AssignmentExpr::Local { span, pat_span }) => (
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)
79 let sugg = sugg_with_curlies(
82 (bind_names, matched_vars),
96 "this match could be written as a `let` statement",
97 "consider using a `let` statement",
103 if ex.can_have_side_effects() {
104 let sugg = sugg_with_curlies(
107 (bind_names, matched_vars),
116 MATCH_SINGLE_BINDING,
118 "this match could be replaced by its scrutinee and body",
119 "consider using the scrutinee and body instead",
126 MATCH_SINGLE_BINDING,
128 "this match could be replaced by its body itself",
129 "consider using the match body instead",
131 Applicability::MachineApplicable,
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();
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(),
149 Some(Node::Expr(Expr {
150 kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
152 })) => Some(AssignmentExpr::Assign {
153 span: parent_assign_expr.span,
154 match_span: match_expr.span,
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),
168 applicability: &mut Applicability,
169 assignment: Option<Span>,
170 needs_var_binding: bool,
172 let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
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}");
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}");
196 let assignment_str = assignment.map_or_else(String::new, |span| {
197 let mut s = snippet(cx, span, "..").to_string();
202 let scrutinee = if needs_var_binding {
205 snippet_with_applicability(cx, bind_names, "..", applicability),
206 snippet_with_applicability(cx, matched_vars, "..", applicability)
209 snippet_with_applicability(cx, matched_vars, "..", applicability).to_string()
212 format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")