@@ -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
17031708mod 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 }
0 commit comments