@@ -4,12 +4,17 @@ use clippy_utils::ty::is_copy;
44use clippy_utils:: ty:: is_type_diagnostic_item;
55use rustc_data_structures:: fx:: FxHashSet ;
66use rustc_errors:: Applicability ;
7+ use rustc_hir:: def:: Res ;
78use rustc_hir:: intravisit:: { walk_path, Visitor } ;
9+ use rustc_hir:: ExprKind ;
10+ use rustc_hir:: Node ;
11+ use rustc_hir:: PatKind ;
12+ use rustc_hir:: QPath ;
813use rustc_hir:: { self , HirId , Path } ;
914use rustc_lint:: LateContext ;
1015use rustc_middle:: hir:: nested_filter;
1116use rustc_span:: source_map:: Span ;
12- use rustc_span:: { sym, Symbol } ;
17+ use rustc_span:: sym;
1318
1419use super :: MAP_UNWRAP_OR ;
1520
@@ -26,23 +31,41 @@ pub(super) fn check<'tcx>(
2631 // lint if the caller of `map()` is an `Option`
2732 if is_type_diagnostic_item ( cx, cx. typeck_results ( ) . expr_ty ( recv) , sym:: Option ) {
2833 if !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( unwrap_arg) ) {
29- // Do not lint if the `map` argument uses identifiers in the `map`
30- // argument that are also used in the `unwrap_or` argument
34+ // Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
35+ // borrowck errors, see #10579 for one such instance.
36+ // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
37+ // ```
38+ // let x = vec![1, 2];
39+ // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
40+ // ```
41+ // This compiles, but changing it to `map_or` will produce a compile error:
42+ // ```
43+ // let x = vec![1, 2];
44+ // x.get(0..1).map_or(x, |s| s.to_vec())
45+ // ^ moving `x` here
46+ // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
47+ // ```
48+ // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
49+ // before the call to `unwrap_or`.
3150
3251 let mut unwrap_visitor = UnwrapVisitor {
3352 cx,
3453 identifiers : FxHashSet :: default ( ) ,
3554 } ;
3655 unwrap_visitor. visit_expr ( unwrap_arg) ;
3756
38- let mut map_expr_visitor = MapExprVisitor {
57+ let mut reference_visitor = ReferenceVisitor {
3958 cx,
4059 identifiers : unwrap_visitor. identifiers ,
41- found_identifier : false ,
60+ found_reference : false ,
61+ unwrap_or_span : unwrap_arg. span ,
4262 } ;
43- map_expr_visitor. visit_expr ( map_arg) ;
4463
45- if map_expr_visitor. found_identifier {
64+ let map = cx. tcx . hir ( ) ;
65+ let body = map. body ( map. body_owned_by ( map. enclosing_body_owner ( expr. hir_id ) ) ) ;
66+ reference_visitor. visit_body ( body) ;
67+
68+ if reference_visitor. found_reference {
4669 return ;
4770 }
4871 }
@@ -91,14 +114,19 @@ pub(super) fn check<'tcx>(
91114
92115struct UnwrapVisitor < ' a , ' tcx > {
93116 cx : & ' a LateContext < ' tcx > ,
94- identifiers : FxHashSet < Symbol > ,
117+ identifiers : FxHashSet < HirId > ,
95118}
96119
97120impl < ' a , ' tcx > Visitor < ' tcx > for UnwrapVisitor < ' a , ' tcx > {
98121 type NestedFilter = nested_filter:: All ;
99122
100- fn visit_path ( & mut self , path : & Path < ' tcx > , _id : HirId ) {
101- self . identifiers . insert ( ident ( path) ) ;
123+ fn visit_path ( & mut self , path : & Path < ' tcx > , _: HirId ) {
124+ if let Res :: Local ( local_id) = path. res
125+ && let Some ( Node :: Pat ( pat) ) = self . cx . tcx . hir ( ) . find ( local_id)
126+ && let PatKind :: Binding ( _, local_id, ..) = pat. kind
127+ {
128+ self . identifiers . insert ( local_id) ;
129+ }
102130 walk_path ( self , path) ;
103131 }
104132
@@ -107,32 +135,35 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
107135 }
108136}
109137
110- struct MapExprVisitor < ' a , ' tcx > {
138+ struct ReferenceVisitor < ' a , ' tcx > {
111139 cx : & ' a LateContext < ' tcx > ,
112- identifiers : FxHashSet < Symbol > ,
113- found_identifier : bool ,
140+ identifiers : FxHashSet < HirId > ,
141+ found_reference : bool ,
142+ unwrap_or_span : Span ,
114143}
115144
116- impl < ' a , ' tcx > Visitor < ' tcx > for MapExprVisitor < ' a , ' tcx > {
145+ impl < ' a , ' tcx > Visitor < ' tcx > for ReferenceVisitor < ' a , ' tcx > {
117146 type NestedFilter = nested_filter:: All ;
118-
119- fn visit_path ( & mut self , path : & Path < ' tcx > , _id : HirId ) {
120- if self . identifiers . contains ( & ident ( path) ) {
121- self . found_identifier = true ;
122- return ;
147+ fn visit_expr ( & mut self , expr : & ' tcx rustc_hir:: Expr < ' _ > ) {
148+ // If we haven't found a reference yet, check if this references
149+ // one of the locals that was moved in the `unwrap_or` argument.
150+ // We are only interested in exprs that appear before the `unwrap_or` call.
151+ if !self . found_reference {
152+ if expr. span < self . unwrap_or_span
153+ && let ExprKind :: Path ( ref path) = expr. kind
154+ && let QPath :: Resolved ( _, path) = path
155+ && let Res :: Local ( local_id) = path. res
156+ && let Some ( Node :: Pat ( pat) ) = self . cx . tcx . hir ( ) . find ( local_id)
157+ && let PatKind :: Binding ( _, local_id, ..) = pat. kind
158+ && self . identifiers . contains ( & local_id)
159+ {
160+ self . found_reference = true ;
161+ }
162+ rustc_hir:: intravisit:: walk_expr ( self , expr) ;
123163 }
124- walk_path ( self , path) ;
125164 }
126165
127166 fn nested_visit_map ( & mut self ) -> Self :: Map {
128167 self . cx . tcx . hir ( )
129168 }
130169}
131-
132- fn ident ( path : & Path < ' _ > ) -> Symbol {
133- path. segments
134- . last ( )
135- . expect ( "segments should be composed of at least 1 element" )
136- . ident
137- . name
138- }
0 commit comments