Skip to content

Commit b90cf7c

Browse files
committed
Improve redundant_pattern_matching
Add a note when the drop order change may result in different behaviour.
1 parent db6ea84 commit b90cf7c

13 files changed

+581
-91
lines changed

clippy_lints/src/matches.rs

Lines changed: 207 additions & 26 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
///
@@ -1698,54 +1703,203 @@ where
16981703
mod redundant_pattern_match {
16991704
use super::REDUNDANT_PATTERN_MATCHING;
17001705
use clippy_utils::diagnostics::span_lint_and_then;
1701-
use clippy_utils::source::snippet;
1706+
use clippy_utils::source::{snippet, snippet_with_applicability};
1707+
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
17021708
use clippy_utils::{is_trait_method, match_qpath, paths};
17031709
use if_chain::if_chain;
17041710
use rustc_ast::ast::LitKind;
17051711
use rustc_errors::Applicability;
1706-
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath};
1712+
use rustc_hir::{
1713+
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
1714+
Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath,
1715+
};
17071716
use rustc_lint::LateContext;
1717+
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
17081718
use rustc_span::sym;
17091719

17101720
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
17111721
if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
17121722
match match_source {
17131723
MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
1714-
MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"),
1715-
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "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),
17161728
_ => {},
17171729
}
17181730
}
17191731
}
17201732

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+
false
1737+
} else if cx
1738+
.tcx
1739+
.lang_items()
1740+
.drop_trait()
1741+
.map_or(false, |id| !implements_trait(cx, ty, id, &[]))
1742+
{
1743+
// This type doesn't implement drop, so no side effects here.
1744+
// Check if any component type has any.
1745+
match ty.kind() {
1746+
ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)),
1747+
ty::Array(ty, _) => type_needs_ordered_drop(cx, ty),
1748+
ty::Adt(adt, subs) => adt
1749+
.all_fields()
1750+
.map(|f| f.ty(cx.tcx, subs))
1751+
.any(|ty| type_needs_ordered_drop(cx, ty)),
1752+
_ => true,
1753+
}
1754+
}
1755+
// Check for std types which implement drop, but only for memory allocation.
1756+
else if is_type_diagnostic_item(cx, ty, sym::vec_type)
1757+
|| is_type_lang_item(cx, ty, LangItem::OwnedBox)
1758+
|| is_type_diagnostic_item(cx, ty, sym::Rc)
1759+
|| is_type_diagnostic_item(cx, ty, sym::Arc)
1760+
|| is_type_diagnostic_item(cx, ty, sym::cstring_type)
1761+
|| match_type(cx, ty, &paths::BTREEMAP)
1762+
|| match_type(cx, ty, &paths::LINKED_LIST)
1763+
|| match_type(cx, ty, &paths::WEAK_RC)
1764+
|| match_type(cx, ty, &paths::WEAK_ARC)
1765+
{
1766+
// Check all of the generic arguments.
1767+
if let ty::Adt(_, subs) = ty.kind() {
1768+
subs.types().any(|ty| type_needs_ordered_drop(cx, ty))
1769+
} else {
1770+
true
1771+
}
1772+
} else {
1773+
true
1774+
}
1775+
}
1776+
1777+
// Extract the generic arguments out of a type
1778+
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
1779+
if_chain! {
1780+
if let ty::Adt(_, subs) = ty.kind();
1781+
if let Some(sub) = subs.get(index);
1782+
if let GenericArgKind::Type(sub_ty) = sub.unpack();
1783+
then {
1784+
Some(sub_ty)
1785+
} else {
1786+
None
1787+
}
1788+
}
1789+
}
1790+
1791+
// Checks if there are any temporaries created in the given expression for which drop order
1792+
// matters.
1793+
fn temporaries_need_ordered_drop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
1794+
struct V<'a, 'tcx> {
1795+
cx: &'a LateContext<'tcx>,
1796+
res: bool,
1797+
}
1798+
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
1799+
type Map = ErasedMap<'tcx>;
1800+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
1801+
NestedVisitorMap::None
1802+
}
1803+
1804+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
1805+
match expr.kind {
1806+
// Taking the reference of a value leaves a temporary
1807+
// e.g. In `&String::new()` the string is a temporary value.
1808+
// Remaining fields are temporary values
1809+
// e.g. In `(String::new(), 0).1` the string is a temporary value.
1810+
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
1811+
if !matches!(expr.kind, ExprKind::Path(_)) {
1812+
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
1813+
self.res = true;
1814+
} else {
1815+
self.visit_expr(expr);
1816+
}
1817+
}
1818+
},
1819+
// the base type is alway taken by reference.
1820+
// e.g. In `(vec![0])[0]` the vector is a temporary value.
1821+
ExprKind::Index(base, index) => {
1822+
if !matches!(base.kind, ExprKind::Path(_)) {
1823+
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
1824+
self.res = true;
1825+
} else {
1826+
self.visit_expr(base);
1827+
}
1828+
}
1829+
self.visit_expr(index);
1830+
},
1831+
// Method calls can take self by reference.
1832+
// e.g. In `String::new().len()` the string is a temporary value.
1833+
ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
1834+
if !matches!(self_arg.kind, ExprKind::Path(_)) {
1835+
let self_by_ref = self
1836+
.cx
1837+
.typeck_results()
1838+
.type_dependent_def_id(expr.hir_id)
1839+
.map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
1840+
if self_by_ref
1841+
&& type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg))
1842+
{
1843+
self.res = true;
1844+
} else {
1845+
self.visit_expr(self_arg)
1846+
}
1847+
}
1848+
args.iter().for_each(|arg| self.visit_expr(arg));
1849+
},
1850+
// Either explicitly drops values, or changes control flow.
1851+
ExprKind::DropTemps(_)
1852+
| ExprKind::Ret(_)
1853+
| ExprKind::Break(..)
1854+
| ExprKind::Yield(..)
1855+
| ExprKind::Block(Block { expr: None, .. }, _)
1856+
| ExprKind::Loop(..) => (),
1857+
1858+
// Only consider the final expression.
1859+
ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
1860+
1861+
_ => walk_expr(self, expr),
1862+
}
1863+
}
1864+
}
1865+
1866+
let mut v = V { cx, res: false };
1867+
v.visit_expr(expr);
1868+
v.res
1869+
}
1870+
17211871
fn find_sugg_for_if_let<'tcx>(
17221872
cx: &LateContext<'tcx>,
17231873
expr: &'tcx Expr<'_>,
1724-
op: &Expr<'_>,
1725-
arms: &[Arm<'_>],
1874+
op: &'tcx Expr<'tcx>,
1875+
arm: &Arm<'_>,
17261876
keyword: &'static str,
1877+
has_else: bool,
17271878
) {
17281879
// also look inside refs
1729-
let mut kind = &arms[0].pat.kind;
1880+
let mut kind = &arm.pat.kind;
17301881
// if we have &None for example, peel it so we can detect "if let None = x"
17311882
if let PatKind::Ref(inner, _mutability) = kind {
17321883
kind = &inner.kind;
17331884
}
1734-
let good_method = match kind {
1735-
PatKind::TupleStruct(ref path, patterns, _) if patterns.len() == 1 => {
1736-
if let PatKind::Wild = patterns[0].kind {
1885+
let op_ty = cx.typeck_results().expr_ty(op);
1886+
// Determine which function should be used, and the type contained by the corresponding
1887+
// variant.
1888+
let (good_method, inner_ty) = match *kind {
1889+
PatKind::TupleStruct(ref path, [sub_pat], _) => {
1890+
if let PatKind::Wild = sub_pat.kind {
17371891
if match_qpath(path, &paths::RESULT_OK) {
1738-
"is_ok()"
1892+
("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
17391893
} else if match_qpath(path, &paths::RESULT_ERR) {
1740-
"is_err()"
1894+
("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
17411895
} else if match_qpath(path, &paths::OPTION_SOME) {
1742-
"is_some()"
1896+
("is_some()", op_ty)
17431897
} else if match_qpath(path, &paths::POLL_READY) {
1744-
"is_ready()"
1898+
("is_ready()", op_ty)
17451899
} else if match_qpath(path, &paths::IPADDR_V4) {
1746-
"is_ipv4()"
1900+
("is_ipv4()", op_ty)
17471901
} else if match_qpath(path, &paths::IPADDR_V6) {
1748-
"is_ipv6()"
1902+
("is_ipv6()", op_ty)
17491903
} else {
17501904
return;
17511905
}
@@ -1754,17 +1908,36 @@ mod redundant_pattern_match {
17541908
}
17551909
},
17561910
PatKind::Path(ref path) => {
1757-
if match_qpath(path, &paths::OPTION_NONE) {
1911+
let method = if match_qpath(path, &paths::OPTION_NONE) {
17581912
"is_none()"
17591913
} else if match_qpath(path, &paths::POLL_PENDING) {
17601914
"is_pending()"
17611915
} else {
17621916
return;
1763-
}
1917+
};
1918+
// `None` and `Pending` don't have an inner type.
1919+
(method, cx.tcx.types.unit)
17641920
},
17651921
_ => return,
17661922
};
17671923

1924+
// If this is the last expression in a block or there is an else clause then the whole
1925+
// type needs to be considered, not just the inner type of the branch being matched on.
1926+
// Note the last expression in a block is dropped after all local bindings.
1927+
let check_ty = if has_else
1928+
|| (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
1929+
{
1930+
op_ty
1931+
} else {
1932+
inner_ty
1933+
};
1934+
1935+
// All temporaries created in the scrutinee expression are dropped at the same time as the
1936+
// scrutinee would be, so they have to be considered as well.
1937+
// e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
1938+
// for the duration if body.
1939+
let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, op);
1940+
17681941
// check that `while_let_on_iterator` lint does not trigger
17691942
if_chain! {
17701943
if keyword == "while";
@@ -1783,7 +1956,7 @@ mod redundant_pattern_match {
17831956
span_lint_and_then(
17841957
cx,
17851958
REDUNDANT_PATTERN_MATCHING,
1786-
arms[0].pat.span,
1959+
arm.pat.span,
17871960
&format!("redundant pattern matching, consider using `{}`", good_method),
17881961
|diag| {
17891962
// while let ... = ... { ... }
@@ -1797,12 +1970,20 @@ mod redundant_pattern_match {
17971970
// while let ... = ... { ... }
17981971
// ^^^^^^^^^^^^^^^^^^^
17991972
let span = expr_span.until(op_span.shrink_to_hi());
1800-
diag.span_suggestion(
1801-
span,
1802-
"try this",
1803-
format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
1804-
Applicability::MachineApplicable, // snippet
1805-
);
1973+
1974+
let mut app = if needs_drop {
1975+
Applicability::MaybeIncorrect
1976+
} else {
1977+
Applicability::MachineApplicable
1978+
};
1979+
let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
1980+
1981+
diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
1982+
1983+
if needs_drop {
1984+
diag.note("this will change drop order of the result, as well as all temporaries");
1985+
diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
1986+
}
18061987
},
18071988
);
18081989
}
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)