Skip to content

Commit 946810a

Browse files
committed
Always trigger redundant_pattern_matching, but with a note when drop order might matter.
1 parent ebc6469 commit 946810a

13 files changed

+598
-130
lines changed

clippy_lints/src/matches.rs

Lines changed: 222 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,12 @@ declare_clippy_lint! {
421421
/// **Why is this bad?** It's more concise and clear to just use the proper
422422
/// utility function
423423
///
424-
/// **Known problems:** None.
424+
/// **Known problems:** This will change the drop order for the matched type. Both `if let` and
425+
/// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
426+
/// value before entering the block. For most types this change will not matter, but for a few
427+
/// types this will not be an acceptable change (e.g. locks). See the
428+
/// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
429+
/// drop order.
425430
///
426431
/// **Example:**
427432
///
@@ -1701,57 +1706,219 @@ where
17011706
mod redundant_pattern_match {
17021707
use super::REDUNDANT_PATTERN_MATCHING;
17031708
use clippy_utils::diagnostics::span_lint_and_then;
1704-
use clippy_utils::source::snippet;
1705-
use clippy_utils::{is_trait_method, match_qpath, paths};
1709+
use clippy_utils::source::{snippet, snippet_with_applicability};
1710+
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item};
1711+
use clippy_utils::{is_trait_method, match_qpath, match_trait_method, paths};
17061712
use if_chain::if_chain;
17071713
use rustc_ast::ast::LitKind;
17081714
use rustc_errors::Applicability;
1709-
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath};
1715+
use rustc_hir::{Arm, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath, UnOp};
17101716
use rustc_lint::LateContext;
1717+
use rustc_middle::ty::{self, subst::GenericArgKind, Ty, TyCtxt, TypeckResults};
17111718
use rustc_span::sym;
17121719

17131720
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
17141721
if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
17151722
match match_source {
17161723
MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
1717-
MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, &arms[0], "if"),
1718-
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while"),
1724+
MatchSource::IfLetDesugar { contains_else_clause } => {
1725+
find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause)
1726+
},
1727+
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
17191728
_ => {},
17201729
}
17211730
}
17221731
}
17231732

