1 use super::REDUNDANT_PATTERN_MATCHING;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::source::snippet;
4 use clippy_utils::sugg::Sugg;
5 use clippy_utils::ty::needs_ordered_drop;
6 use clippy_utils::visitors::any_temporaries_need_ordered_drop;
7 use clippy_utils::{higher, is_lang_ctor, is_trait_method, match_def_path, paths};
8 use if_chain::if_chain;
9 use rustc_ast::ast::LitKind;
10 use rustc_errors::Applicability;
11 use rustc_hir::LangItem::{OptionNone, PollPending};
12 use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
13 use rustc_lint::LateContext;
14 use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
17 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
18 if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
19 find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
23 pub(super) fn check_if_let<'tcx>(
24 cx: &LateContext<'tcx>,
27 scrutinee: &'tcx Expr<'_>,
30 find_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else);
33 // Extract the generic arguments out of a type
34 fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
36 if let ty::Adt(_, subs) = ty.kind();
37 if let Some(sub) = subs.get(index);
38 if let GenericArgKind::Type(sub_ty) = sub.unpack();
47 fn find_sugg_for_if_let<'tcx>(
48 cx: &LateContext<'tcx>,
51 let_expr: &'tcx Expr<'_>,
52 keyword: &'static str,
55 // also look inside refs
56 // if we have &None for example, peel it so we can detect "if let None = x"
57 let check_pat = match let_pat.kind {
58 PatKind::Ref(inner, _mutability) => inner,
61 let op_ty = cx.typeck_results().expr_ty(let_expr);
62 // Determine which function should be used, and the type contained by the corresponding
64 let (good_method, inner_ty) = match check_pat.kind {
65 PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
66 if let PatKind::Wild = sub_pat.kind {
67 let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
68 let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
69 let lang_items = cx.tcx.lang_items();
70 if Some(id) == lang_items.result_ok_variant() {
71 ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
72 } else if Some(id) == lang_items.result_err_variant() {
73 ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
74 } else if Some(id) == lang_items.option_some_variant() {
76 } else if Some(id) == lang_items.poll_ready_variant() {
78 } else if match_def_path(cx, id, &paths::IPADDR_V4) {
80 } else if match_def_path(cx, id, &paths::IPADDR_V6) {
89 PatKind::Path(ref path) => {
90 let method = if is_lang_ctor(cx, path, OptionNone) {
92 } else if is_lang_ctor(cx, path, PollPending) {
97 // `None` and `Pending` don't have an inner type.
98 (method, cx.tcx.types.unit)
103 // If this is the last expression in a block or there is an else clause then the whole
104 // type needs to be considered, not just the inner type of the branch being matched on.
105 // Note the last expression in a block is dropped after all local bindings.
106 let check_ty = if has_else
107 || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
114 // All temporaries created in the scrutinee expression are dropped at the same time as the
115 // scrutinee would be, so they have to be considered as well.
116 // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
117 // for the duration if body.
118 let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr);
120 // check that `while_let_on_iterator` lint does not trigger
122 if keyword == "while";
123 if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind;
124 if method_path.ident.name == sym::next;
125 if is_trait_method(cx, let_expr, sym::Iterator);
131 let result_expr = match &let_expr.kind {
132 ExprKind::AddrOf(_, _, borrowed) => borrowed,
133 ExprKind::Unary(UnOp::Deref, deref) => deref,
139 REDUNDANT_PATTERN_MATCHING,
141 &format!("redundant pattern matching, consider using `{}`", good_method),
143 // if/while let ... = ... { ... }
144 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
145 let expr_span = expr.span;
147 // if/while let ... = ... { ... }
149 let op_span = result_expr.span.source_callsite();
151 // if/while let ... = ... { ... }
152 // ^^^^^^^^^^^^^^^^^^^
153 let span = expr_span.until(op_span.shrink_to_hi());
155 let app = if needs_drop {
156 Applicability::MaybeIncorrect
158 Applicability::MachineApplicable
161 let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
165 diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
168 diag.note("this will change drop order of the result, as well as all temporaries");
169 diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
175 pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
177 let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
179 let found_good_method = match node_pair {
181 PatKind::TupleStruct(ref path_left, patterns_left, _),
182 PatKind::TupleStruct(ref path_right, patterns_right, _),
183 ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
184 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
185 find_good_method_for_match(
196 find_good_method_for_match(
211 (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
212 | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
213 if patterns.len() == 1 =>
215 if let PatKind::Wild = patterns[0].kind {
216 find_good_method_for_match(
227 find_good_method_for_match(
233 &paths::POLL_PENDING,
245 if let Some(good_method) = found_good_method {
246 let span = expr.span.to(op.span);
247 let result_expr = match &op.kind {
248 ExprKind::AddrOf(_, _, borrowed) => borrowed,
253 REDUNDANT_PATTERN_MATCHING,
255 &format!("redundant pattern matching, consider using `{}`", good_method),
257 diag.span_suggestion(
260 format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
261 Applicability::MaybeIncorrect, // snippet
269 #[expect(clippy::too_many_arguments)]
270 fn find_good_method_for_match<'a>(
271 cx: &LateContext<'_>,
273 path_left: &QPath<'_>,
274 path_right: &QPath<'_>,
275 expected_left: &[&str],
276 expected_right: &[&str],
277 should_be_left: &'a str,
278 should_be_right: &'a str,
279 ) -> Option<&'a str> {
282 .qpath_res(path_left, arms[0].pat.hir_id)
286 .qpath_res(path_right, arms[1].pat.hir_id)
288 let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
289 (&arms[0].body.kind, &arms[1].body.kind)
290 } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
291 (&arms[1].body.kind, &arms[0].body.kind)
296 match body_node_pair {
297 (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
298 (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
299 (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),