Skip to content

Commit 7f2068c

Browse files
committed
Auto merge of #6568 - Jarcho:redundant_pattern_matching, r=flip1995
Fix: redundant_pattern_matching drop order Fixes #5746 A note about the change in drop order is added when the scrutinee (or any temporary in the expression) isn't known to be safe to drop in any order (i.e. doesn't implement the `Drop` trait, or contain such a type). There is a whitelist for some `std` types, but it's incomplete. Currently just `Vec<_>`, `Box<_>`, `Rc<_>` and `Arc<_>`, but only if the contained type is also safe to drop in any order. Another lint for when the drop order changes could be added as allowed by default, but the drop order requirement is pretty subtle in this case. I think the note added to the lint should be enough to make someone think before applying the change. changelog: Added a note to `redundant_pattern_matching` when the change in drop order might matter
2 parents 1e0a3ff + c02baba commit 7f2068c

13 files changed

+581
-89
lines changed

clippy_lints/src/matches.rs

+207-24
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
///
@@ -1703,55 +1708,206 @@ where
17031708
mod redundant_pattern_match {
17041709
use super::REDUNDANT_PATTERN_MATCHING;
17051710
use clippy_utils::diagnostics::span_lint_and_then;
1706-
use clippy_utils::source::snippet;
1711+
use clippy_utils::source::{snippet, snippet_with_applicability};
1712+
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
17071713
use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
17081714
use if_chain::if_chain;
17091715
use rustc_ast::ast::LitKind;
17101716
use rustc_errors::Applicability;
17111717
use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
1712-
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath};
1718+
use rustc_hir::{
1719+
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
1720+
Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath,
1721+
};
17131722
use rustc_lint::LateContext;
1723+
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
17141724
use rustc_span::sym;
17151725

17161726
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
17171727
if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
17181728
match match_source {
17191729
MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
1720-
MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"),
1721-
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"),
1730+
MatchSource::IfLetDesugar { contains_else_clause } => {
1731+
find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause)
1732+
},
1733+
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
17221734
_ => {},
17231735
}
17241736
}
17251737
}
17261738

