@@ -418,7 +418,12 @@ declare_clippy_lint! {
418
418
/// **Why is this bad?** It's more concise and clear to just use the proper
419
419
/// utility function
420
420
///
421
- /// **Known problems:** None.
421
+ /// **Known problems:** This will change the drop order for the matched type. Both `if let` and
422
+ /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
423
+ /// value before entering the block. For most types this change will not matter, but for a few
424
+ /// types this will not be an acceptable change (e.g. locks). See the
425
+ /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
426
+ /// drop order.
422
427
///
423
428
/// **Example:**
424
429
///
@@ -1553,50 +1558,214 @@ where
1553
1558
1554
1559
mod redundant_pattern_match {
1555
1560
use super :: REDUNDANT_PATTERN_MATCHING ;
1556
- use crate :: utils:: { match_qpath, match_trait_method, paths, snippet, span_lint_and_then} ;
1561
+ use crate :: utils:: {
1562
+ implements_trait, is_type_diagnostic_item, is_type_lang_item, match_qpath, match_trait_method, paths, snippet,
1563
+ snippet_with_applicability, span_lint_and_then,
1564
+ } ;
1557
1565
use if_chain:: if_chain;
1558
1566
use rustc_ast:: ast:: LitKind ;
1559
1567
use rustc_errors:: Applicability ;
1560
- use rustc_hir:: { Arm , Expr , ExprKind , MatchSource , PatKind , QPath } ;
1568
+ use rustc_hir:: { Arm , Expr , ExprKind , LangItem , MatchSource , Node , PatKind , QPath , UnOp } ;
1561
1569
use rustc_lint:: LateContext ;
1570
+ use rustc_middle:: ty:: { self , subst:: GenericArgKind , Ty , TyCtxt , TypeckResults } ;
1562
1571
use rustc_span:: sym;
1563
1572
1564
1573
pub fn check < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
1565
1574
if let ExprKind :: Match ( op, arms, ref match_source) = & expr. kind {
1566
1575
match match_source {
1567
1576
MatchSource :: Normal => find_sugg_for_match ( cx, expr, op, arms) ,
1568
- MatchSource :: IfLetDesugar { .. } => find_sugg_for_if_let ( cx, expr, op, & arms[ 0 ] , "if" ) ,
1569
- MatchSource :: WhileLetDesugar => find_sugg_for_if_let ( cx, expr, op, & arms[ 0 ] , "while" ) ,
1577
+ MatchSource :: IfLetDesugar { contains_else_clause } => {
1578
+ find_sugg_for_if_let ( cx, expr, op, & arms[ 0 ] , "if" , * contains_else_clause)
1579
+ } ,
1580
+ MatchSource :: WhileLetDesugar => find_sugg_for_if_let ( cx, expr, op, & arms[ 0 ] , "while" , false ) ,
1570
1581
_ => { } ,
1571
1582
}
1572
1583
}
1573
1584
}
1574
1585
1586
+ // Check if the drop order for a type matters
1587
+ fn type_needs_ordered_drop ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> bool {
1588
+ if !ty. needs_drop ( cx. tcx , cx. param_env ) {
1589
+ return false ;
1590
+ }
1591
+ if cx
1592
+ . tcx
1593
+ . lang_items ( )
1594
+ . drop_trait ( )
1595
+ . map_or ( false , |id| !implements_trait ( cx, ty, id, & [ ] ) )
1596
+ {
1597
+ // This type doesn't implement drop, so no side effects here.
1598
+ // Check if any component type has any.
1599
+ return match ty. kind ( ) {
1600
+ ty:: Tuple ( _) => ty. tuple_fields ( ) . any ( |ty| type_needs_ordered_drop ( cx, ty) ) ,
1601
+ ty:: Array ( ty, _) => type_needs_ordered_drop ( cx, ty) ,
1602
+ ty:: Adt ( adt, subs) => adt
1603
+ . all_fields ( )
1604
+ . map ( |f| f. ty ( cx. tcx , subs) )
1605
+ . any ( |ty| type_needs_ordered_drop ( cx, ty) ) ,
1606
+ _ => true ,
1607
+ } ;
1608
+ }
1609
+
1610
+ // Check for std types which implement drop, but only for memory allocation.
1611
+ !( ( is_type_diagnostic_item ( cx, ty, sym:: vec_type)
1612
+ && !try_get_generic_ty ( ty, 0 ) . map_or ( false , |ty| type_needs_ordered_drop ( cx, ty) ) )
1613
+ || ( is_type_lang_item ( cx, ty, LangItem :: OwnedBox )
1614
+ && !try_get_generic_ty ( ty, 0 ) . map_or ( false , |ty| type_needs_ordered_drop ( cx, ty) ) )
1615
+ || ( is_type_diagnostic_item ( cx, ty, sym:: option_type)
1616
+ && !try_get_generic_ty ( ty, 0 ) . map_or ( false , |ty| type_needs_ordered_drop ( cx, ty) ) )
1617
+ || ( is_type_diagnostic_item ( cx, ty, sym:: Rc )
1618
+ && !try_get_generic_ty ( ty, 0 ) . map_or ( false , |ty| type_needs_ordered_drop ( cx, ty) ) )
1619
+ || ( is_type_diagnostic_item ( cx, ty, sym:: Arc )
1620
+ && !try_get_generic_ty ( ty, 0 ) . map_or ( false , |ty| type_needs_ordered_drop ( cx, ty) ) ) )
1621
+ }
1622
+
1623
+ // Extract the generic arguments out of a type
1624
+ fn try_get_generic_ty ( ty : Ty < ' _ > , index : usize ) -> Option < Ty < ' _ > > {
1625
+ if_chain ! {
1626
+ if let ty:: Adt ( _, subs) = ty. kind( ) ;
1627
+ if let Some ( sub) = subs. get( index) ;
1628
+ if let GenericArgKind :: Type ( sub_ty) = sub. unpack( ) ;
1629
+ then {
1630
+ Some ( sub_ty)
1631
+ } else {
1632
+ None
1633
+ }
1634
+ }
1635
+ }
1636
+
1637
+ // Gets all the the temporaries in an expression which need to be dropped afterwards.
1638
+ fn get_temporary_tys ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> Vec < Ty < ' tcx > > {
1639
+ fn f (
1640
+ addr_of : bool ,
1641
+ tcx : TyCtxt < ' tcx > ,
1642
+ typeck : & TypeckResults < ' tcx > ,
1643
+ expr : & ' tcx Expr < ' tcx > ,
1644
+ res : & mut Vec < Ty < ' tcx > > ,
1645
+ ) {
1646
+ match expr. kind {
1647
+ ExprKind :: ConstBlock ( _)
1648
+ | ExprKind :: Tup ( [ ] )
1649
+ | ExprKind :: Array ( [ ] )
1650
+ | ExprKind :: Lit ( _)
1651
+ | ExprKind :: Path ( _)
1652
+ | ExprKind :: Assign ( ..)
1653
+ | ExprKind :: AssignOp ( ..)
1654
+ | ExprKind :: Break ( ..)
1655
+ | ExprKind :: Continue ( _)
1656
+ | ExprKind :: Ret ( _)
1657
+ | ExprKind :: InlineAsm ( _)
1658
+ | ExprKind :: LlvmInlineAsm ( _)
1659
+ | ExprKind :: Yield ( ..)
1660
+ | ExprKind :: Err
1661
+ | ExprKind :: MethodCall ( _, _, [ ] , _) => ( ) ,
1662
+ ExprKind :: MethodCall ( _, _, [ self_arg, args @ ..] , _) => {
1663
+ if addr_of {
1664
+ res. push ( typeck. expr_ty ( expr) ) ;
1665
+ }
1666
+ let ref_self = typeck
1667
+ . type_dependent_def_id ( expr. hir_id )
1668
+ . map_or ( false , |id| tcx. fn_sig ( id) . skip_binder ( ) . inputs ( ) [ 0 ] . is_ref ( ) ) ;
1669
+ f ( ref_self, tcx, typeck, self_arg, res) ;
1670
+ for arg in args {
1671
+ f ( false , tcx, typeck, arg, res) ;
1672
+ }
1673
+ } ,
1674
+ ExprKind :: AddrOf ( _, _, expr) | ExprKind :: Field ( expr, _) => f ( true , tcx, typeck, expr, res) ,
1675
+ ExprKind :: Box ( expr) | ExprKind :: Cast ( expr, _) => f ( false , tcx, typeck, expr, res) ,
1676
+ ExprKind :: Array ( exprs @ [ _, ..] ) | ExprKind :: Tup ( exprs @ [ _, ..] ) | ExprKind :: Call ( _, exprs) => {
1677
+ if addr_of {
1678
+ res. push ( typeck. expr_ty ( expr) ) ;
1679
+ }
1680
+ for expr in exprs {
1681
+ f ( false , tcx, typeck, expr, res) ;
1682
+ }
1683
+ } ,
1684
+ ExprKind :: Binary ( _, lhs, rhs) => {
1685
+ if addr_of {
1686
+ res. push ( typeck. expr_ty ( expr) ) ;
1687
+ }
1688
+ f ( false , tcx, typeck, lhs, res) ;
1689
+ f ( false , tcx, typeck, rhs, res) ;
1690
+ } ,
1691
+ ExprKind :: Unary ( UnOp :: UnDeref , e) => {
1692
+ f ( typeck. expr_ty ( e) . is_ref ( ) , tcx, typeck, e, res) ;
1693
+ } ,
1694
+ ExprKind :: Type ( e, _) | ExprKind :: Unary ( _, e) => {
1695
+ if addr_of {
1696
+ res. push ( typeck. expr_ty ( expr) ) ;
1697
+ }
1698
+ f ( false , tcx, typeck, e, res) ;
1699
+ } ,
1700
+ ExprKind :: Index ( base, index) => {
1701
+ f ( true , tcx, typeck, base, res) ;
1702
+ f ( false , tcx, typeck, index, res) ;
1703
+ } ,
1704
+ ExprKind :: DropTemps ( _) | ExprKind :: Closure ( ..) => {
1705
+ if addr_of {
1706
+ res. push ( typeck. expr_ty ( expr) ) ;
1707
+ }
1708
+ } ,
1709
+ ExprKind :: Match ( e, _, _) => {
1710
+ if addr_of {
1711
+ res. push ( typeck. expr_ty ( expr) ) ;
1712
+ }
1713
+ f ( true , tcx, typeck, e, res) ;
1714
+ } ,
1715
+ ExprKind :: Block ( b, _) | ExprKind :: Loop ( b, _, _) => {
1716
+ if addr_of {
1717
+ res. push ( typeck. expr_ty ( expr) ) ;
1718
+ }
1719
+ if let Some ( expr) = b. expr {
1720
+ f ( false , tcx, typeck, expr, res) ;
1721
+ }
1722
+ } ,
1723
+ ExprKind :: Struct ( _, fields, _) => {
1724
+ if addr_of {
1725
+ res. push ( typeck. expr_ty ( expr) ) ;
1726
+ }
1727
+ for field in fields {
1728
+ f ( false , tcx, typeck, field. expr , res) ;
1729
+ }
1730
+ } ,
1731
+ ExprKind :: Repeat ( expr, _) => {
1732
+ if addr_of {
1733
+ res. push ( typeck. expr_ty ( expr) ) ;
1734
+ }
1735
+ f ( false , tcx, typeck, expr, res) ;
1736
+ } ,
1737
+ }
1738
+ }
1739
+
1740
+ let mut res = Vec :: new ( ) ;
1741
+ f ( false , cx. tcx , cx. typeck_results ( ) , expr, & mut res) ;
1742
+ res
1743
+ }
1744
+
1575
1745
fn find_sugg_for_if_let < ' tcx > (
1576
1746
cx : & LateContext < ' tcx > ,
1577
1747
expr : & ' tcx Expr < ' _ > ,
1578
- op : & Expr < ' _ > ,
1748
+ op : & ' tcx Expr < ' tcx > ,
1579
1749
arm : & Arm < ' _ > ,
1580
1750
keyword : & ' static str ,
1751
+ has_else : bool ,
1581
1752
) {
1582
- let good_method = match arm. pat . kind {
1753
+ let op_ty = cx. typeck_results ( ) . expr_ty ( op) ;
1754
+ let ( good_method, inner_ty) = match arm. pat . kind {
1583
1755
PatKind :: TupleStruct ( ref path, ref patterns, _) if patterns. len ( ) == 1 => {
1584
1756
if let PatKind :: Wild = patterns[ 0 ] . kind {
1585
- if cx. typeck_results ( ) . expr_ty ( op) . needs_drop ( cx. tcx , cx. param_env ) {
1586
- // if let doesn't drop the value until after the block ends
1587
- return ;
1588
- } else if match_qpath ( path, & paths:: RESULT_OK ) {
1589
- "is_ok()"
1757
+ if match_qpath ( path, & paths:: RESULT_OK ) {
1758
+ ( "is_ok()" , try_get_generic_ty ( op_ty, 0 ) . unwrap_or ( op_ty) )
1590
1759
} else if match_qpath ( path, & paths:: RESULT_ERR ) {
1591
- "is_err()"
1760
+ ( "is_err()" , try_get_generic_ty ( op_ty , 1 ) . unwrap_or ( op_ty ) )
1592
1761
} else if match_qpath ( path, & paths:: OPTION_SOME ) {
1593
- "is_some()"
1762
+ ( "is_some()" , op_ty )
1594
1763
} else if match_qpath ( path, & paths:: POLL_READY ) {
1595
- "is_ready()"
1764
+ ( "is_ready()" , op_ty )
1596
1765
} else if match_qpath ( path, & paths:: IPADDR_V4 ) {
1597
- "is_ipv4()"
1766
+ ( "is_ipv4()" , op_ty )
1598
1767
} else if match_qpath ( path, & paths:: IPADDR_V6 ) {
1599
- "is_ipv6()"
1768
+ ( "is_ipv6()" , op_ty )
1600
1769
} else {
1601
1770
return ;
1602
1771
}
@@ -1605,17 +1774,37 @@ mod redundant_pattern_match {
1605
1774
}
1606
1775
} ,
1607
1776
PatKind :: Path ( ref path) => {
1608
- if match_qpath ( path, & paths:: OPTION_NONE ) {
1777
+ let method = if match_qpath ( path, & paths:: OPTION_NONE ) {
1609
1778
"is_none()"
1610
1779
} else if match_qpath ( path, & paths:: POLL_PENDING ) {
1611
1780
"is_pending()"
1612
1781
} else {
1613
1782
return ;
1614
- }
1783
+ } ;
1784
+ // drop for `None` and `Pending` do nothing
1785
+ ( method, cx. tcx . types . unit )
1615
1786
} ,
1616
1787
_ => return ,
1617
1788
} ;
1618
1789
1790
+ // if this is the last expression in a block then the lifetime of the expression is extended
1791
+ // past the end of the block
1792
+ let check_ty = if has_else
1793
+ || ( keyword == "if" && {
1794
+ let map = cx. tcx . hir ( ) ;
1795
+ let id = map. get_parent_node ( expr. hir_id ) ;
1796
+ id != expr. hir_id && matches ! ( map. find( id) , Some ( Node :: Block ( ..) ) )
1797
+ } ) {
1798
+ op_ty
1799
+ } else {
1800
+ inner_ty
1801
+ } ;
1802
+
1803
+ let needs_drop = type_needs_ordered_drop ( cx, check_ty)
1804
+ || get_temporary_tys ( cx, op)
1805
+ . iter ( )
1806
+ . any ( |ty| type_needs_ordered_drop ( cx, ty) ) ;
1807
+
1619
1808
// check that `while_let_on_iterator` lint does not trigger
1620
1809
if_chain ! {
1621
1810
if keyword == "while" ;
@@ -1648,12 +1837,20 @@ mod redundant_pattern_match {
1648
1837
// while let ... = ... { ... }
1649
1838
// ^^^^^^^^^^^^^^^^^^^
1650
1839
let span = expr_span. until ( op_span. shrink_to_hi ( ) ) ;
1651
- diag. span_suggestion (
1652
- span,
1653
- "try this" ,
1654
- format ! ( "{} {}.{}" , keyword, snippet( cx, op_span, "_" ) , good_method) ,
1655
- Applicability :: MachineApplicable , // snippet
1656
- ) ;
1840
+
1841
+ let mut app = if needs_drop {
1842
+ Applicability :: MaybeIncorrect
1843
+ } else {
1844
+ Applicability :: MachineApplicable
1845
+ } ;
1846
+ let sugg = snippet_with_applicability ( cx, op_span, "_" , & mut app) ;
1847
+
1848
+ diag. span_suggestion ( span, "try this" , format ! ( "{} {}.{}" , keyword, sugg, good_method) , app) ;
1849
+
1850
+ if needs_drop {
1851
+ diag. note ( "this will change drop order of the result, as well as all temporaries" ) ;
1852
+ diag. note ( "add `#[allow(clippy::redundant_pattern_matching)]` if this is important" ) ;
1853
+ }
1657
1854
} ,
1658
1855
) ;
1659
1856
}
0 commit comments