@@ -421,7 +421,12 @@ declare_clippy_lint! {
421
421
/// **Why is this bad?** It's more concise and clear to just use the proper
422
422
/// utility function
423
423
///
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.
425
430
///
426
431
/// **Example:**
427
432
///
@@ -1701,57 +1706,219 @@ where
1701
1706
mod redundant_pattern_match {
1702
1707
use super :: REDUNDANT_PATTERN_MATCHING ;
1703
1708
use clippy_utils:: diagnostics:: span_lint_and_then;
1704
- use clippy_utils:: source:: snippet;
1705
- use clippy_utils:: { is_trait_method, match_qpath, paths} ;
1709
+ use clippy_utils:: source:: { snippet, snippet_with_applicability} ;
1710
+ use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item, is_type_lang_item} ;
1711
+ use clippy_utils:: { is_trait_method, match_qpath, match_trait_method, paths} ;
1706
1712
use if_chain:: if_chain;
1707
1713
use rustc_ast:: ast:: LitKind ;
1708
1714
use rustc_errors:: Applicability ;
1709
- use rustc_hir:: { Arm , Expr , ExprKind , MatchSource , PatKind , QPath } ;
1715
+ use rustc_hir:: { Arm , Expr , ExprKind , LangItem , MatchSource , Node , PatKind , QPath , UnOp } ;
1710
1716
use rustc_lint:: LateContext ;
1717
+ use rustc_middle:: ty:: { self , subst:: GenericArgKind , Ty , TyCtxt , TypeckResults } ;
1711
1718
use rustc_span:: sym;
1712
1719
1713
1720
pub fn check < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
1714
1721
if let ExprKind :: Match ( op, arms, ref match_source) = & expr. kind {
1715
1722
match match_source {
1716
1723
MatchSource :: Normal => find_sugg_for_match ( cx, expr, op, arms) ,
1717
- MatchSource :: IfLetDesugar { .. } => find_sugg_for_if_let ( cx, expr, op, & arms[ 0 ] , "if" ) ,
1718
- MatchSource :: WhileLetDesugar => find_sugg_for_if_let ( cx, expr, op, & arms[ 0 ] , "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 ) ,
1719
1728
_ => { } ,
1720
1729
}
1721
1730
}
1722
1731
}
1723
1732
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
+ return false ;
1737
+ }
1738
+ if cx
1739
+ . tcx
1740
+ . lang_items ( )
1741
+ . drop_trait ( )
1742
+ . map_or ( false , |id| !implements_trait ( cx, ty, id, & [ ] ) )
1743
+ {
1744
+ // This type doesn't implement drop, so no side effects here.
1745
+ // Check if any component type has any.
1746
+ return match ty. kind ( ) {
1747
+ ty:: Tuple ( _) => ty. tuple_fields ( ) . any ( |ty| type_needs_ordered_drop ( cx, ty) ) ,
1748
+ ty:: Array ( ty, _) => type_needs_ordered_drop ( cx, ty) ,
1749
+ ty:: Adt ( adt, subs) => adt
1750
+ . all_fields ( )
1751
+ . map ( |f| f. ty ( cx. tcx , subs) )
1752
+ . any ( |ty| type_needs_ordered_drop ( cx, ty) ) ,
1753
+ _ => true ,
1754
+ } ;
1755
+ }
1756
+
1757
+ // Check for std types which implement drop, but only for memory allocation.
1758
+ if is_type_diagnostic_item ( cx, ty, sym:: vec_type)
1759
+ || is_type_lang_item ( cx, ty, LangItem :: OwnedBox )
1760
+ || is_type_diagnostic_item ( cx, ty, sym:: Rc )
1761
+ || is_type_diagnostic_item ( cx, ty, sym:: Arc )
1762
+ {
1763
+ try_get_generic_ty ( ty, 0 ) . map_or ( true , |ty| type_needs_ordered_drop ( cx, ty) )
1764
+ } else {
1765
+ true
1766
+ }
1767
+ }
1768
+
1769
+ // Extract the generic arguments out of a type
1770
+ fn try_get_generic_ty ( ty : Ty < ' _ > , index : usize ) -> Option < Ty < ' _ > > {
1771
+ if_chain ! {
1772
+ if let ty:: Adt ( _, subs) = ty. kind( ) ;
1773
+ if let Some ( sub) = subs. get( index) ;
1774
+ if let GenericArgKind :: Type ( sub_ty) = sub. unpack( ) ;
1775
+ then {
1776
+ Some ( sub_ty)
1777
+ } else {
1778
+ None
1779
+ }
1780
+ }
1781
+ }
1782
+
1783
+ // Gets all the the temporaries in an expression which need to be dropped afterwards.
1784
+ #[ allow( clippy:: too_many_lines) ]
1785
+ fn get_temporary_tys ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> Vec < Ty < ' tcx > > {
1786
+ fn f (
1787
+ addr_of : bool ,
1788
+ tcx : TyCtxt < ' tcx > ,
1789
+ typeck : & TypeckResults < ' tcx > ,
1790
+ expr : & ' tcx Expr < ' tcx > ,
1791
+ result : & mut Vec < Ty < ' tcx > > ,
1792
+ ) {
1793
+ match expr. kind {
1794
+ ExprKind :: ConstBlock ( _)
1795
+ | ExprKind :: Tup ( [ ] )
1796
+ | ExprKind :: Array ( [ ] )
1797
+ | ExprKind :: Lit ( _)
1798
+ | ExprKind :: Path ( _)
1799
+ | ExprKind :: Assign ( ..)
1800
+ | ExprKind :: AssignOp ( ..)
1801
+ | ExprKind :: Break ( ..)
1802
+ | ExprKind :: Continue ( _)
1803
+ | ExprKind :: Ret ( _)
1804
+ | ExprKind :: InlineAsm ( _)
1805
+ | ExprKind :: LlvmInlineAsm ( _)
1806
+ | ExprKind :: Yield ( ..)
1807
+ | ExprKind :: Err
1808
+ | ExprKind :: MethodCall ( _, _, [ ] , _) => ( ) ,
1809
+ ExprKind :: MethodCall ( _, _, [ self_arg, args @ ..] , _) => {
1810
+ if addr_of {
1811
+ result. push ( typeck. expr_ty ( expr) ) ;
1812
+ }
1813
+ let ref_self = typeck
1814
+ . type_dependent_def_id ( expr. hir_id )
1815
+ . map_or ( false , |id| tcx. fn_sig ( id) . skip_binder ( ) . inputs ( ) [ 0 ] . is_ref ( ) ) ;
1816
+ f ( ref_self, tcx, typeck, self_arg, result) ;
1817
+ for arg in args {
1818
+ f ( false , tcx, typeck, arg, result) ;
1819
+ }
1820
+ } ,
1821
+ ExprKind :: AddrOf ( _, _, expr) | ExprKind :: Field ( expr, _) => f ( true , tcx, typeck, expr, result) ,
1822
+ ExprKind :: Box ( expr) | ExprKind :: Cast ( expr, _) => f ( false , tcx, typeck, expr, result) ,
1823
+ ExprKind :: Array ( exprs @ [ _, ..] ) | ExprKind :: Tup ( exprs @ [ _, ..] ) | ExprKind :: Call ( _, exprs) => {
1824
+ if addr_of {
1825
+ result. push ( typeck. expr_ty ( expr) ) ;
1826
+ }
1827
+ for expr in exprs {
1828
+ f ( false , tcx, typeck, expr, result) ;
1829
+ }
1830
+ } ,
1831
+ ExprKind :: Binary ( _, lhs, rhs) => {
1832
+ if addr_of {
1833
+ result. push ( typeck. expr_ty ( expr) ) ;
1834
+ }
1835
+ f ( false , tcx, typeck, lhs, result) ;
1836
+ f ( false , tcx, typeck, rhs, result) ;
1837
+ } ,
1838
+ ExprKind :: Unary ( UnOp :: UnDeref , e) => {
1839
+ f ( typeck. expr_ty ( e) . is_ref ( ) , tcx, typeck, e, result) ;
1840
+ } ,
1841
+ ExprKind :: Type ( e, _) | ExprKind :: Unary ( _, e) => {
1842
+ if addr_of {
1843
+ result. push ( typeck. expr_ty ( expr) ) ;
1844
+ }
1845
+ f ( false , tcx, typeck, e, result) ;
1846
+ } ,
1847
+ ExprKind :: Index ( base, index) => {
1848
+ f ( true , tcx, typeck, base, result) ;
1849
+ f ( false , tcx, typeck, index, result) ;
1850
+ } ,
1851
+ ExprKind :: DropTemps ( _) | ExprKind :: Closure ( ..) => {
1852
+ if addr_of {
1853
+ result. push ( typeck. expr_ty ( expr) ) ;
1854
+ }
1855
+ } ,
1856
+ ExprKind :: Match ( e, _, _) => {
1857
+ if addr_of {
1858
+ result. push ( typeck. expr_ty ( expr) ) ;
1859
+ }
1860
+ f ( true , tcx, typeck, e, result) ;
1861
+ } ,
1862
+ ExprKind :: Block ( b, _) | ExprKind :: Loop ( b, _, _) => {
1863
+ if addr_of {
1864
+ result. push ( typeck. expr_ty ( expr) ) ;
1865
+ }
1866
+ if let Some ( expr) = b. expr {
1867
+ f ( false , tcx, typeck, expr, result) ;
1868
+ }
1869
+ } ,
1870
+ ExprKind :: Struct ( _, fields, _) => {
1871
+ if addr_of {
1872
+ result. push ( typeck. expr_ty ( expr) ) ;
1873
+ }
1874
+ for field in fields {
1875
+ f ( false , tcx, typeck, field. expr , result) ;
1876
+ }
1877
+ } ,
1878
+ ExprKind :: Repeat ( expr, _) => {
1879
+ if addr_of {
1880
+ result. push ( typeck. expr_ty ( expr) ) ;
1881
+ }
1882
+ f ( false , tcx, typeck, expr, result) ;
1883
+ } ,
1884
+ }
1885
+ }
1886
+
1887
+ let mut res = Vec :: new ( ) ;
1888
+ f ( false , cx. tcx , cx. typeck_results ( ) , expr, & mut res) ;
1889
+ res
1890
+ }
1891
+
1724
1892
fn find_sugg_for_if_let < ' tcx > (
1725
1893
cx : & LateContext < ' tcx > ,
1726
1894
expr : & ' tcx Expr < ' _ > ,
1727
- op : & Expr < ' _ > ,
1895
+ op : & ' tcx Expr < ' tcx > ,
1728
1896
arm : & Arm < ' _ > ,
1729
1897
keyword : & ' static str ,
1898
+ has_else : bool ,
1730
1899
) {
1731
1900
// also look inside refs
1732
- let mut kind = & arms [ 0 ] . pat . kind ;
1901
+ let mut kind = arm . pat . kind ;
1733
1902
// if we have &None for example, peel it so we can detect "if let None = x"
1734
1903
if let PatKind :: Ref ( inner, _mutability) = kind {
1735
1904
kind = & inner. kind ;
1736
1905
}
1737
- let good_method = match kind {
1906
+ let op_ty = cx. typeck_results ( ) . expr_ty ( op) ;
1907
+ let ( good_method, inner_ty) = match kind {
1738
1908
PatKind :: TupleStruct ( ref path, ref patterns, _) if patterns. len ( ) == 1 => {
1739
1909
if let PatKind :: Wild = patterns[ 0 ] . kind {
1740
- if cx. typeck_results ( ) . expr_ty ( op) . needs_drop ( cx. tcx , cx. param_env ) {
1741
- // if let doesn't drop the value until after the block ends
1742
- return ;
1743
- } else if match_qpath ( path, & paths:: RESULT_OK ) {
1744
- "is_ok()"
1910
+ if match_qpath ( path, & paths:: RESULT_OK ) {
1911
+ ( "is_ok()" , try_get_generic_ty ( op_ty, 0 ) . unwrap_or ( op_ty) )
1745
1912
} else if match_qpath ( path, & paths:: RESULT_ERR ) {
1746
- "is_err()"
1913
+ ( "is_err()" , try_get_generic_ty ( op_ty , 1 ) . unwrap_or ( op_ty ) )
1747
1914
} else if match_qpath ( path, & paths:: OPTION_SOME ) {
1748
- "is_some()"
1915
+ ( "is_some()" , op_ty )
1749
1916
} else if match_qpath ( path, & paths:: POLL_READY ) {
1750
- "is_ready()"
1917
+ ( "is_ready()" , op_ty )
1751
1918
} else if match_qpath ( path, & paths:: IPADDR_V4 ) {
1752
- "is_ipv4()"
1919
+ ( "is_ipv4()" , op_ty )
1753
1920
} else if match_qpath ( path, & paths:: IPADDR_V6 ) {
1754
- "is_ipv6()"
1921
+ ( "is_ipv6()" , op_ty )
1755
1922
} else {
1756
1923
return ;
1757
1924
}
@@ -1760,17 +1927,37 @@ mod redundant_pattern_match {
1760
1927
}
1761
1928
} ,
1762
1929
PatKind :: Path ( ref path) => {
1763
- if match_qpath ( path, & paths:: OPTION_NONE ) {
1930
+ let method = if match_qpath ( path, & paths:: OPTION_NONE ) {
1764
1931
"is_none()"
1765
1932
} else if match_qpath ( path, & paths:: POLL_PENDING ) {
1766
1933
"is_pending()"
1767
1934
} else {
1768
1935
return ;
1769
- }
1936
+ } ;
1937
+ // drop for `None` and `Pending` do nothing
1938
+ ( method, cx. tcx . types . unit )
1770
1939
} ,
1771
1940
_ => return ,
1772
1941
} ;
1773
1942
1943
+ // if this is the last expression in a block then the lifetime of the expression is extended
1944
+ // past the end of the block
1945
+ let check_ty = if has_else
1946
+ || ( keyword == "if" && {
1947
+ let map = cx. tcx . hir ( ) ;
1948
+ let id = map. get_parent_node ( expr. hir_id ) ;
1949
+ id != expr. hir_id && matches ! ( map. find( id) , Some ( Node :: Block ( ..) ) )
1950
+ } ) {
1951
+ op_ty
1952
+ } else {
1953
+ inner_ty
1954
+ } ;
1955
+
1956
+ let needs_drop = type_needs_ordered_drop ( cx, check_ty)
1957
+ || get_temporary_tys ( cx, op)
1958
+ . iter ( )
1959
+ . any ( |ty| type_needs_ordered_drop ( cx, ty) ) ;
1960
+
1774
1961
// check that `while_let_on_iterator` lint does not trigger
1775
1962
if_chain ! {
1776
1963
if keyword == "while" ;
@@ -1803,12 +1990,20 @@ mod redundant_pattern_match {
1803
1990
// while let ... = ... { ... }
1804
1991
// ^^^^^^^^^^^^^^^^^^^
1805
1992
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
- ) ;
1993
+
1994
+ let mut app = if needs_drop {
1995
+ Applicability :: MaybeIncorrect
1996
+ } else {
1997
+ Applicability :: MachineApplicable
1998
+ } ;
1999
+ let sugg = snippet_with_applicability ( cx, op_span, "_" , & mut app) ;
2000
+
2001
+ diag. span_suggestion ( span, "try this" , format ! ( "{} {}.{}" , keyword, sugg, good_method) , app) ;
2002
+
2003
+ if needs_drop {
2004
+ diag. note ( "this will change drop order of the result, as well as all temporaries" ) ;
2005
+ diag. note ( "add `#[allow(clippy::redundant_pattern_matching)]` if this is important" ) ;
2006
+ }
1812
2007
} ,
1813
2008
) ;
1814
2009
}
0 commit comments