@@ -67,19 +67,20 @@ use rustc_data_structures::unhash::UnhashMap;
67
67
use rustc_hir as hir;
68
68
use rustc_hir:: def:: { DefKind , Res } ;
69
69
use rustc_hir:: def_id:: DefId ;
70
- use rustc_hir:: hir_id:: HirIdSet ;
70
+ use rustc_hir:: hir_id:: { HirIdMap , HirIdSet } ;
71
71
use rustc_hir:: intravisit:: { self , walk_expr, ErasedMap , FnKind , NestedVisitorMap , Visitor } ;
72
72
use rustc_hir:: LangItem :: { ResultErr , ResultOk } ;
73
73
use rustc_hir:: {
74
74
def, Arm , BindingAnnotation , Block , Body , Constness , Destination , Expr , ExprKind , FnDecl , GenericArgs , HirId , Impl ,
75
- ImplItem , ImplItemKind , IsAsync , Item , ItemKind , LangItem , Local , MatchSource , Node , Param , Pat , PatKind , Path ,
76
- PathSegment , PrimTy , QPath , Stmt , StmtKind , TraitItem , TraitItemKind , TraitRef , TyKind , UnOp ,
75
+ ImplItem , ImplItemKind , IsAsync , Item , ItemKind , LangItem , Local , MatchSource , Mutability , Node , Param , Pat ,
76
+ PatKind , Path , PathSegment , PrimTy , QPath , Stmt , StmtKind , TraitItem , TraitItemKind , TraitRef , TyKind , UnOp ,
77
77
} ;
78
78
use rustc_lint:: { LateContext , Level , Lint , LintContext } ;
79
79
use rustc_middle:: hir:: exports:: Export ;
80
80
use rustc_middle:: hir:: map:: Map ;
81
81
use rustc_middle:: ty as rustc_ty;
82
- use rustc_middle:: ty:: { layout:: IntegerExt , DefIdTree , Ty , TyCtxt , TypeFoldable } ;
82
+ use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow } ;
83
+ use rustc_middle:: ty:: { layout:: IntegerExt , DefIdTree , Ty , TyCtxt , TypeAndMut , TypeFoldable } ;
83
84
use rustc_semver:: RustcVersion ;
84
85
use rustc_session:: Session ;
85
86
use rustc_span:: hygiene:: { ExpnKind , MacroKind } ;
@@ -603,8 +604,82 @@ pub fn can_move_expr_to_closure_no_visit(
603
604
}
604
605
}
605
606
606
- /// Checks if the expression can be moved into a closure as is.
607
- pub fn can_move_expr_to_closure ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> bool {
607
+ /// How a local is captured by a closure
608
+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
609
+ pub enum CaptureKind {
610
+ Value ,
611
+ Ref ( Mutability ) ,
612
+ }
613
+ impl std:: ops:: BitOr for CaptureKind {
614
+ type Output = Self ;
615
+ fn bitor ( self , rhs : Self ) -> Self :: Output {
616
+ match ( self , rhs) {
617
+ ( CaptureKind :: Value , _) | ( _, CaptureKind :: Value ) => CaptureKind :: Value ,
618
+ ( CaptureKind :: Ref ( Mutability :: Mut ) , CaptureKind :: Ref ( _) )
619
+ | ( CaptureKind :: Ref ( _) , CaptureKind :: Ref ( Mutability :: Mut ) ) => CaptureKind :: Ref ( Mutability :: Mut ) ,
620
+ ( CaptureKind :: Ref ( Mutability :: Not ) , CaptureKind :: Ref ( Mutability :: Not ) ) => CaptureKind :: Ref ( Mutability :: Not ) ,
621
+ }
622
+ }
623
+ }
624
+ impl std:: ops:: BitOrAssign for CaptureKind {
625
+ fn bitor_assign ( & mut self , rhs : Self ) {
626
+ * self = * self | rhs;
627
+ }
628
+ }
629
+
630
+ /// Given an expression referencing a local, determines how it would be captured in a closure.
631
+ /// Note as this will walk up to parent expressions until the capture can be determined it should
632
+ /// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
633
+ /// function argument (other than a receiver).
634
+ pub fn capture_local_usage ( cx : & LateContext < ' tcx > , e : & Expr < ' _ > ) -> CaptureKind {
635
+ debug_assert ! ( matches!(
636
+ e. kind,
637
+ ExprKind :: Path ( QPath :: Resolved ( None , Path { res: Res :: Local ( _) , .. } ) )
638
+ ) ) ;
639
+
640
+ let map = cx. tcx . hir ( ) ;
641
+ let mut child_id = e. hir_id ;
642
+ let mut capture = CaptureKind :: Value ;
643
+
644
+ for ( parent_id, parent) in map. parent_iter ( e. hir_id ) {
645
+ if let [ Adjustment {
646
+ kind : Adjust :: Deref ( _) | Adjust :: Borrow ( AutoBorrow :: Ref ( ..) ) ,
647
+ target,
648
+ } , ref adjust @ ..] = * cx
649
+ . typeck_results ( )
650
+ . adjustments ( )
651
+ . get ( child_id)
652
+ . map_or ( & [ ] [ ..] , |x| & * * x)
653
+ {
654
+ if let rustc_ty:: RawPtr ( TypeAndMut { mutbl : mutability, .. } ) | rustc_ty:: Ref ( _, _, mutability) =
655
+ * adjust. last ( ) . map_or ( target, |a| a. target ) . kind ( )
656
+ {
657
+ return CaptureKind :: Ref ( mutability) ;
658
+ }
659
+ }
660
+
661
+ if let Node :: Expr ( e) = parent {
662
+ match e. kind {
663
+ ExprKind :: AddrOf ( _, mutability, _) => return CaptureKind :: Ref ( mutability) ,
664
+ ExprKind :: Index ( ..) | ExprKind :: Unary ( UnOp :: Deref , _) => capture = CaptureKind :: Ref ( Mutability :: Not ) ,
665
+ ExprKind :: Assign ( lhs, ..) | ExprKind :: Assign ( _, lhs, _) if lhs. hir_id == child_id => {
666
+ return CaptureKind :: Ref ( Mutability :: Mut ) ;
667
+ } ,
668
+ _ => break ,
669
+ }
670
+ } else {
671
+ break ;
672
+ }
673
+
674
+ child_id = parent_id;
675
+ }
676
+
677
+ capture
678
+ }
679
+
680
+ /// Checks if the expression can be moved into a closure as is. This will return a list of captures
681
+ /// if so, otherwise, `None`.
682
+ pub fn can_move_expr_to_closure ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < HirIdMap < CaptureKind > > {
608
683
struct V < ' cx , ' tcx > {
609
684
cx : & ' cx LateContext < ' tcx > ,
610
685
// Stack of potential break targets contained in the expression.
@@ -613,6 +688,9 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
613
688
locals : HirIdSet ,
614
689
/// Whether this expression can be turned into a closure.
615
690
allow_closure : bool ,
691
+ /// Locals which need to be captured, and whether they need to be by value, reference, or
692
+ /// mutable reference.
693
+ captures : HirIdMap < CaptureKind > ,
616
694
}
617
695
impl Visitor < ' tcx > for V < ' _ , ' tcx > {
618
696
type Map = ErasedMap < ' tcx > ;
@@ -624,13 +702,23 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
624
702
if !self . allow_closure {
625
703
return ;
626
704
}
627
- if let ExprKind :: Loop ( b, ..) = e. kind {
628
- self . loops . push ( e. hir_id ) ;
629
- self . visit_block ( b) ;
630
- self . loops . pop ( ) ;
631
- } else {
632
- self . allow_closure &= can_move_expr_to_closure_no_visit ( self . cx , e, & self . loops , & self . locals ) ;
633
- walk_expr ( self , e) ;
705
+
706
+ match e. kind {
707
+ ExprKind :: Path ( QPath :: Resolved ( None , & Path { res : Res :: Local ( l) , .. } ) ) => {
708
+ if !self . locals . contains ( & l) {
709
+ let cap = capture_local_usage ( self . cx , e) ;
710
+ self . captures . entry ( l) . and_modify ( |e| * e |= cap) . or_insert ( cap) ;
711
+ }
712
+ } ,
713
+ ExprKind :: Loop ( b, ..) => {
714
+ self . loops . push ( e. hir_id ) ;
715
+ self . visit_block ( b) ;
716
+ self . loops . pop ( ) ;
717
+ } ,
718
+ _ => {
719
+ self . allow_closure &= can_move_expr_to_closure_no_visit ( self . cx , e, & self . loops , & self . locals ) ;
720
+ walk_expr ( self , e) ;
721
+ } ,
634
722
}
635
723
}
636
724
@@ -646,9 +734,10 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
646
734
allow_closure : true ,
647
735
loops : Vec :: new ( ) ,
648
736
locals : HirIdSet :: default ( ) ,
737
+ captures : HirIdMap :: default ( ) ,
649
738
} ;
650
739
v. visit_expr ( expr) ;
651
- v. allow_closure
740
+ v. allow_closure . then ( || v . captures )
652
741
}
653
742
654
743
/// Returns the method names and argument list of nested method call expressions that make up
0 commit comments