1739+
/// Checks if the drop order for a type matters. Some std types implement drop solely to
1740+
/// deallocate memory. For these types, and composites containing them, changing the drop order
1741+
/// won't result in any observable side effects.
1742+
fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
1743+
if !ty.needs_drop(cx.tcx, cx.param_env) {
1744+
false
1745+
} else if !cx
1746+
.tcx
1747+
.lang_items()
1748+
.drop_trait()
1749+
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
1750+
{
1751+
// This type doesn't implement drop, so no side effects here.
1752+
// Check if any component type has any.
1753+
match ty.kind() {
1754+
ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)),
1755+
ty::Array(ty, _) => type_needs_ordered_drop(cx, ty),
1756+
ty::Adt(adt, subs) => adt
1757+
.all_fields()
1758+
.map(|f| f.ty(cx.tcx, subs))
1759+
.any(|ty| type_needs_ordered_drop(cx, ty)),
1760+
_ => true,
1761+
}
1762+
}
1763+
// Check for std types which implement drop, but only for memory allocation.
1764+
else if is_type_diagnostic_item(cx, ty, sym::vec_type)
1765+
|| is_type_lang_item(cx, ty, LangItem::OwnedBox)
1766+
|| is_type_diagnostic_item(cx, ty, sym::Rc)
1767+
|| is_type_diagnostic_item(cx, ty, sym::Arc)
1768+
|| is_type_diagnostic_item(cx, ty, sym::cstring_type)
1769+
|| match_type(cx, ty, &paths::BTREEMAP)
1770+
|| match_type(cx, ty, &paths::LINKED_LIST)
1771+
|| match_type(cx, ty, &paths::WEAK_RC)
1772+
|| match_type(cx, ty, &paths::WEAK_ARC)
1773+
{
1774+
// Check all of the generic arguments.
1775+
if let ty::Adt(_, subs) = ty.kind() {
1776+
subs.types().any(|ty| type_needs_ordered_drop(cx, ty))
1777+
} else {
1778+
true
1779+
}
1780+
} else {
1781+
true
1782+
}
1783+
}
1784+
1785+
// Extract the generic arguments out of a type
1786+
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
1787+
if_chain! {
1788+
if let ty::Adt(_, subs) = ty.kind();
1789+
if let Some(sub) = subs.get(index);
1790+
if let GenericArgKind::Type(sub_ty) = sub.unpack();
1791+
then {
1792+
Some(sub_ty)
1793+
} else {
1794+
None
1795+
}
1796+
}
1797+
}
1798+
1799+
// Checks if there are any temporaries created in the given expression for which drop order
1800+
// matters.
1801+
fn temporaries_need_ordered_drop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
1802+
struct V<'a, 'tcx> {
1803+
cx: &'a LateContext<'tcx>,
1804+
res: bool,
1805+
}
1806+
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
1807+
type Map = ErasedMap<'tcx>;
1808+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
1809+
NestedVisitorMap::None
1810+
}
1811+
1812+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
1813+
match expr.kind {
1814+
// Taking the reference of a value leaves a temporary
1815+
// e.g. In `&String::new()` the string is a temporary value.
1816+
// Remaining fields are temporary values
1817+
// e.g. In `(String::new(), 0).1` the string is a temporary value.
1818+
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
1819+
if !matches!(expr.kind, ExprKind::Path(_)) {
1820+
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
1821+
self.res = true;
1822+
} else {
1823+
self.visit_expr(expr);
1824+
}
1825+
}
1826+
},
1827+
// the base type is alway taken by reference.
1828+
// e.g. In `(vec![0])[0]` the vector is a temporary value.
1829+
ExprKind::Index(base, index) => {
1830+
if !matches!(base.kind, ExprKind::Path(_)) {
1831+
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
1832+
self.res = true;
1833+
} else {
1834+
self.visit_expr(base);
1835+
}
1836+
}
1837+
self.visit_expr(index);
1838+
},
1839+
// Method calls can take self by reference.
1840+
// e.g. In `String::new().len()` the string is a temporary value.
1841+
ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
1842+
if !matches!(self_arg.kind, ExprKind::Path(_)) {
1843+
let self_by_ref = self
1844+
.cx
1845+
.typeck_results()
1846+
.type_dependent_def_id(expr.hir_id)
1847+
.map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
1848+
if self_by_ref
1849+
&& type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg))
1850+
{
1851+
self.res = true;
1852+
} else {
1853+
self.visit_expr(self_arg)
1854+
}
1855+
}
1856+
args.iter().for_each(|arg| self.visit_expr(arg));
1857+
},
1858+
// Either explicitly drops values, or changes control flow.
1859+
ExprKind::DropTemps(_)
1860+
| ExprKind::Ret(_)
1861+
| ExprKind::Break(..)
1862+
| ExprKind::Yield(..)
1863+
| ExprKind::Block(Block { expr: None, .. }, _)
1864+
| ExprKind::Loop(..) => (),
1865+
1866+
// Only consider the final expression.
1867+
ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
1868+
1869+
_ => walk_expr(self, expr),
1870+
}
1871+
}
1872+
}
1873+
1874+
let mut v = V { cx, res: false };
1875+
v.visit_expr(expr);
1876+
v.res
1877+
}
1878+
17271879
fn find_sugg_for_if_let<'tcx>(
17281880
cx: &LateContext<'tcx>,
17291881
expr: &'tcx Expr<'_>,
1730-
op: &Expr<'_>,
1731-
arms: &[Arm<'_>],
1882+
op: &'tcx Expr<'tcx>,
1883+
arm: &Arm<'_>,
17321884
keyword: &'static str,
1885+
has_else: bool,
17331886
) {
17341887
// also look inside refs
1735-
let mut kind = &arms[0].pat.kind;
1888+
let mut kind = &arm.pat.kind;
17361889
// if we have &None for example, peel it so we can detect "if let None = x"
17371890
if let PatKind::Ref(inner, _mutability) = kind {
17381891
kind = &inner.kind;
17391892
}
1740-
let good_method = match kind {
1893+
let op_ty = cx.typeck_results().expr_ty(op);
1894+
// Determine which function should be used, and the type contained by the corresponding
1895+
// variant.
1896+
let (good_method, inner_ty) = match kind {
17411897
PatKind::TupleStruct(ref path, [sub_pat], _) => {
17421898
if let PatKind::Wild = sub_pat.kind {
17431899
if is_lang_ctor(cx, path, ResultOk) {
1744-
"is_ok()"
1900+
("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
17451901
} else if is_lang_ctor(cx, path, ResultErr) {
1746-
"is_err()"
1902+
("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
17471903
} else if is_lang_ctor(cx, path, OptionSome) {
1748-
"is_some()"
1904+
("is_some()", op_ty)
17491905
} else if is_lang_ctor(cx, path, PollReady) {
1750-
"is_ready()"
1906+
("is_ready()", op_ty)
17511907
} else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) {
1752-
"is_ipv4()"
1908+
("is_ipv4()", op_ty)
17531909
} else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V6) {
1754-
"is_ipv6()"
1910+
("is_ipv6()", op_ty)
17551911
} else {
17561912
return;
17571913
}
@@ -1760,17 +1916,36 @@ mod redundant_pattern_match {
17601916
}
17611917
},
17621918
PatKind::Path(ref path) => {
1763-
if is_lang_ctor(cx, path, OptionNone) {
1919+
let method = if is_lang_ctor(cx, path, OptionNone) {
17641920
"is_none()"
17651921
} else if is_lang_ctor(cx, path, PollPending) {
17661922
"is_pending()"
17671923
} else {
17681924
return;
1769-
}
1925+
};
1926+
// `None` and `Pending` don't have an inner type.
1927+
(method, cx.tcx.types.unit)
17701928
},
17711929
_ => return,
17721930
};
17731931

1932+
// If this is the last expression in a block or there is an else clause then the whole
1933+
// type needs to be considered, not just the inner type of the branch being matched on.
1934+
// Note the last expression in a block is dropped after all local bindings.
1935+
let check_ty = if has_else
1936+
|| (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
1937+
{
1938+
op_ty
1939+
} else {
1940+
inner_ty
1941+
};
1942+
1943+
// All temporaries created in the scrutinee expression are dropped at the same time as the
1944+
// scrutinee would be, so they have to be considered as well.
1945+
// e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
1946+
// for the duration if body.
1947+
let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, op);
1948+
17741949
// check that `while_let_on_iterator` lint does not trigger
17751950
if_chain! {
17761951
if keyword == "while";
@@ -1789,7 +1964,7 @@ mod redundant_pattern_match {
17891964
span_lint_and_then(
17901965
cx,
17911966
REDUNDANT_PATTERN_MATCHING,
1792-
arms[0].pat.span,
1967+
arm.pat.span,
17931968
&format!("redundant pattern matching, consider using `{}`", good_method),
17941969
|diag| {
17951970
// while let ... = ... { ... }
@@ -1803,12 +1978,20 @@ mod redundant_pattern_match {
18031978
// while let ... = ... { ... }
18041979
// ^^^^^^^^^^^^^^^^^^^
18051980
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-
);
1981+
1982+
let mut app = if needs_drop {
1983+
Applicability::MaybeIncorrect
1984+
} else {
1985+
Applicability::MachineApplicable
1986+
};
1987+
let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
1988+
1989+
diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
1990+
1991+
if needs_drop {
1992+
diag.note("this will change drop order of the result, as well as all temporaries");
1993+
diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
1994+
}
18121995
},
18131996
);
18141997
}
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)