1
1
use crate :: utils:: {
2
- is_allowed , is_type_diagnostic_item, match_def_path, match_path , paths , snippet_with_applicability ,
3
- span_lint_and_sugg,
2
+ is_type_diagnostic_item, match_def_path, paths , peel_hir_expr_refs , peel_mid_ty_refs_is_mutable ,
3
+ snippet_with_applicability , span_lint_and_sugg,
4
4
} ;
5
- use core:: fmt;
6
- use if_chain:: if_chain;
5
+ use rustc_ast:: util:: parser:: PREC_POSTFIX ;
7
6
use rustc_errors:: Applicability ;
8
- use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , PatKind , QPath , Pat , Path , Mutability } ;
7
+ use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , Mutability , Pat , PatKind , Path , QPath } ;
9
8
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10
- use rustc_middle:: { lint:: in_external_macro, ty :: { self , Ty , TyS } } ;
9
+ use rustc_middle:: lint:: in_external_macro;
11
10
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
12
11
use rustc_span:: symbol:: { sym, Ident } ;
13
12
@@ -39,176 +38,159 @@ declare_lint_pass!(ManualMap => [MANUAL_MAP]);
39
38
40
39
impl LateLintPass < ' _ > for ManualMap {
41
40
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
42
- if ! in_external_macro ( cx. sess ( ) , expr. span ) {
41
+ if in_external_macro ( cx. sess ( ) , expr. span ) {
43
42
return ;
44
43
}
45
44
46
45
if let ExprKind :: Match ( scrutinee, [ arm1 @ Arm { guard : None , .. } , arm2 @ Arm { guard : None , .. } ] , _) =
47
46
expr. kind
48
47
{
49
- let scrutinee_ty = cx. typeck_results ( ) . expr_ty ( expr) ;
50
- let some_ty = match extract_some_ty ( cx, scrutinee_ty) {
51
- Some ( ty) => ty,
52
- None => return ,
53
- } ;
48
+ let ( scrutinee_ty, ty_ref_count, ty_mutability) =
49
+ peel_mid_ty_refs_is_mutable ( cx. typeck_results ( ) . expr_ty ( scrutinee) ) ;
50
+ if !is_type_diagnostic_item ( cx, scrutinee_ty, sym:: option_type)
51
+ || !is_type_diagnostic_item ( cx, cx. typeck_results ( ) . expr_ty ( expr) , sym:: option_type)
52
+ {
53
+ return ;
54
+ }
54
55
55
- let ( some_expr, some_pat, is_ref , is_wild_none) =
56
+ let ( some_expr, some_pat, pat_ref_count , is_wild_none) =
56
57
match ( try_parse_pattern ( cx, arm1. pat ) , try_parse_pattern ( cx, arm2. pat ) ) {
57
- ( Some ( OptionPat :: Wild ) , Some ( OptionPat :: Some { pattern, is_ref } ) )
58
+ ( Some ( OptionPat :: Wild ) , Some ( OptionPat :: Some { pattern, ref_count } ) )
58
59
if is_none_expr ( cx, arm1. body ) =>
59
60
{
60
- ( arm2. body , pattern, is_ref , true )
61
+ ( arm2. body , pattern, ref_count , true )
61
62
} ,
62
- ( Some ( OptionPat :: None ) , Some ( OptionPat :: Some { pattern, is_ref } ) )
63
+ ( Some ( OptionPat :: None ) , Some ( OptionPat :: Some { pattern, ref_count } ) )
63
64
if is_none_expr ( cx, arm1. body ) =>
64
65
{
65
- ( arm2. body , pattern, is_ref , false )
66
+ ( arm2. body , pattern, ref_count , false )
66
67
} ,
67
- ( Some ( OptionPat :: Some { pattern, is_ref } ) , Some ( OptionPat :: Wild ) )
68
- if is_none_expr ( cx, arm1 . body ) =>
68
+ ( Some ( OptionPat :: Some { pattern, ref_count } ) , Some ( OptionPat :: Wild ) )
69
+ if is_none_expr ( cx, arm2 . body ) =>
69
70
{
70
- ( arm2 . body , pattern, is_ref , true )
71
+ ( arm1 . body , pattern, ref_count , true )
71
72
} ,
72
- ( Some ( OptionPat :: Some { pattern, is_ref } ) , Some ( OptionPat :: None ) )
73
+ ( Some ( OptionPat :: Some { pattern, ref_count } ) , Some ( OptionPat :: None ) )
73
74
if is_none_expr ( cx, arm2. body ) =>
74
75
{
75
- ( arm1. body , pattern, is_ref , false )
76
+ ( arm1. body , pattern, ref_count , false )
76
77
} ,
77
78
_ => return ,
78
79
} ;
79
80
81
+ // Top level or patterns aren't allowed in closures.
80
82
if matches ! ( some_pat. kind, PatKind :: Or ( _) ) {
81
83
return ;
82
84
}
83
85
84
- if let Some ( some_expr) = get_some_expr ( cx, some_expr) {
85
- let explicit_ref = some_pat. contains_explicit_ref_binding ( ) ;
86
- let binding_mutability = match scrutinee_ty. kind ( ) {
87
- ty:: Ref ( _, _, mutability) if !is_ref => Some ( mutability) ,
88
- _ => explicit_ref,
89
- } ;
86
+ let some_expr = match get_some_expr ( cx, some_expr) {
87
+ Some ( expr) => expr,
88
+ None => return ,
89
+ } ;
90
90
91
- let as_ref_str = match binding_mutability {
92
- Some ( Mutability :: Mut ) => "as_mut()" ,
93
- Some ( Mutability :: Not ) => "as_ref()" ,
94
- None => "" ,
95
- } ;
91
+ // Determine which binding mode to use.
92
+ let explicit_ref = some_pat. contains_explicit_ref_binding ( ) ;
93
+ let binding_mutability = explicit_ref. or ( if ty_ref_count != pat_ref_count {
94
+ Some ( ty_mutability)
95
+ } else {
96
+ None
97
+ } ) ;
98
+ let as_ref_str = match binding_mutability {
99
+ Some ( Mutability :: Mut ) => ".as_mut()" ,
100
+ Some ( Mutability :: Not ) => ".as_ref()" ,
101
+ None => "" ,
102
+ } ;
96
103
97
- let mut app = Applicability :: MachineApplicable ;
104
+ let mut app = Applicability :: MachineApplicable ;
98
105
99
- let scrutinee_span = match scrutinee. kind {
100
- ExprKind :: AddrOf ( _, _, expr) => expr. span ,
101
- _ => scrutinee. span ,
102
- } ;
103
- let scrutinee_str = snippet_with_applicability ( cx, scrutinee_span, "_" , & mut app) ;
106
+ // Remove address-of expressions from the scrutinee. `as_ref` will be called,
107
+ // the type is copyable, or the option is being passed by value.
108
+ let scrutinee = peel_hir_expr_refs ( scrutinee) . 0 ;
109
+ let scrutinee_str = snippet_with_applicability ( cx, scrutinee. span , "_" , & mut app) ;
110
+ let scrutinee_str = if expr. precedence ( ) . order ( ) < PREC_POSTFIX {
111
+ // Parens are needed to chain method calls.
112
+ format ! ( "({})" , scrutinee_str)
113
+ } else {
114
+ scrutinee_str. into ( )
115
+ } ;
104
116
105
- let body_str = if let PatKind :: Binding ( annotation, _, some_binding, None ) = some_pat {
106
- match some_expr. kind {
107
- ExprKind :: Call (
108
- & Expr {
109
- kind : ExprKind :: Path ( func_path) ,
110
- hir_id : func_id,
111
- span : func_span,
112
- ..
113
- } ,
114
- [ Expr {
115
- kind :
116
- ExprKind :: Path ( QPath :: Resolved (
117
- None ,
118
- & Path {
119
- segments : [ arg_name] , ..
120
- } ,
121
- ) ) ,
122
- ..
123
- } ] ,
124
- ) if arg_name. ident == some_binding
125
- && cx. qpath_res ( func_path, func_id) . opt_def_id ( ) . map_or ( false , |id| {
126
- TyS :: same_type (
127
- peel_ref_if (
128
- cx. tcx . fn_sig ( id) . skip_binder ( ) . inputs ( ) [ 0 ] ,
129
- binding_mutability. is_some ( ) ,
130
- ) ,
131
- some_ty,
132
- )
133
- } ) =>
134
- {
135
- snippet_with_applicability ( cx, func_span, ".." , & mut app) . into_owned ( )
136
- } ,
137
- _ => {
138
- let annotation = if matches ! ( annotation, BindingAnnotation :: Mutable ) {
139
- "mut "
140
- } else {
141
- ""
142
- } ;
143
- format ! (
144
- "|{}{}| {}" ,
145
- annotation,
146
- some_binding,
147
- snippet_with_applicability( cx, some_expr. span, ".." , & mut app)
148
- )
149
- } ,
150
- }
151
- } else if !is_wild_none && explicit_ref. is_none ( ) {
117
+ let body_str = if let PatKind :: Binding ( annotation, _, some_binding, None ) = some_pat. kind {
118
+ if let Some ( func) = can_pass_as_func ( cx, some_binding, some_expr) {
119
+ snippet_with_applicability ( cx, func. span , ".." , & mut app) . into_owned ( )
120
+ } else {
121
+ // `ref` and `ref mut` annotations were handled earlier.
122
+ let annotation = if matches ! ( annotation, BindingAnnotation :: Mutable ) {
123
+ "mut "
124
+ } else {
125
+ ""
126
+ } ;
152
127
format ! (
153
- "|{}| {}" ,
154
- snippet_with_applicability( cx, some_pat. span, ".." , & mut app) ,
128
+ "|{}{}| {}" ,
129
+ annotation,
130
+ some_binding,
155
131
snippet_with_applicability( cx, some_expr. span, ".." , & mut app)
156
132
)
157
- } else {
158
- // TODO: handle explicit reference annotations
159
- return ;
160
- } ;
133
+ }
134
+ } else if !is_wild_none && explicit_ref. is_none ( ) {
135
+ // TODO: handle explicit reference annotations.
136
+ format ! (
137
+ "|{}| {}" ,
138
+ snippet_with_applicability( cx, some_pat. span, ".." , & mut app) ,
139
+ snippet_with_applicability( cx, some_expr. span, ".." , & mut app)
140
+ )
141
+ } else {
142
+ // Refutable bindings and mixed reference annotations can't be handled by `map`.
143
+ return ;
144
+ } ;
161
145
162
- span_lint_and_sugg (
163
- cx,
164
- MANUAL_MAP ,
165
- expr. span ,
166
- "manual implementation of `Option::map`" ,
167
- "use `map` instead" ,
168
- format ! ( "{}{}.map({})" , scrutinee_str, as_ref_str, body_str) ,
169
- app,
170
- ) ;
171
- }
146
+ span_lint_and_sugg (
147
+ cx,
148
+ MANUAL_MAP ,
149
+ expr. span ,
150
+ "manual implementation of `Option::map`" ,
151
+ "try this" ,
152
+ format ! ( "{}{}.map({})" , scrutinee_str, as_ref_str, body_str) ,
153
+ app,
154
+ ) ;
172
155
}
173
156
}
174
157
}
175
158
176
- fn extract_some_ty ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> Option < Ty < ' tcx > > {
177
- match ty. kind ( ) {
178
- ty:: Ref ( _, ty, _) if is_type_diagnostic_item ( cx, ty, sym:: option_type) => {
179
- if let ty:: Adt ( _, subs) = ty. kind ( ) {
180
- subs. types ( ) . next ( )
181
- } else {
182
- None
183
- }
159
+ // Checks whether the expression could be passed as a function, or whether a closure is needed.
160
+ // Returns the function to be passed to `map` if it exists.
161
+ fn can_pass_as_func ( cx : & LateContext < ' tcx > , binding : Ident , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
162
+ match expr. kind {
163
+ ExprKind :: Call ( func, [ arg] )
164
+ if matches ! ( arg. kind,
165
+ ExprKind :: Path ( QPath :: Resolved ( None , Path { segments: [ path] , ..} ) )
166
+ if path. ident == binding
167
+ ) && cx. typeck_results ( ) . expr_adjustments ( arg) . is_empty ( ) =>
168
+ {
169
+ Some ( func)
184
170
} ,
185
- ty:: Adt ( _, subs) if is_type_diagnostic_item ( cx, ty, sym:: option_type) => subs. types ( ) . next ( ) ,
186
171
_ => None ,
187
172
}
188
173
}
189
174
190
- fn peel_ref_if ( ty : Ty < ' _ > , cond : bool ) -> Ty < ' _ > {
191
- match ty. kind ( ) {
192
- ty:: Ref ( _, ty, _) if cond => ty,
193
- _ => ty,
194
- }
195
- }
196
-
197
175
enum OptionPat < ' a > {
198
176
Wild ,
199
177
None ,
200
178
Some {
179
+ // The pattern contained in the `Some` tuple.
201
180
pattern : & ' a Pat < ' a > ,
202
- // &Some(_)
203
- is_ref : bool ,
181
+ // The number of references before the `Some` tuple.
182
+ // e.g. `&&Some(_)` has a ref count of 2.
183
+ ref_count : usize ,
204
184
} ,
205
185
}
206
186
187
+ // Try to parse into a recognized `Option` pattern.
188
+ // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
207
189
fn try_parse_pattern ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > ) -> Option < OptionPat < ' tcx > > {
208
- fn f ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > , is_ref : bool ) -> Option < OptionPat < ' tcx > > {
190
+ fn f ( cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > , ref_count : usize ) -> Option < OptionPat < ' tcx > > {
209
191
match pat. kind {
210
192
PatKind :: Wild => Some ( OptionPat :: Wild ) ,
211
- PatKind :: Ref ( pat, _) => f ( cx, pat, true ) ,
193
+ PatKind :: Ref ( pat, _) => f ( cx, pat, ref_count + 1 ) ,
212
194
PatKind :: Path ( QPath :: Resolved ( None , path) )
213
195
if path
214
196
. res
@@ -223,15 +205,17 @@ fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<Optio
223
205
. opt_def_id ( )
224
206
. map_or ( false , |id| match_def_path ( cx, id, & paths:: OPTION_SOME ) ) =>
225
207
{
226
- Some ( OptionPat :: Some { pattern, is_ref } )
208
+ Some ( OptionPat :: Some { pattern, ref_count } )
227
209
} ,
228
210
_ => None ,
229
211
}
230
212
}
231
- f ( cx, pat, false )
213
+ f ( cx, pat, 0 )
232
214
}
233
215
216
+ // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
234
217
fn get_some_expr ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
218
+ // TODO: Allow more complex expressions.
235
219
match expr. kind {
236
220
ExprKind :: Call (
237
221
Expr {
@@ -258,6 +242,7 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
258
242
}
259
243
}
260
244
245
+ // Checks for the `None` value.
261
246
fn is_none_expr ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> bool {
262
247
match expr. kind {
263
248
ExprKind :: Path ( QPath :: Resolved ( None , path) ) => path
0 commit comments