1733+
// Check if the drop order for a type matters
1734+
fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
1735+
if !ty.needs_drop(cx.tcx, cx.param_env) {
1736+
return false;
1737+
}
1738+
if cx
1739+
.tcx
1740+
.lang_items()
1741+
.drop_trait()
1742+
.map_or(false, |id| !implements_trait(cx, ty, id, &[]))
1743+
{
1744+
// This type doesn't implement drop, so no side effects here.
1745+
// Check if any component type has any.
1746+
return match ty.kind() {
1747+
ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)),
1748+
ty::Array(ty, _) => type_needs_ordered_drop(cx, ty),
1749+
ty::Adt(adt, subs) => adt
1750+
.all_fields()
1751+
.map(|f| f.ty(cx.tcx, subs))
1752+
.any(|ty| type_needs_ordered_drop(cx, ty)),
1753+
_ => true,
1754+
};
1755+
}
1756+
1757+
// Check for std types which implement drop, but only for memory allocation.
1758+
if is_type_diagnostic_item(cx, ty, sym::vec_type)
1759+
|| is_type_lang_item(cx, ty, LangItem::OwnedBox)
1760+
|| is_type_diagnostic_item(cx, ty, sym::Rc)
1761+
|| is_type_diagnostic_item(cx, ty, sym::Arc)
1762+
{
1763+
try_get_generic_ty(ty, 0).map_or(true, |ty| type_needs_ordered_drop(cx, ty))
1764+
} else {
1765+
true
1766+
}
1767+
}
1768+
1769+
// Extract the generic arguments out of a type
1770+
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
1771+
if_chain! {
1772+
if let ty::Adt(_, subs) = ty.kind();
1773+
if let Some(sub) = subs.get(index);
1774+
if let GenericArgKind::Type(sub_ty) = sub.unpack();
1775+
then {
1776+
Some(sub_ty)
1777+
} else {
1778+
None
1779+
}
1780+
}
1781+
}
1782+
1783+
// Gets all the the temporaries in an expression which need to be dropped afterwards.
1784+
#[allow(clippy::too_many_lines)]
1785+
fn get_temporary_tys(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<Ty<'tcx>> {
1786+
fn f(
1787+
addr_of: bool,
1788+
tcx: TyCtxt<'tcx>,
1789+
typeck: &TypeckResults<'tcx>,
1790+
expr: &'tcx Expr<'tcx>,
1791+
result: &mut Vec<Ty<'tcx>>,
1792+
) {
1793+
match expr.kind {
1794+
ExprKind::ConstBlock(_)
1795+
| ExprKind::Tup([])
1796+
| ExprKind::Array([])
1797+
| ExprKind::Lit(_)
1798+
| ExprKind::Path(_)
1799+
| ExprKind::Assign(..)
1800+
| ExprKind::AssignOp(..)
1801+
| ExprKind::Break(..)
1802+
| ExprKind::Continue(_)
1803+
| ExprKind::Ret(_)
1804+
| ExprKind::InlineAsm(_)
1805+
| ExprKind::LlvmInlineAsm(_)
1806+
| ExprKind::Yield(..)
1807+
| ExprKind::Err
1808+
| ExprKind::MethodCall(_, _, [], _) => (),
1809+
ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
1810+
if addr_of {
1811+
result.push(typeck.expr_ty(expr));
1812+
}
1813+
let ref_self = typeck
1814+
.type_dependent_def_id(expr.hir_id)
1815+
.map_or(false, |id| tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
1816+
f(ref_self, tcx, typeck, self_arg, result);
1817+
for arg in args {
1818+
f(false, tcx, typeck, arg, result);
1819+
}
1820+
},
1821+
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => f(true, tcx, typeck, expr, result),
1822+
ExprKind::Box(expr) | ExprKind::Cast(expr, _) => f(false, tcx, typeck, expr, result),
1823+
ExprKind::Array(exprs @ [_, ..]) | ExprKind::Tup(exprs @ [_, ..]) | ExprKind::Call(_, exprs) => {
1824+
if addr_of {
1825+
result.push(typeck.expr_ty(expr));
1826+
}
1827+
for expr in exprs {
1828+
f(false, tcx, typeck, expr, result);
1829+
}
1830+
},
1831+
ExprKind::Binary(_, lhs, rhs) => {
1832+
if addr_of {
1833+
result.push(typeck.expr_ty(expr));
1834+
}
1835+
f(false, tcx, typeck, lhs, result);
1836+
f(false, tcx, typeck, rhs, result);
1837+
},
1838+
ExprKind::Unary(UnOp::UnDeref, e) => {
1839+
f(typeck.expr_ty(e).is_ref(), tcx, typeck, e, result);
1840+
},
1841+
ExprKind::Type(e, _) | ExprKind::Unary(_, e) => {
1842+
if addr_of {
1843+
result.push(typeck.expr_ty(expr));
1844+
}
1845+
f(false, tcx, typeck, e, result);
1846+
},
1847+
ExprKind::Index(base, index) => {
1848+
f(true, tcx, typeck, base, result);
1849+
f(false, tcx, typeck, index, result);
1850+
},
1851+
ExprKind::DropTemps(_) | ExprKind::Closure(..) => {
1852+
if addr_of {
1853+
result.push(typeck.expr_ty(expr));
1854+
}
1855+
},
1856+
ExprKind::Match(e, _, _) => {
1857+
if addr_of {
1858+
result.push(typeck.expr_ty(expr));
1859+
}
1860+
f(true, tcx, typeck, e, result);
1861+
},
1862+
ExprKind::Block(b, _) | ExprKind::Loop(b, _, _) => {
1863+
if addr_of {
1864+
result.push(typeck.expr_ty(expr));
1865+
}
1866+
if let Some(expr) = b.expr {
1867+
f(false, tcx, typeck, expr, result);
1868+
}
1869+
},
1870+
ExprKind::Struct(_, fields, _) => {
1871+
if addr_of {
1872+
result.push(typeck.expr_ty(expr));
1873+
}
1874+
for field in fields {
1875+
f(false, tcx, typeck, field.expr, result);
1876+
}
1877+
},
1878+
ExprKind::Repeat(expr, _) => {
1879+
if addr_of {
1880+
result.push(typeck.expr_ty(expr));
1881+
}
1882+
f(false, tcx, typeck, expr, result);
1883+
},
1884+
}
1885+
}
1886+
1887+
let mut res = Vec::new();
1888+
f(false, cx.tcx, cx.typeck_results(), expr, &mut res);
1889+
res
1890+
}
1891+
17241892
fn find_sugg_for_if_let<'tcx>(
17251893
cx: &LateContext<'tcx>,
17261894
expr: &'tcx Expr<'_>,
1727-
op: &Expr<'_>,
1895+
op: &'tcx Expr<'tcx>,
17281896
arm: &Arm<'_>,
17291897
keyword: &'static str,
1898+
has_else: bool,
17301899
) {
17311900
// also look inside refs
1732-
let mut kind = &arms[0].pat.kind;
1901+
let mut kind = arm.pat.kind;
17331902
// if we have &None for example, peel it so we can detect "if let None = x"
17341903
if let PatKind::Ref(inner, _mutability) = kind {
17351904
kind = &inner.kind;
17361905
}
1737-
let good_method = match kind {
1906+
let op_ty = cx.typeck_results().expr_ty(op);
1907+
let (good_method, inner_ty) = match kind {
17381908
PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => {
17391909
if let PatKind::Wild = patterns[0].kind {
1740-
if cx.typeck_results().expr_ty(op).needs_drop(cx.tcx, cx.param_env) {
1741-
// if let doesn't drop the value until after the block ends
1742-
return;
1743-
} else if match_qpath(path, &paths::RESULT_OK) {
1744-
"is_ok()"
1910+
if match_qpath(path, &paths::RESULT_OK) {
1911+
("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
17451912
} else if match_qpath(path, &paths::RESULT_ERR) {
1746-
"is_err()"
1913+
("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
17471914
} else if match_qpath(path, &paths::OPTION_SOME) {
1748-
"is_some()"
1915+
("is_some()", op_ty)
17491916
} else if match_qpath(path, &paths::POLL_READY) {
1750-
"is_ready()"
1917+
("is_ready()", op_ty)
17511918
} else if match_qpath(path, &paths::IPADDR_V4) {
1752-
"is_ipv4()"
1919+
("is_ipv4()", op_ty)
17531920
} else if match_qpath(path, &paths::IPADDR_V6) {
1754-
"is_ipv6()"
1921+
("is_ipv6()", op_ty)
17551922
} else {
17561923
return;
17571924
}
@@ -1760,17 +1927,37 @@ mod redundant_pattern_match {
17601927
}
17611928
},
17621929
PatKind::Path(ref path) => {
1763-
if match_qpath(path, &paths::OPTION_NONE) {
1930+
let method = if match_qpath(path, &paths::OPTION_NONE) {
17641931
"is_none()"
17651932
} else if match_qpath(path, &paths::POLL_PENDING) {
17661933
"is_pending()"
17671934
} else {
17681935
return;
1769-
}
1936+
};
1937+
// drop for `None` and `Pending` do nothing
1938+
(method, cx.tcx.types.unit)
17701939
},
17711940
_ => return,
17721941
};
17731942

1943+
// if this is the last expression in a block then the lifetime of the expression is extended
1944+
// past the end of the block
1945+
let check_ty = if has_else
1946+
|| (keyword == "if" && {
1947+
let map = cx.tcx.hir();
1948+
let id = map.get_parent_node(expr.hir_id);
1949+
id != expr.hir_id && matches!(map.find(id), Some(Node::Block(..)))
1950+
}) {
1951+
op_ty
1952+
} else {
1953+
inner_ty
1954+
};
1955+
1956+
let needs_drop = type_needs_ordered_drop(cx, check_ty)
1957+
|| get_temporary_tys(cx, op)
1958+
.iter()
1959+
.any(|ty| type_needs_ordered_drop(cx, ty));
1960+
17741961
// check that `while_let_on_iterator` lint does not trigger
17751962
if_chain! {
17761963
if keyword == "while";
@@ -1803,12 +1990,20 @@ mod redundant_pattern_match {
18031990
// while let ... = ... { ... }
18041991
// ^^^^^^^^^^^^^^^^^^^
18051992
let span = expr_span.until(op_span.shrink_to_hi());
1806-
diag.span_suggestion(
1807-
span,
1808-
"try this",
1809-
format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
1810-
Applicability::MachineApplicable, // snippet
1811-
);
1993+
1994+
let mut app = if needs_drop {
1995+
Applicability::MaybeIncorrect
1996+
} else {
1997+
Applicability::MachineApplicable
1998+
};
1999+
let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
2000+
2001+
diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
2002+
2003+
if needs_drop {
2004+
diag.note("this will change drop order of the result, as well as all temporaries");
2005+
diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
2006+
}
18122007
},
18132008
);
18142009
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// run-rustfix
2+
3+
// Issue #5746
4+
#![warn(clippy::redundant_pattern_matching)]
5+
#![allow(clippy::if_same_then_else)]
6+
use std::task::Poll::{Pending, Ready};
7+
8+
fn main() {
9+
let m = std::sync::Mutex::new((0, 0));
10+
11+
// Result
12+
if m.lock().is_ok() {}
13+
if Err::<(), _>(m.lock().unwrap().0).is_err() {}
14+
15+
{
16+
if Ok::<_, std::sync::MutexGuard<()>>(()).is_ok() {}
17+
}
18+
if Ok::<_, std::sync::MutexGuard<()>>(()).is_ok() {
19+
} else {
20+
}
21+
if Ok::<_, std::sync::MutexGuard<()>>(()).is_ok() {}
22+
if Err::<std::sync::MutexGuard<()>, _>(()).is_err() {}
23+
24+
if Ok::<_, ()>(String::new()).is_ok() {}
25+
if Err::<(), _>((String::new(), ())).is_err() {}
26+
27+
// Option
28+
if Some(m.lock()).is_some() {}
29+
if Some(m.lock().unwrap().0).is_some() {}
30+
31+
{
32+
if None::<std::sync::MutexGuard<()>>.is_none() {}
33+
}
34+
if None::<std::sync::MutexGuard<()>>.is_none() {
35+
} else {
36+
}
37+
38+
if None::<std::sync::MutexGuard<()>>.is_none() {}
39+
40+
if Some(String::new()).is_some() {}
41+
if Some((String::new(), ())).is_some() {}
42+
43+
// Poll
44+
if Ready(m.lock()).is_ready() {}
45+
if Ready(m.lock().unwrap().0).is_ready() {}
46+
47+
{
48+
if Pending::<std::sync::MutexGuard<()>>.is_pending() {}
49+
}
50+
if Pending::<std::sync::MutexGuard<()>>.is_pending() {
51+
} else {
52+
}
53+
54+
if Pending::<std::sync::MutexGuard<()>>.is_pending() {}
55+
56+
if Ready(String::new()).is_ready() {}
57+
if Ready((String::new(), ())).is_ready() {}
58+
}

0 commit comments

Comments
 (0)