Skip to content

Commit 5a2b7f0

Browse files
committed
Always trigger redundant_pattern_matching, but with a note when drop order might matter.
Will now also check if the drop order of temporaries might matter.
1 parent 7e59dbc commit 5a2b7f0

10 files changed

+505
-68
lines changed

clippy_lints/src/matches.rs

Lines changed: 222 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,12 @@ declare_clippy_lint! {
418418
/// **Why is this bad?** It's more concise and clear to just use the proper
419419
/// utility function
420420
///
421-
/// **Known problems:** None.
421+
/// **Known problems:** This will change the drop order for the matched type. Both `if let` and
422+
/// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
423+
/// value before entering the block. For most types this change will not matter, but for a few
424+
/// types this will not be an acceptable change (e.g. locks). See the
425+
/// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
426+
/// drop order.
422427
///
423428
/// **Example:**
424429
///
@@ -1553,50 +1558,214 @@ where
15531558

15541559
mod redundant_pattern_match {
15551560
use super::REDUNDANT_PATTERN_MATCHING;
1556-
use crate::utils::{match_qpath, match_trait_method, paths, snippet, span_lint_and_then};
1561+
use crate::utils::{
1562+
implements_trait, is_type_diagnostic_item, is_type_lang_item, match_qpath, match_trait_method, paths, snippet,
1563+
snippet_with_applicability, span_lint_and_then,
1564+
};
15571565
use if_chain::if_chain;
15581566
use rustc_ast::ast::LitKind;
15591567
use rustc_errors::Applicability;
1560-
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath};
1568+
use rustc_hir::{Arm, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath, UnOp};
15611569
use rustc_lint::LateContext;
1570+
use rustc_middle::ty::{self, subst::GenericArgKind, Ty, TyCtxt, TypeckResults};
15621571
use rustc_span::sym;
15631572

15641573
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
15651574
if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
15661575
match match_source {
15671576
MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
1568-
MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, &arms[0], "if"),
1569-
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while"),
1577+
MatchSource::IfLetDesugar { contains_else_clause } => {
1578+
find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause)
1579+
},
1580+
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
15701581
_ => {},
15711582
}
15721583
}
15731584
}
15741585

