1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::{indent_of, snippet_block, snippet_with_applicability};
3 use clippy_utils::sugg::Sugg;
4 use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
5 use rustc_errors::Applicability;
6 use rustc_hir::{Arm, Expr, ExprKind, Local, Node, PatKind};
7 use rustc_lint::LateContext;
9 use super::MATCH_SINGLE_BINDING;
11 #[allow(clippy::too_many_lines)]
12 pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
13 if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
17 let matched_vars = ex.span;
18 let bind_names = arms[0].pat.span;
19 let match_body = peel_blocks(arms[0].body);
20 let mut snippet_body = if match_body.span.from_expansion() {
21 Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
23 snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
26 // Do we need to add ';' to suggestion ?
27 match match_body.kind {
28 ExprKind::Block(block, _) => {
29 // macro + expr_ty(body) == ()
30 if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
31 snippet_body.push(';');
35 // expr_ty(body) == ()
36 if cx.typeck_results().expr_ty(match_body).is_unit() {
37 snippet_body.push(';');
42 let mut applicability = Applicability::MaybeIncorrect;
43 match arms[0].pat.kind {
44 PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
45 // If this match is in a local (`let`) stmt
46 let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
50 "let {} = {};\n{}let {} = {};",
51 snippet_with_applicability(cx, bind_names, "..", &mut applicability),
52 snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
53 " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
54 snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
59 // If we are in closure, we need curly braces around suggestion
60 let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
61 let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
62 if let Some(parent_expr) = get_parent_expr(cx, expr) {
63 if let ExprKind::Closure(..) = parent_expr.kind {
64 cbrace_end = format!("\n{}}}", indent);
65 // Fix body indent due to the closure
66 indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
67 cbrace_start = format!("{{\n{}", indent);
70 // If the parent is already an arm, and the body is another match statement,
71 // we need curly braces around suggestion
72 let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
73 if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
74 if let ExprKind::Match(..) = arm.body.kind {
75 cbrace_end = format!("\n{}}}", indent);
76 // Fix body indent due to the match
77 indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
78 cbrace_start = format!("{{\n{}", indent);
84 "{}let {} = {};\n{}{}{}",
86 snippet_with_applicability(cx, bind_names, "..", &mut applicability),
87 snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
98 "this match could be written as a `let` statement",
99 "consider using `let` statement",
105 if ex.can_have_side_effects() {
106 let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
109 snippet_with_applicability(cx, ex.span, "..", &mut applicability),
115 MATCH_SINGLE_BINDING,
117 "this match could be replaced by its scrutinee and body",
118 "consider using the scrutinee and body instead",
125 MATCH_SINGLE_BINDING,
127 "this match could be replaced by its body itself",
128 "consider using the match body instead",
130 Applicability::MachineApplicable,
138 /// Returns true if the `ex` match expression is in a local (`let`) statement
139 fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
140 let map = &cx.tcx.hir();
142 if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
143 if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
145 return Some(parent_let_expr);