@@ -423,7 +423,12 @@ declare_clippy_lint! {
423
423
/// **Why is this bad?** It's more concise and clear to just use the proper
424
424
/// utility function
425
425
///
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.
427
432
///
428
433
/// **Example:**
429
434
///
@@ -1700,55 +1705,204 @@ where
1700
1705
mod redundant_pattern_match {
1701
1706
use super :: REDUNDANT_PATTERN_MATCHING ;
1702
1707
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} ;
1704
1710
use clippy_utils:: { is_lang_ctor, is_qpath_def_path, is_trait_method, paths} ;
1705
1711
use if_chain:: if_chain;
1706
1712
use rustc_ast:: ast:: LitKind ;
1707
1713
use rustc_errors:: Applicability ;
1708
1714
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
+ } ;
1710
1719
use rustc_lint:: LateContext ;
1720
+ use rustc_middle:: ty:: { self , subst:: GenericArgKind , Ty } ;
1711
1721
use rustc_span:: sym;
1712
1722
1713
1723
pub fn check < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
1714
1724
if let ExprKind :: Match ( op, arms, ref match_source) = & expr. kind {
1715
1725
match match_source {
1716
1726
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 ) ,
1719
1731
_ => { } ,
1720
1732
}
1721
1733
}
1722
1734
}
1723
1735
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
+
1724
1874
fn find_sugg_for_if_let < ' tcx > (
1725
1875
cx : & LateContext < ' tcx > ,
1726
1876
expr : & ' tcx Expr < ' _ > ,
1727
- op : & Expr < ' _ > ,
1728
- arms : & [ Arm < ' _ > ] ,
1877
+ op : & ' tcx Expr < ' tcx > ,
1878
+ arm : & Arm < ' _ > ,
1729
1879
keyword : & ' static str ,
1880
+ has_else : bool ,
1730
1881
) {
1731
1882
// also look inside refs
1732
- let mut kind = & arms [ 0 ] . pat . kind ;
1883
+ let mut kind = & arm . pat . kind ;
1733
1884
// if we have &None for example, peel it so we can detect "if let None = x"
1734
1885
if let PatKind :: Ref ( inner, _mutability) = kind {
1735
1886
kind = & inner. kind ;
1736
1887
}
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 {
1738
1892
PatKind :: TupleStruct ( ref path, [ sub_pat] , _) => {
1739
1893
if let PatKind :: Wild = sub_pat. kind {
1740
1894
if is_lang_ctor ( cx, path, ResultOk ) {
1741
- "is_ok()"
1895
+ ( "is_ok()" , try_get_generic_ty ( op_ty , 0 ) . unwrap_or ( op_ty ) )
1742
1896
} 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 ) )
1744
1898
} else if is_lang_ctor ( cx, path, OptionSome ) {
1745
- "is_some()"
1899
+ ( "is_some()" , op_ty )
1746
1900
} else if is_lang_ctor ( cx, path, PollReady ) {
1747
- "is_ready()"
1901
+ ( "is_ready()" , op_ty )
1748
1902
} else if is_qpath_def_path ( cx, path, sub_pat. hir_id , & paths:: IPADDR_V4 ) {
1749
- "is_ipv4()"
1903
+ ( "is_ipv4()" , op_ty )
1750
1904
} else if is_qpath_def_path ( cx, path, sub_pat. hir_id , & paths:: IPADDR_V6 ) {
1751
- "is_ipv6()"
1905
+ ( "is_ipv6()" , op_ty )
1752
1906
} else {
1753
1907
return ;
1754
1908
}
@@ -1757,17 +1911,36 @@ mod redundant_pattern_match {
1757
1911
}
1758
1912
} ,
1759
1913
PatKind :: Path ( ref path) => {
1760
- if is_lang_ctor ( cx, path, OptionNone ) {
1914
+ let method = if is_lang_ctor ( cx, path, OptionNone ) {
1761
1915
"is_none()"
1762
1916
} else if is_lang_ctor ( cx, path, PollPending ) {
1763
1917
"is_pending()"
1764
1918
} else {
1765
1919
return ;
1766
- }
1920
+ } ;
1921
+ // `None` and `Pending` don't have an inner type.
1922
+ ( method, cx. tcx . types . unit )
1767
1923
} ,
1768
1924
_ => return ,
1769
1925
} ;
1770
1926
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
+
1771
1944
// check that `while_let_on_iterator` lint does not trigger
1772
1945
if_chain ! {
1773
1946
if keyword == "while" ;
@@ -1786,7 +1959,7 @@ mod redundant_pattern_match {
1786
1959
span_lint_and_then (
1787
1960
cx,
1788
1961
REDUNDANT_PATTERN_MATCHING ,
1789
- arms [ 0 ] . pat . span ,
1962
+ arm . pat . span ,
1790
1963
& format ! ( "redundant pattern matching, consider using `{}`" , good_method) ,
1791
1964
|diag| {
1792
1965
// while let ... = ... { ... }
@@ -1800,12 +1973,20 @@ mod redundant_pattern_match {
1800
1973
// while let ... = ... { ... }
1801
1974
// ^^^^^^^^^^^^^^^^^^^
1802
1975
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
+ }
1809
1990
} ,
1810
1991
) ;
1811
1992
}
0 commit comments