1586+
// Check if the drop order for a type matters
1587+
fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
1588+
if !ty.needs_drop(cx.tcx, cx.param_env) {
1589+
return false;
1590+
}
1591+
if cx
1592+
.tcx
1593+
.lang_items()
1594+
.drop_trait()
1595+
.map_or(false, |id| !implements_trait(cx, ty, id, &[]))
1596+
{
1597+
// This type doesn't implement drop, so no side effects here.
1598+
// Check if any component type has any.
1599+
return match ty.kind() {
1600+
ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)),
1601+
ty::Array(ty, _) => type_needs_ordered_drop(cx, ty),
1602+
ty::Adt(adt, subs) => adt
1603+
.all_fields()
1604+
.map(|f| f.ty(cx.tcx, subs))
1605+
.any(|ty| type_needs_ordered_drop(cx, ty)),
1606+
_ => true,
1607+
};
1608+
}
1609+
1610+
// Check for std types which implement drop, but only for memory allocation.
1611+
!((is_type_diagnostic_item(cx, ty, sym::vec_type)
1612+
&& !try_get_generic_ty(ty, 0).map_or(false, |ty| type_needs_ordered_drop(cx, ty)))
1613+
|| (is_type_lang_item(cx, ty, LangItem::OwnedBox)
1614+
&& !try_get_generic_ty(ty, 0).map_or(false, |ty| type_needs_ordered_drop(cx, ty)))
1615+
|| (is_type_diagnostic_item(cx, ty, sym::option_type)
1616+
&& !try_get_generic_ty(ty, 0).map_or(false, |ty| type_needs_ordered_drop(cx, ty)))
1617+
|| (is_type_diagnostic_item(cx, ty, sym::Rc)
1618+
&& !try_get_generic_ty(ty, 0).map_or(false, |ty| type_needs_ordered_drop(cx, ty)))
1619+
|| (is_type_diagnostic_item(cx, ty, sym::Arc)
1620+
&& !try_get_generic_ty(ty, 0).map_or(false, |ty| type_needs_ordered_drop(cx, ty))))
1621+
}
1622+
1623+
// Extract the generic arguments out of a type
1624+
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
1625+
if_chain! {
1626+
if let ty::Adt(_, subs) = ty.kind();
1627+
if let Some(sub) = subs.get(index);
1628+
if let GenericArgKind::Type(sub_ty) = sub.unpack();
1629+
then {
1630+
Some(sub_ty)
1631+
} else {
1632+
None
1633+
}
1634+
}
1635+
}
1636+
1637+
// Gets all the the temporaries in an expression which need to be dropped afterwards.
1638+
fn get_temporary_tys(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<Ty<'tcx>> {
1639+
fn f(
1640+
addr_of: bool,
1641+
tcx: TyCtxt<'tcx>,
1642+
typeck: &TypeckResults<'tcx>,
1643+
expr: &'tcx Expr<'tcx>,
1644+
res: &mut Vec<Ty<'tcx>>,
1645+
) {
1646+
match expr.kind {
1647+
ExprKind::ConstBlock(_)
1648+
| ExprKind::Tup([])
1649+
| ExprKind::Array([])
1650+
| ExprKind::Lit(_)
1651+
| ExprKind::Path(_)
1652+
| ExprKind::Assign(..)
1653+
| ExprKind::AssignOp(..)
1654+
| ExprKind::Break(..)
1655+
| ExprKind::Continue(_)
1656+
| ExprKind::Ret(_)
1657+
| ExprKind::InlineAsm(_)
1658+
| ExprKind::LlvmInlineAsm(_)
1659+
| ExprKind::Yield(..)
1660+
| ExprKind::Err
1661+
| ExprKind::MethodCall(_, _, [], _) => (),
1662+
ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
1663+
if addr_of {
1664+
res.push(typeck.expr_ty(expr));
1665+
}
1666+
let ref_self = typeck
1667+
.type_dependent_def_id(expr.hir_id)
1668+
.map_or(false, |id| tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
1669+
f(ref_self, tcx, typeck, self_arg, res);
1670+
for arg in args {
1671+
f(false, tcx, typeck, arg, res);
1672+
}
1673+
},
1674+
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => f(true, tcx, typeck, expr, res),
1675+
ExprKind::Box(expr) | ExprKind::Cast(expr, _) => f(false, tcx, typeck, expr, res),
1676+
ExprKind::Array(exprs @ [_, ..]) | ExprKind::Tup(exprs @ [_, ..]) | ExprKind::Call(_, exprs) => {
1677+
if addr_of {
1678+
res.push(typeck.expr_ty(expr));
1679+
}
1680+
for expr in exprs {
1681+
f(false, tcx, typeck, expr, res);
1682+
}
1683+
},
1684+
ExprKind::Binary(_, lhs, rhs) => {
1685+
if addr_of {
1686+
res.push(typeck.expr_ty(expr));
1687+
}
1688+
f(false, tcx, typeck, lhs, res);
1689+
f(false, tcx, typeck, rhs, res);
1690+
},
1691+
ExprKind::Unary(UnOp::UnDeref, e) => {
1692+
f(typeck.expr_ty(e).is_ref(), tcx, typeck, e, res);
1693+
},
1694+
ExprKind::Type(e, _) | ExprKind::Unary(_, e) => {
1695+
if addr_of {
1696+
res.push(typeck.expr_ty(expr));
1697+
}
1698+
f(false, tcx, typeck, e, res);
1699+
},
1700+
ExprKind::Index(base, index) => {
1701+
f(true, tcx, typeck, base, res);
1702+
f(false, tcx, typeck, index, res);
1703+
},
1704+
ExprKind::DropTemps(_) | ExprKind::Closure(..) => {
1705+
if addr_of {
1706+
res.push(typeck.expr_ty(expr));
1707+
}
1708+
},
1709+
ExprKind::Match(e, _, _) => {
1710+
if addr_of {
1711+
res.push(typeck.expr_ty(expr));
1712+
}
1713+
f(true, tcx, typeck, e, res);
1714+
},
1715+
ExprKind::Block(b, _) | ExprKind::Loop(b, _, _) => {
1716+
if addr_of {
1717+
res.push(typeck.expr_ty(expr));
1718+
}
1719+
if let Some(expr) = b.expr {
1720+
f(false, tcx, typeck, expr, res);
1721+
}
1722+
},
1723+
ExprKind::Struct(_, fields, _) => {
1724+
if addr_of {
1725+
res.push(typeck.expr_ty(expr));
1726+
}
1727+
for field in fields {
1728+
f(false, tcx, typeck, field.expr, res);
1729+
}
1730+
},
1731+
ExprKind::Repeat(expr, _) => {
1732+
if addr_of {
1733+
res.push(typeck.expr_ty(expr));
1734+
}
1735+
f(false, tcx, typeck, expr, res);
1736+
},
1737+
}
1738+
}
1739+
1740+
let mut res = Vec::new();
1741+
f(false, cx.tcx, cx.typeck_results(), expr, &mut res);
1742+
res
1743+
}
1744+
15751745
fn find_sugg_for_if_let<'tcx>(
15761746
cx: &LateContext<'tcx>,
15771747
expr: &'tcx Expr<'_>,
1578-
op: &Expr<'_>,
1748+
op: &'tcx Expr<'tcx>,
15791749
arm: &Arm<'_>,
15801750
keyword: &'static str,
1751+
has_else: bool,
15811752
) {
1582-
let good_method = match arm.pat.kind {
1753+
let op_ty = cx.typeck_results().expr_ty(op);
1754+
let (good_method, inner_ty) = match arm.pat.kind {
15831755
PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => {
15841756
if let PatKind::Wild = patterns[0].kind {
1585-
if cx.typeck_results().expr_ty(op).needs_drop(cx.tcx, cx.param_env) {
1586-
// if let doesn't drop the value until after the block ends
1587-
return;
1588-
} else if match_qpath(path, &paths::RESULT_OK) {
1589-
"is_ok()"
1757+
if match_qpath(path, &paths::RESULT_OK) {
1758+
("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
15901759
} else if match_qpath(path, &paths::RESULT_ERR) {
1591-
"is_err()"
1760+
("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
15921761
} else if match_qpath(path, &paths::OPTION_SOME) {
1593-
"is_some()"
1762+
("is_some()", op_ty)
15941763
} else if match_qpath(path, &paths::POLL_READY) {
1595-
"is_ready()"
1764+
("is_ready()", op_ty)
15961765
} else if match_qpath(path, &paths::IPADDR_V4) {
1597-
"is_ipv4()"
1766+
("is_ipv4()", op_ty)
15981767
} else if match_qpath(path, &paths::IPADDR_V6) {
1599-
"is_ipv6()"
1768+
("is_ipv6()", op_ty)
16001769
} else {
16011770
return;
16021771
}
@@ -1605,17 +1774,37 @@ mod redundant_pattern_match {
16051774
}
16061775
},
16071776
PatKind::Path(ref path) => {
1608-
if match_qpath(path, &paths::OPTION_NONE) {
1777+
let method = if match_qpath(path, &paths::OPTION_NONE) {
16091778
"is_none()"
16101779
} else if match_qpath(path, &paths::POLL_PENDING) {
16111780
"is_pending()"
16121781
} else {
16131782
return;
1614-
}
1783+
};
1784+
// drop for `None` and `Pending` do nothing
1785+
(method, cx.tcx.types.unit)
16151786
},
16161787
_ => return,
16171788
};
16181789

1790+
// if this is the last expression in a block then the lifetime of the expression is extended
1791+
// past the end of the block
1792+
let check_ty = if has_else
1793+
|| (keyword == "if" && {
1794+
let map = cx.tcx.hir();
1795+
let id = map.get_parent_node(expr.hir_id);
1796+
id != expr.hir_id && matches!(map.find(id), Some(Node::Block(..)))
1797+
}) {
1798+
op_ty
1799+
} else {
1800+
inner_ty
1801+
};
1802+
1803+
let needs_drop = type_needs_ordered_drop(cx, check_ty)
1804+
|| get_temporary_tys(cx, op)
1805+
.iter()
1806+
.any(|ty| type_needs_ordered_drop(cx, ty));
1807+
16191808
// check that `while_let_on_iterator` lint does not trigger
16201809
if_chain! {
16211810
if keyword == "while";
@@ -1648,12 +1837,20 @@ mod redundant_pattern_match {
16481837
// while let ... = ... { ... }
16491838
// ^^^^^^^^^^^^^^^^^^^
16501839
let span = expr_span.until(op_span.shrink_to_hi());
1651-
diag.span_suggestion(
1652-
span,
1653-
"try this",
1654-
format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
1655-
Applicability::MachineApplicable, // snippet
1656-
);
1840+
1841+
let mut app = if needs_drop {
1842+
Applicability::MaybeIncorrect
1843+
} else {
1844+
Applicability::MachineApplicable
1845+
};
1846+
let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
1847+
1848+
diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
1849+
1850+
if needs_drop {
1851+
diag.note("this will change drop order of the result, as well as all temporaries");
1852+
diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
1853+
}
16571854
},
16581855
);
16591856
}

tests/ui/redundant_pattern_matching_option.fixed

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,22 @@ fn main() {
5050
};
5151

5252
// Issue #5746
53-
if let Some(_) = Some(vec![0]) {}
54-
if Some(vec![0]).is_none() {}
53+
let m = std::sync::Mutex::new((0, 0));
54+
55+
if Some(m.lock()).is_some() {}
56+
if Some(m.lock().unwrap().0).is_some() {}
57+
58+
{
59+
if None::<std::sync::MutexGuard<()>>.is_none() {}
60+
}
61+
if None::<std::sync::MutexGuard<()>>.is_none() {
62+
} else {
63+
}
64+
65+
if None::<std::sync::MutexGuard<()>>.is_none() {}
66+
67+
if Some(String::new()).is_some() {}
68+
if Some((String::new(), ())).is_some() {}
5569
}
5670

5771
fn gen_opt() -> Option<()> {

tests/ui/redundant_pattern_matching_option.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,22 @@ fn main() {
5959
};
6060

6161
// Issue #5746
62-
if let Some(_) = Some(vec![0]) {}
63-
if let None = Some(vec![0]) {}
62+
let m = std::sync::Mutex::new((0, 0));
63+
64+
if let Some(_) = Some(m.lock()) {}
65+
if let Some(_) = Some(m.lock().unwrap().0) {}
66+
67+
{
68+
if let None = None::<std::sync::MutexGuard<()>> {}
69+
}
70+
if let None = None::<std::sync::MutexGuard<()>> {
71+
} else {
72+
}
73+
74+
if let None = None::<std::sync::MutexGuard<()>> {}
75+
76+
if let Some(_) = Some(String::new()) {}
77+
if let Some(_) = Some((String::new(), ())) {}
6478
}
6579

6680
fn gen_opt() -> Option<()> {

0 commit comments

Comments
 (0)