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::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
6 use clippy_utils::{higher, match_def_path};
7 use clippy_utils::{is_lang_ctor, is_trait_method, paths};
8 use if_chain::if_chain;
9 use rustc_ast::ast::LitKind;
10 use rustc_data_structures::fx::FxHashSet;
11 use rustc_errors::Applicability;
12 use rustc_hir::LangItem::{OptionNone, PollPending};
14 intravisit::{walk_expr, Visitor},
15 Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp,
17 use rustc_lint::LateContext;
18 use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
21 pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
22 if let Some(higher::IfLet {
27 }) = higher::IfLet::hir(cx, expr)
29 find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
31 if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind {
32 find_sugg_for_match(cx, expr, op, arms);
34 if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
35 find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
39 /// Checks if the drop order for a type matters. Some std types implement drop solely to
40 /// deallocate memory. For these types, and composites containing them, changing the drop order
41 /// won't result in any observable side effects.
42 fn type_needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
43 type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
46 fn type_needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
50 if !ty.needs_drop(cx.tcx, cx.param_env) {
56 .map_or(false, |id| implements_trait(cx, ty, id, &[]))
58 // This type doesn't implement drop, so no side effects here.
59 // Check if any component type has any.
61 ty::Tuple(fields) => fields.iter().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
62 &ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen),
63 ty::Adt(adt, subs) => adt
65 .map(|f| f.ty(cx.tcx, subs))
66 .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
70 // Check for std types which implement drop, but only for memory allocation.
71 else if is_type_diagnostic_item(cx, ty, sym::Vec)
72 || is_type_lang_item(cx, ty, LangItem::OwnedBox)
73 || is_type_diagnostic_item(cx, ty, sym::Rc)
74 || is_type_diagnostic_item(cx, ty, sym::Arc)
75 || is_type_diagnostic_item(cx, ty, sym::cstring_type)
76 || is_type_diagnostic_item(cx, ty, sym::BTreeMap)
77 || is_type_diagnostic_item(cx, ty, sym::LinkedList)
78 || match_type(cx, ty, &paths::WEAK_RC)
79 || match_type(cx, ty, &paths::WEAK_ARC)
81 // Check all of the generic arguments.
82 if let ty::Adt(_, subs) = ty.kind() {
83 subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen))
92 // Extract the generic arguments out of a type
93 fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
95 if let ty::Adt(_, subs) = ty.kind();
96 if let Some(sub) = subs.get(index);
97 if let GenericArgKind::Type(sub_ty) = sub.unpack();
106 // Checks if there are any temporaries created in the given expression for which drop order
108 fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
110 cx: &'a LateContext<'tcx>,
113 impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
114 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
116 // Taking the reference of a value leaves a temporary
117 // e.g. In `&String::new()` the string is a temporary value.
118 // Remaining fields are temporary values
119 // e.g. In `(String::new(), 0).1` the string is a temporary value.
120 ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
121 if !matches!(expr.kind, ExprKind::Path(_)) {
122 if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
125 self.visit_expr(expr);
129 // the base type is alway taken by reference.
130 // e.g. In `(vec![0])[0]` the vector is a temporary value.
131 ExprKind::Index(base, index) => {
132 if !matches!(base.kind, ExprKind::Path(_)) {
133 if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
136 self.visit_expr(base);
139 self.visit_expr(index);
141 // Method calls can take self by reference.
142 // e.g. In `String::new().len()` the string is a temporary value.
143 ExprKind::MethodCall(_, [self_arg, args @ ..], _) => {
144 if !matches!(self_arg.kind, ExprKind::Path(_)) {
145 let self_by_ref = self
148 .type_dependent_def_id(expr.hir_id)
149 .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
150 if self_by_ref && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
153 self.visit_expr(self_arg);
156 args.iter().for_each(|arg| self.visit_expr(arg));
158 // Either explicitly drops values, or changes control flow.
159 ExprKind::DropTemps(_)
161 | ExprKind::Break(..)
162 | ExprKind::Yield(..)
163 | ExprKind::Block(Block { expr: None, .. }, _)
164 | ExprKind::Loop(..) => (),
166 // Only consider the final expression.
167 ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
169 _ => walk_expr(self, expr),
174 let mut v = V { cx, res: false };
179 fn find_sugg_for_if_let<'tcx>(
180 cx: &LateContext<'tcx>,
181 expr: &'tcx Expr<'_>,
183 let_expr: &'tcx Expr<'_>,
184 keyword: &'static str,
187 // also look inside refs
188 // if we have &None for example, peel it so we can detect "if let None = x"
189 let check_pat = match let_pat.kind {
190 PatKind::Ref(inner, _mutability) => inner,
193 let op_ty = cx.typeck_results().expr_ty(let_expr);
194 // Determine which function should be used, and the type contained by the corresponding
196 let (good_method, inner_ty) = match check_pat.kind {
197 PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
198 if let PatKind::Wild = sub_pat.kind {
199 let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
200 let Some(id) = res.opt_def_id().and_then(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
201 let lang_items = cx.tcx.lang_items();
202 if Some(id) == lang_items.result_ok_variant() {
203 ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
204 } else if Some(id) == lang_items.result_err_variant() {
205 ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
206 } else if Some(id) == lang_items.option_some_variant() {
208 } else if Some(id) == lang_items.poll_ready_variant() {
209 ("is_ready()", op_ty)
210 } else if match_def_path(cx, id, &paths::IPADDR_V4) {
212 } else if match_def_path(cx, id, &paths::IPADDR_V6) {
221 PatKind::Path(ref path) => {
222 let method = if is_lang_ctor(cx, path, OptionNone) {
224 } else if is_lang_ctor(cx, path, PollPending) {
229 // `None` and `Pending` don't have an inner type.
230 (method, cx.tcx.types.unit)
235 // If this is the last expression in a block or there is an else clause then the whole
236 // type needs to be considered, not just the inner type of the branch being matched on.
237 // Note the last expression in a block is dropped after all local bindings.
238 let check_ty = if has_else
239 || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
246 // All temporaries created in the scrutinee expression are dropped at the same time as the
247 // scrutinee would be, so they have to be considered as well.
248 // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
249 // for the duration if body.
250 let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
252 // check that `while_let_on_iterator` lint does not trigger
254 if keyword == "while";
255 if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind;
256 if method_path.ident.name == sym::next;
257 if is_trait_method(cx, let_expr, sym::Iterator);
263 let result_expr = match &let_expr.kind {
264 ExprKind::AddrOf(_, _, borrowed) => borrowed,
265 ExprKind::Unary(UnOp::Deref, deref) => deref,
271 REDUNDANT_PATTERN_MATCHING,
273 &format!("redundant pattern matching, consider using `{}`", good_method),
275 // if/while let ... = ... { ... }
276 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
277 let expr_span = expr.span;
279 // if/while let ... = ... { ... }
281 let op_span = result_expr.span.source_callsite();
283 // if/while let ... = ... { ... }
284 // ^^^^^^^^^^^^^^^^^^^
285 let span = expr_span.until(op_span.shrink_to_hi());
287 let app = if needs_drop {
288 Applicability::MaybeIncorrect
290 Applicability::MachineApplicable
293 let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
297 diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
300 diag.note("this will change drop order of the result, as well as all temporaries");
301 diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
307 fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
309 let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
311 let found_good_method = match node_pair {
313 PatKind::TupleStruct(ref path_left, patterns_left, _),
314 PatKind::TupleStruct(ref path_right, patterns_right, _),
315 ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
316 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
317 find_good_method_for_match(
328 find_good_method_for_match(
343 (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
344 | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
345 if patterns.len() == 1 =>
347 if let PatKind::Wild = patterns[0].kind {
348 find_good_method_for_match(
359 find_good_method_for_match(
365 &paths::POLL_PENDING,
377 if let Some(good_method) = found_good_method {
378 let span = expr.span.to(op.span);
379 let result_expr = match &op.kind {
380 ExprKind::AddrOf(_, _, borrowed) => borrowed,
385 REDUNDANT_PATTERN_MATCHING,
387 &format!("redundant pattern matching, consider using `{}`", good_method),
389 diag.span_suggestion(
392 format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
393 Applicability::MaybeIncorrect, // snippet
401 #[allow(clippy::too_many_arguments)]
402 fn find_good_method_for_match<'a>(
403 cx: &LateContext<'_>,
405 path_left: &QPath<'_>,
406 path_right: &QPath<'_>,
407 expected_left: &[&str],
408 expected_right: &[&str],
409 should_be_left: &'a str,
410 should_be_right: &'a str,
411 ) -> Option<&'a str> {
414 .qpath_res(path_left, arms[0].pat.hir_id)
418 .qpath_res(path_right, arms[1].pat.hir_id)
420 let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
421 (&(*arms[0].body).kind, &(*arms[1].body).kind)
422 } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
423 (&(*arms[1].body).kind, &(*arms[0].body).kind)
428 match body_node_pair {
429 (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
430 (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
431 (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),