@@ -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,54 +1706,203 @@ where
17011706mod redundant_pattern_match {
17021707 use super :: REDUNDANT_PATTERN_MATCHING ;
17031708 use clippy_utils:: diagnostics:: span_lint_and_then;
1704- use clippy_utils:: source:: snippet;
1709+ use clippy_utils:: source:: { snippet, snippet_with_applicability} ;
1710+ use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type} ;
17051711 use clippy_utils:: { is_trait_method, match_qpath, 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:: {
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 {
1738- PatKind :: TupleStruct ( ref path, ref patterns, _) if patterns. len ( ) == 1 => {
1739- if let PatKind :: Wild = patterns[ 0 ] . 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 {
1892+ PatKind :: TupleStruct ( ref path, [ sub_pat] , _) => {
1893+ if let PatKind :: Wild = sub_pat. kind {
17401894 if match_qpath ( path, & paths:: RESULT_OK ) {
1741- "is_ok()"
1895+ ( "is_ok()" , try_get_generic_ty ( op_ty , 0 ) . unwrap_or ( op_ty ) )
17421896 } else if match_qpath ( path, & paths:: RESULT_ERR ) {
1743- "is_err()"
1897+ ( "is_err()" , try_get_generic_ty ( op_ty , 1 ) . unwrap_or ( op_ty ) )
17441898 } else if match_qpath ( path, & paths:: OPTION_SOME ) {
1745- "is_some()"
1899+ ( "is_some()" , op_ty )
17461900 } else if match_qpath ( path, & paths:: POLL_READY ) {
1747- "is_ready()"
1901+ ( "is_ready()" , op_ty )
17481902 } else if match_qpath ( path, & paths:: IPADDR_V4 ) {
1749- "is_ipv4()"
1903+ ( "is_ipv4()" , op_ty )
17501904 } else if match_qpath ( path, & 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 match_qpath ( path, & paths:: OPTION_NONE ) {
1914+ let method = if match_qpath ( path, & paths:: OPTION_NONE ) {
17611915 "is_none()"
17621916 } else if match_qpath ( path, & paths:: POLL_PENDING ) {
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 }
0 commit comments