Skip to content

Commit b658163

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

13 files changed

+579
-89
lines changed

clippy_lints/src/matches.rs

Lines changed: 205 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,12 @@ declare_clippy_lint! {
423423
/// **Why is this bad?** It's more concise and clear to just use the proper
424424
/// utility function
425425
///
426-
/// **Known problems:** None.
426+
/// **Known problems:** This will change the drop order for the matched type. Both `if let` and
427+
/// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
428+
/// value before entering the block. For most types this change will not matter, but for a few
429+
/// types this will not be an acceptable change (e.g. locks). See the
430+
/// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
431+
/// drop order.
427432
///
428433
/// **Example:**
429434
///
@@ -1700,55 +1705,204 @@ where
17001705
mod redundant_pattern_match {
17011706
use super::REDUNDANT_PATTERN_MATCHING;
17021707
use clippy_utils::diagnostics::span_lint_and_then;
1703-
use clippy_utils::source::snippet;
1708+
use clippy_utils::source::{snippet, snippet_with_applicability};
1709+
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
17041710
use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
17051711
use if_chain::if_chain;
17061712
use rustc_ast::ast::LitKind;
17071713
use rustc_errors::Applicability;
17081714
use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
1709-
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath};
1715+
use rustc_hir::{
1716+
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
1717+
Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath,
1718+
};
17101719
use rustc_lint::LateContext;
1720+
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
17111721
use rustc_span::sym;
17121722

17131723
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
17141724
if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
17151725
match match_source {
17161726
MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
1717-
MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"),
1718-
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"),
1727+
MatchSource::IfLetDesugar { contains_else_clause } => {
1728+
find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause)
1729+
},
1730+
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
17191731
_ => {},
17201732
}
17211733
}
17221734
}
17231735

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

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