@@ -2,24 +2,32 @@ use crate::{
2
2
map_unit_fn:: OPTION_MAP_UNIT_FN ,
3
3
matches:: MATCH_AS_REF ,
4
4
utils:: {
5
- is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, peel_hir_expr_refs,
6
- peel_mid_ty_refs_is_mutable, snippet_with_applicability, span_lint_and_sugg,
5
+ can_partially_move_ty, is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths,
6
+ peel_hir_expr_refs, peel_mid_ty_refs_is_mutable, snippet_with_applicability, snippet_with_context,
7
+ span_lint_and_sugg,
7
8
} ,
8
9
} ;
9
10
use rustc_ast:: util:: parser:: PREC_POSTFIX ;
10
11
use rustc_errors:: Applicability ;
11
- use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , Mutability , Pat , PatKind , QPath } ;
12
+ use rustc_hir:: {
13
+ def:: Res ,
14
+ intravisit:: { walk_expr, ErasedMap , NestedVisitorMap , Visitor } ,
15
+ Arm , BindingAnnotation , Block , Expr , ExprKind , Mutability , Pat , PatKind , Path , QPath ,
16
+ } ;
12
17
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
13
18
use rustc_middle:: lint:: in_external_macro;
14
19
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
15
- use rustc_span:: symbol:: { sym, Ident } ;
20
+ use rustc_span:: {
21
+ symbol:: { sym, Ident } ,
22
+ SyntaxContext ,
23
+ } ;
16
24
17
25
declare_clippy_lint ! {
18
26
/// **What it does:** Checks for usages of `match` which could be implemented using `map`
19
27
///
20
28
/// **Why is this bad?** Using the `map` method is clearer and more concise.
21
29
///
22
- /// **Known problems:** `map` is not capable of representing some control flow which works fine in `match` .
30
+ /// **Known problems:** None .
23
31
///
24
32
/// **Example:**
25
33
///
@@ -34,7 +42,7 @@ declare_clippy_lint! {
34
42
/// Some(0).map(|x| x + 1);
35
43
/// ```
36
44
pub MANUAL_MAP ,
37
- nursery ,
45
+ style ,
38
46
"reimplementation of `map`"
39
47
}
40
48
@@ -52,43 +60,46 @@ impl LateLintPass<'_> for ManualMap {
52
60
{
53
61
let ( scrutinee_ty, ty_ref_count, ty_mutability) =
54
62
peel_mid_ty_refs_is_mutable ( cx. typeck_results ( ) . expr_ty ( scrutinee) ) ;
55
- if !is_type_diagnostic_item ( cx, scrutinee_ty, sym:: option_type)
56
- || ! is_type_diagnostic_item ( cx, cx. typeck_results ( ) . expr_ty ( expr) , sym:: option_type)
63
+ if !( is_type_diagnostic_item ( cx, scrutinee_ty, sym:: option_type)
64
+ && is_type_diagnostic_item ( cx, cx. typeck_results ( ) . expr_ty ( expr) , sym:: option_type) )
57
65
{
58
66
return ;
59
67
}
60
68
61
- let ( some_expr, some_pat, pat_ref_count, is_wild_none) =
62
- match ( try_parse_pattern ( cx, arm1. pat ) , try_parse_pattern ( cx, arm2. pat ) ) {
63
- ( Some ( OptionPat :: Wild ) , Some ( OptionPat :: Some { pattern, ref_count } ) )
64
- if is_none_expr ( cx, arm1. body ) =>
65
- {
66
- ( arm2. body , pattern, ref_count, true )
67
- } ,
68
- ( Some ( OptionPat :: None ) , Some ( OptionPat :: Some { pattern, ref_count } ) )
69
- if is_none_expr ( cx, arm1. body ) =>
70
- {
71
- ( arm2. body , pattern, ref_count, false )
72
- } ,
73
- ( Some ( OptionPat :: Some { pattern, ref_count } ) , Some ( OptionPat :: Wild ) )
74
- if is_none_expr ( cx, arm2. body ) =>
75
- {
76
- ( arm1. body , pattern, ref_count, true )
77
- } ,
78
- ( Some ( OptionPat :: Some { pattern, ref_count } ) , Some ( OptionPat :: None ) )
79
- if is_none_expr ( cx, arm2. body ) =>
80
- {
81
- ( arm1. body , pattern, ref_count, false )
82
- } ,
83
- _ => return ,
84
- } ;
69
+ let expr_ctxt = expr. span . ctxt ( ) ;
70
+ let ( some_expr, some_pat, pat_ref_count, is_wild_none) = match (
71
+ try_parse_pattern ( cx, arm1. pat , expr_ctxt) ,
72
+ try_parse_pattern ( cx, arm2. pat , expr_ctxt) ,
73
+ ) {
74
+ ( Some ( OptionPat :: Wild ) , Some ( OptionPat :: Some { pattern, ref_count } ) )
75
+ if is_none_expr ( cx, arm1. body ) =>
76
+ {
77
+ ( arm2. body , pattern, ref_count, true )
78
+ } ,
79
+ ( Some ( OptionPat :: None ) , Some ( OptionPat :: Some { pattern, ref_count } ) )
80
+ if is_none_expr ( cx, arm1. body ) =>
81
+ {
82
+ ( arm2. body , pattern, ref_count, false )
83
+ } ,
84
+ ( Some ( OptionPat :: Some { pattern, ref_count } ) , Some ( OptionPat :: Wild ) )
85
+ if is_none_expr ( cx, arm2. body ) =>
86
+ {
87
+ ( arm1. body , pattern, ref_count, true )
88
+ } ,
89
+ ( Some ( OptionPat :: Some { pattern, ref_count } ) , Some ( OptionPat :: None ) )
90
+ if is_none_expr ( cx, arm2. body ) =>
91
+ {
92
+ ( arm1. body , pattern, ref_count, false )
93
+ } ,
94
+ _ => return ,
95
+ } ;
85
96
86
97
// Top level or patterns aren't allowed in closures.
87
98
if matches ! ( some_pat. kind, PatKind :: Or ( _) ) {
88
99
return ;
89
100
}
90
101
91
- let some_expr = match get_some_expr ( cx, some_expr) {
102
+ let some_expr = match get_some_expr ( cx, some_expr, expr_ctxt ) {
92
103
Some ( expr) => expr,
93
104
None => return ,
94
105
} ;
@@ -99,6 +110,10 @@ impl LateLintPass<'_> for ManualMap {
99
110
return ;
100
111
}
101
112
113
+ if !can_move_expr_to_closure ( cx, some_expr) {
114
+ return ;
115
+ }
116
+
102
117
// Determine which binding mode to use.
103
118
let explicit_ref = some_pat. contains_explicit_ref_binding ( ) ;
104
119
let binding_ref = explicit_ref. or_else ( || ( ty_ref_count != pat_ref_count) . then ( || ty_mutability) ) ;
@@ -111,47 +126,50 @@ impl LateLintPass<'_> for ManualMap {
111
126
112
127
let mut app = Applicability :: MachineApplicable ;
113
128
114
- // Remove address-of expressions from the scrutinee. `as_ref` will be called,
115
- // the type is copyable, or the option is being passed by value.
129
+ // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
130
+ // it's being passed by value.
116
131
let scrutinee = peel_hir_expr_refs ( scrutinee) . 0 ;
117
- let scrutinee_str = snippet_with_applicability ( cx, scrutinee. span , "_ ", & mut app) ;
118
- let scrutinee_str = if expr . precedence ( ) . order ( ) < PREC_POSTFIX {
119
- // Parens are needed to chain method calls.
120
- format ! ( "({})" , scrutinee_str)
121
- } else {
122
- scrutinee_str. into ( )
123
- } ;
132
+ let scrutinee_str = snippet_with_context ( cx, scrutinee. span , expr_ctxt , ".. ", & mut app) ;
133
+ let scrutinee_str =
134
+ if scrutinee . span . ctxt ( ) == expr . span . ctxt ( ) && scrutinee . precedence ( ) . order ( ) < PREC_POSTFIX {
135
+ format ! ( "({})" , scrutinee_str)
136
+ } else {
137
+ scrutinee_str. into ( )
138
+ } ;
124
139
125
140
let body_str = if let PatKind :: Binding ( annotation, _, some_binding, None ) = some_pat. kind {
126
- if let Some ( func) = can_pass_as_func ( cx, some_binding, some_expr) {
127
- snippet_with_applicability ( cx, func. span , ".." , & mut app) . into_owned ( )
128
- } else {
129
- if match_var ( some_expr, some_binding. name )
130
- && !is_allowed ( cx, MATCH_AS_REF , expr. hir_id )
131
- && binding_ref. is_some ( )
132
- {
133
- return ;
134
- }
141
+ match can_pass_as_func ( cx, some_binding, some_expr) {
142
+ Some ( func) if func. span . ctxt ( ) == some_expr. span . ctxt ( ) => {
143
+ snippet_with_applicability ( cx, func. span , ".." , & mut app) . into_owned ( )
144
+ } ,
145
+ _ => {
146
+ if match_var ( some_expr, some_binding. name )
147
+ && !is_allowed ( cx, MATCH_AS_REF , expr. hir_id )
148
+ && binding_ref. is_some ( )
149
+ {
150
+ return ;
151
+ }
135
152
136
- // `ref` and `ref mut` annotations were handled earlier.
137
- let annotation = if matches ! ( annotation, BindingAnnotation :: Mutable ) {
138
- "mut "
139
- } else {
140
- ""
141
- } ;
142
- format ! (
143
- "|{}{}| {}" ,
144
- annotation,
145
- some_binding,
146
- snippet_with_applicability( cx, some_expr. span, ".." , & mut app)
147
- )
153
+ // `ref` and `ref mut` annotations were handled earlier.
154
+ let annotation = if matches ! ( annotation, BindingAnnotation :: Mutable ) {
155
+ "mut "
156
+ } else {
157
+ ""
158
+ } ;
159
+ format ! (
160
+ "|{}{}| {}" ,
161
+ annotation,
162
+ some_binding,
163
+ snippet_with_context( cx, some_expr. span, expr_ctxt, ".." , & mut app)
164
+ )
165
+ } ,
148
166
}
149
167
} else if !is_wild_none && explicit_ref. is_none ( ) {
150
168
// TODO: handle explicit reference annotations.
151
169
format ! (
152
170
"|{}| {}" ,
153
- snippet_with_applicability ( cx, some_pat. span, ".." , & mut app) ,
154
- snippet_with_applicability ( cx, some_expr. span, ".." , & mut app)
171
+ snippet_with_context ( cx, some_pat. span, expr_ctxt , ".." , & mut app) ,
172
+ snippet_with_context ( cx, some_expr. span, expr_ctxt , ".." , & mut app)
155
173
)
156
174
} else {
157
175
// Refutable bindings and mixed reference annotations can't be handled by `map`.
@@ -171,6 +189,51 @@ impl LateLintPass<'_> for ManualMap {
171
189
}
172
190
}
173
191
192
+ // Checks if the expression can be moved into a closure as is.
193
+ fn can_move_expr_to_closure ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> bool {
194
+ struct V < ' cx , ' tcx > {
195
+ cx : & ' cx LateContext < ' tcx > ,
196
+ make_closure : bool ,
197
+ }
198
+ impl Visitor < ' tcx > for V < ' _ , ' tcx > {
199
+ type Map = ErasedMap < ' tcx > ;
200
+ fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
201
+ NestedVisitorMap :: None
202
+ }
203
+
204
+ fn visit_expr ( & mut self , e : & ' tcx Expr < ' _ > ) {
205
+ match e. kind {
206
+ ExprKind :: Break ( ..)
207
+ | ExprKind :: Continue ( _)
208
+ | ExprKind :: Ret ( _)
209
+ | ExprKind :: Yield ( ..)
210
+ | ExprKind :: InlineAsm ( _)
211
+ | ExprKind :: LlvmInlineAsm ( _) => {
212
+ self . make_closure = false ;
213
+ } ,
214
+ // Accessing a field of a local value can only be done if the type isn't
215
+ // partially moved.
216
+ ExprKind :: Field ( base_expr, _)
217
+ if matches ! (
218
+ base_expr. kind,
219
+ ExprKind :: Path ( QPath :: Resolved ( _, Path { res: Res :: Local ( _) , .. } ) )
220
+ ) && can_partially_move_ty ( self . cx , self . cx . typeck_results ( ) . expr_ty ( base_expr) ) =>
221
+ {
222
+ // TODO: check if the local has been partially moved. Assume it has for now.
223
+ self . make_closure = false ;
224
+ return ;
225
+ }
226
+ _ => ( ) ,
227
+ } ;
228
+ walk_expr ( self , e) ;
229
+ }
230
+ }
231
+
232
+ let mut v = V { cx, make_closure : true } ;
233
+ v. visit_expr ( expr) ;
234
+ v. make_closure
235
+ }
236
+
174
237
// Checks whether the expression could be passed as a function, or whether a closure is needed.
175
238
// Returns the function to be passed to `map` if it exists.
176
239
fn can_pass_as_func ( cx : & LateContext < ' tcx > , binding : Ident , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
@@ -198,11 +261,11 @@ enum OptionPat<'a> {
198
261
199
262
// Try to parse into a recognized `Option` pattern.
200
263
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
201
- fn try_parse_pattern ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > ) -> Option < OptionPat < ' tcx > > {
202
- fn f ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > , ref_count : usize ) -> Option < OptionPat < ' tcx > > {
264
+ fn try_parse_pattern ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > , ctxt : SyntaxContext ) -> Option < OptionPat < ' tcx > > {
265
+ fn f ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > , ref_count : usize , ctxt : SyntaxContext ) -> Option < OptionPat < ' tcx > > {
203
266
match pat. kind {
204
267
PatKind :: Wild => Some ( OptionPat :: Wild ) ,
205
- PatKind :: Ref ( pat, _) => f ( cx, pat, ref_count + 1 ) ,
268
+ PatKind :: Ref ( pat, _) => f ( cx, pat, ref_count + 1 , ctxt ) ,
206
269
PatKind :: Path ( QPath :: Resolved ( None , path) )
207
270
if path
208
271
. res
@@ -215,18 +278,19 @@ fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<Optio
215
278
if path
216
279
. res
217
280
. opt_def_id ( )
218
- . map_or ( false , |id| match_def_path ( cx, id, & paths:: OPTION_SOME ) ) =>
281
+ . map_or ( false , |id| match_def_path ( cx, id, & paths:: OPTION_SOME ) )
282
+ && pat. span . ctxt ( ) == ctxt =>
219
283
{
220
284
Some ( OptionPat :: Some { pattern, ref_count } )
221
285
} ,
222
286
_ => None ,
223
287
}
224
288
}
225
- f ( cx, pat, 0 )
289
+ f ( cx, pat, 0 , ctxt )
226
290
}
227
291
228
292
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
229
- fn get_some_expr ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
293
+ fn get_some_expr ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , ctxt : SyntaxContext ) -> Option < & ' tcx Expr < ' tcx > > {
230
294
// TODO: Allow more complex expressions.
231
295
match expr. kind {
232
296
ExprKind :: Call (
@@ -235,7 +299,7 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
235
299
..
236
300
} ,
237
301
[ arg] ,
238
- ) => {
302
+ ) if ctxt == expr . span . ctxt ( ) => {
239
303
if match_def_path ( cx, path. res . opt_def_id ( ) ?, & paths:: OPTION_SOME ) {
240
304
Some ( arg)
241
305
} else {
@@ -249,7 +313,7 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
249
313
..
250
314
} ,
251
315
_,
252
- ) => get_some_expr ( cx, expr) ,
316
+ ) => get_some_expr ( cx, expr, ctxt ) ,
253
317
_ => None ,
254
318
}
255
319
}
0 commit comments