]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
Merge commit 'e329249b6a3a98830d860c74c8234a8dd9407436' into clippyup
[rust.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::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;
8
9 use super::MATCH_SINGLE_BINDING;
10
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) {
14         return;
15     }
16
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()
22     } else {
23         snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
24     };
25
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(';');
32             }
33         },
34         _ => {
35             // expr_ty(body) == ()
36             if cx.typeck_results().expr_ty(match_body).is_unit() {
37                 snippet_body.push(';');
38             }
39         },
40     }
41
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) {
47                 (
48                     parent_let_node.span,
49                     format!(
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),
55                         snippet_body
56                     ),
57                 )
58             } else {
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);
68                     }
69                 }
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);
79                     }
80                 }
81                 (
82                     expr.span,
83                     format!(
84                         "{}let {} = {};\n{}{}{}",
85                         cbrace_start,
86                         snippet_with_applicability(cx, bind_names, "..", &mut applicability),
87                         snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
88                         indent,
89                         snippet_body,
90                         cbrace_end
91                     ),
92                 )
93             };
94             span_lint_and_sugg(
95                 cx,
96                 MATCH_SINGLE_BINDING,
97                 target_span,
98                 "this match could be written as a `let` statement",
99                 "consider using `let` statement",
100                 sugg,
101                 applicability,
102             );
103         },
104         PatKind::Wild => {
105             if ex.can_have_side_effects() {
106                 let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
107                 let sugg = format!(
108                     "{};\n{}{}",
109                     snippet_with_applicability(cx, ex.span, "..", &mut applicability),
110                     indent,
111                     snippet_body
112                 );
113                 span_lint_and_sugg(
114                     cx,
115                     MATCH_SINGLE_BINDING,
116                     expr.span,
117                     "this match could be replaced by its scrutinee and body",
118                     "consider using the scrutinee and body instead",
119                     sugg,
120                     applicability,
121                 );
122             } else {
123                 span_lint_and_sugg(
124                     cx,
125                     MATCH_SINGLE_BINDING,
126                     expr.span,
127                     "this match could be replaced by its body itself",
128                     "consider using the match body instead",
129                     snippet_body,
130                     Applicability::MachineApplicable,
131                 );
132             }
133         },
134         _ => (),
135     }
136 }
137
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();
141     if_chain! {
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));
144         then {
145             return Some(parent_let_expr);
146         }
147     }
148     None
149 }