@@ -4,12 +4,17 @@ use clippy_utils::ty::is_copy;
4
4
use clippy_utils:: ty:: is_type_diagnostic_item;
5
5
use rustc_data_structures:: fx:: FxHashSet ;
6
6
use rustc_errors:: Applicability ;
7
+ use rustc_hir:: def:: Res ;
7
8
use 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 ;
8
13
use rustc_hir:: { self , HirId , Path } ;
9
14
use rustc_lint:: LateContext ;
10
15
use rustc_middle:: hir:: nested_filter;
11
16
use rustc_span:: source_map:: Span ;
12
- use rustc_span:: { sym, Symbol } ;
17
+ use rustc_span:: sym;
13
18
14
19
use super :: MAP_UNWRAP_OR ;
15
20
@@ -26,23 +31,41 @@ pub(super) fn check<'tcx>(
26
31
// lint if the caller of `map()` is an `Option`
27
32
if is_type_diagnostic_item ( cx, cx. typeck_results ( ) . expr_ty ( recv) , sym:: Option ) {
28
33
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`.
31
50
32
51
let mut unwrap_visitor = UnwrapVisitor {
33
52
cx,
34
53
identifiers : FxHashSet :: default ( ) ,
35
54
} ;
36
55
unwrap_visitor. visit_expr ( unwrap_arg) ;
37
56
38
- let mut map_expr_visitor = MapExprVisitor {
57
+ let mut reference_visitor = ReferenceVisitor {
39
58
cx,
40
59
identifiers : unwrap_visitor. identifiers ,
41
- found_identifier : false ,
60
+ found_reference : false ,
61
+ unwrap_or_span : unwrap_arg. span ,
42
62
} ;
43
- map_expr_visitor. visit_expr ( map_arg) ;
44
63
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 {
46
69
return ;
47
70
}
48
71
}
@@ -91,14 +114,19 @@ pub(super) fn check<'tcx>(
91
114
92
115
struct UnwrapVisitor < ' a , ' tcx > {
93
116
cx : & ' a LateContext < ' tcx > ,
94
- identifiers : FxHashSet < Symbol > ,
117
+ identifiers : FxHashSet < HirId > ,
95
118
}
96
119
97
120
impl < ' a , ' tcx > Visitor < ' tcx > for UnwrapVisitor < ' a , ' tcx > {
98
121
type NestedFilter = nested_filter:: All ;
99
122
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
+ }
102
130
walk_path ( self , path) ;
103
131
}
104
132
@@ -107,32 +135,35 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
107
135
}
108
136
}
109
137
110
- struct MapExprVisitor < ' a , ' tcx > {
138
+ struct ReferenceVisitor < ' a , ' tcx > {
111
139
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 ,
114
143
}
115
144
116
- impl < ' a , ' tcx > Visitor < ' tcx > for MapExprVisitor < ' a , ' tcx > {
145
+ impl < ' a , ' tcx > Visitor < ' tcx > for ReferenceVisitor < ' a , ' tcx > {
117
146
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) ;
123
163
}
124
- walk_path ( self , path) ;
125
164
}
126
165
127
166
fn nested_visit_map ( & mut self ) -> Self :: Map {
128
167
self . cx . tcx . hir ( )
129
168
}
130
169
}
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