1 use crate::utils::{in_constant, match_qpath, match_trait_method, paths, snippet, span_lint_and_then};
2 use if_chain::if_chain;
3 use rustc_ast::ast::LitKind;
4 use rustc_errors::Applicability;
5 use rustc_hir::{Arm, Expr, ExprKind, HirId, MatchSource, PatKind, QPath};
6 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_mir::const_eval::is_const_fn;
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 use rustc_span::source_map::Symbol;
12 declare_clippy_lint! {
13 /// **What it does:** Lint for redundant pattern matching over `Result` or
16 /// **Why is this bad?** It's more concise and clear to just use the proper
19 /// **Known problems:** None.
24 /// if let Ok(_) = Ok::<i32, i32>(42) {}
25 /// if let Err(_) = Err::<i32, i32>(42) {}
26 /// if let None = None::<()> {}
27 /// if let Some(_) = Some(42) {}
28 /// match Ok::<i32, i32>(42) {
34 /// The more idiomatic use would be:
37 /// if Ok::<i32, i32>(42).is_ok() {}
38 /// if Err::<i32, i32>(42).is_err() {}
39 /// if None::<()>.is_none() {}
40 /// if Some(42).is_some() {}
41 /// Ok::<i32, i32>(42).is_ok();
43 pub REDUNDANT_PATTERN_MATCHING,
45 "use the proper utility function avoiding an `if let`"
48 declare_lint_pass!(RedundantPatternMatching => [REDUNDANT_PATTERN_MATCHING]);
50 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantPatternMatching {
51 fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
52 if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
54 MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
55 MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"),
56 MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"),
63 fn find_sugg_for_if_let<'a, 'tcx>(
64 cx: &LateContext<'a, 'tcx>,
68 keyword: &'static str,
70 fn find_suggestion(cx: &LateContext<'_, '_>, hir_id: HirId, path: &QPath<'_>) -> Option<&'static str> {
71 if match_qpath(path, &paths::RESULT_OK) && can_suggest(cx, hir_id, sym!(result_type), "is_ok") {
72 return Some("is_ok()");
74 if match_qpath(path, &paths::RESULT_ERR) && can_suggest(cx, hir_id, sym!(result_type), "is_err") {
75 return Some("is_err()");
77 if match_qpath(path, &paths::OPTION_SOME) && can_suggest(cx, hir_id, sym!(option_type), "is_some") {
78 return Some("is_some()");
80 if match_qpath(path, &paths::OPTION_NONE) && can_suggest(cx, hir_id, sym!(option_type), "is_none") {
81 return Some("is_none()");
86 let hir_id = expr.hir_id;
87 let good_method = match arms[0].pat.kind {
88 PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => {
89 if let PatKind::Wild = patterns[0].kind {
90 find_suggestion(cx, hir_id, path)
95 PatKind::Path(ref path) => find_suggestion(cx, hir_id, path),
98 let good_method = match good_method {
99 Some(method) => method,
103 // check that `while_let_on_iterator` lint does not trigger
105 if keyword == "while";
106 if let ExprKind::MethodCall(method_path, _, _, _) = op.kind;
107 if method_path.ident.name == sym!(next);
108 if match_trait_method(cx, op, &paths::ITERATOR);
116 REDUNDANT_PATTERN_MATCHING,
118 &format!("redundant pattern matching, consider using `{}`", good_method),
120 // while let ... = ... { ... }
121 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
122 let expr_span = expr.span;
124 // while let ... = ... { ... }
126 let op_span = op.span.source_callsite();
128 // while let ... = ... { ... }
129 // ^^^^^^^^^^^^^^^^^^^
130 let span = expr_span.until(op_span.shrink_to_hi());
131 diag.span_suggestion(
134 format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
135 Applicability::MachineApplicable, // snippet
141 fn find_sugg_for_match<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
143 let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
145 let hir_id = expr.hir_id;
146 let found_good_method = match node_pair {
148 PatKind::TupleStruct(ref path_left, ref patterns_left, _),
149 PatKind::TupleStruct(ref path_right, ref patterns_right, _),
150 ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
151 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
152 find_good_method_for_match(
160 || can_suggest(cx, hir_id, sym!(result_type), "is_ok"),
161 || can_suggest(cx, hir_id, sym!(result_type), "is_err"),
167 (PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right))
168 | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _))
169 if patterns.len() == 1 =>
171 if let PatKind::Wild = patterns[0].kind {
172 find_good_method_for_match(
180 || can_suggest(cx, hir_id, sym!(option_type), "is_some"),
181 || can_suggest(cx, hir_id, sym!(option_type), "is_none"),
190 if let Some(good_method) = found_good_method {
193 REDUNDANT_PATTERN_MATCHING,
195 &format!("redundant pattern matching, consider using `{}`", good_method),
197 let span = expr.span.to(op.span);
198 diag.span_suggestion(
201 format!("{}.{}", snippet(cx, op.span, "_"), good_method),
202 Applicability::MaybeIncorrect, // snippet
210 #[allow(clippy::too_many_arguments)]
211 fn find_good_method_for_match<'a>(
213 path_left: &QPath<'_>,
214 path_right: &QPath<'_>,
215 expected_left: &[&str],
216 expected_right: &[&str],
217 should_be_left: &'a str,
218 should_be_right: &'a str,
219 can_suggest_left: impl Fn() -> bool,
220 can_suggest_right: impl Fn() -> bool,
221 ) -> Option<&'a str> {
222 let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) {
223 (&(*arms[0].body).kind, &(*arms[1].body).kind)
224 } else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) {
225 (&(*arms[1].body).kind, &(*arms[0].body).kind)
230 match body_node_pair {
231 (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
232 (LitKind::Bool(true), LitKind::Bool(false)) if can_suggest_left() => Some(should_be_left),
233 (LitKind::Bool(false), LitKind::Bool(true)) if can_suggest_right() => Some(should_be_right),
240 fn can_suggest(cx: &LateContext<'_, '_>, hir_id: HirId, diag_item: Symbol, name: &str) -> bool {
241 if !in_constant(cx, hir_id) {
245 // Avoid suggesting calls to non-`const fn`s in const contexts, see #5697.
247 .get_diagnostic_item(diag_item)
249 cx.tcx.inherent_impls(def_id).iter().find_map(|imp| {
251 .associated_items(*imp)
252 .in_definition_order()
253 .find_map(|item| match item.kind {
254 ty::AssocKind::Fn if item.ident.name.as_str() == name => Some(item.def_id),
259 .map_or(false, |def_id| is_const_fn(cx.tcx, def_id))