1
1
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
2
use clippy_utils:: source:: { expr_block, snippet, SpanRangeExt } ;
3
- use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item, peel_mid_ty_refs} ;
4
- use clippy_utils:: { is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs} ;
5
- use core:: cmp:: max;
3
+ use clippy_utils:: ty:: { implements_trait, peel_mid_ty_refs} ;
4
+ use clippy_utils:: { is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs} ;
5
+ use core:: ops:: ControlFlow ;
6
+ use rustc_arena:: DroplessArena ;
6
7
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { Arm , BindingMode , Expr , ExprKind , Pat , PatKind } ;
8
+ use rustc_hir:: def:: { DefKind , Res } ;
9
+ use rustc_hir:: intravisit:: { walk_pat, Visitor } ;
10
+ use rustc_hir:: { Arm , Expr , ExprKind , HirId , Pat , PatKind , QPath } ;
8
11
use rustc_lint:: LateContext ;
9
- use rustc_middle:: ty:: { self , Ty } ;
12
+ use rustc_middle:: ty:: { self , AdtDef , ParamEnv , TyCtxt , TypeckResults , VariantDef } ;
10
13
use rustc_span:: { sym, Span } ;
11
14
12
15
use super :: { MATCH_BOOL , SINGLE_MATCH , SINGLE_MATCH_ELSE } ;
@@ -27,7 +30,7 @@ fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
27
30
}
28
31
29
32
#[ rustfmt:: skip]
30
- pub ( crate ) fn check ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > , arms : & [ Arm < ' _ > ] , expr : & Expr < ' _ > ) {
33
+ pub ( crate ) fn check < ' tcx > ( cx : & LateContext < ' tcx > , ex : & ' tcx Expr < ' _ > , arms : & ' tcx [ Arm < ' _ > ] , expr : & ' tcx Expr < ' _ > ) {
31
34
if let [ arm1, arm2] = arms
32
35
&& arm1. guard . is_none ( )
33
36
&& arm2. guard . is_none ( )
@@ -50,10 +53,29 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
50
53
return ;
51
54
} ;
52
55
53
- let ty = cx. typeck_results ( ) . expr_ty ( ex) ;
54
- if ( * ty. kind ( ) != ty:: Bool || is_lint_allowed ( cx, MATCH_BOOL , ex. hir_id ) )
55
- && ( is_wild ( arm2. pat ) || form_exhaustive_matches ( cx, ty, arm1. pat , arm2. pat ) )
56
- {
56
+ let typeck = cx. typeck_results ( ) ;
57
+ if * typeck. expr_ty ( ex) . peel_refs ( ) . kind ( ) != ty:: Bool || is_lint_allowed ( cx, MATCH_BOOL , ex. hir_id ) {
58
+ let mut v = PatVisitor {
59
+ typeck,
60
+ has_enum : false ,
61
+ } ;
62
+ if v. visit_pat ( arm2. pat ) . is_break ( ) {
63
+ return ;
64
+ }
65
+ if v. has_enum {
66
+ let cx = PatCtxt {
67
+ tcx : cx. tcx ,
68
+ param_env : cx. param_env ,
69
+ typeck,
70
+ arena : DroplessArena :: default ( ) ,
71
+ } ;
72
+ let mut state = PatState :: Other ;
73
+ if !( state. add_pat ( & cx, arm2. pat ) || state. add_pat ( & cx, arm1. pat ) ) {
74
+ // Don't lint if the pattern contains an enum which doesn't have a wild match.
75
+ return ;
76
+ }
77
+ }
78
+
57
79
report_single_pattern ( cx, ex, arm1, expr, els) ;
58
80
}
59
81
}
@@ -119,100 +141,225 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
119
141
span_lint_and_sugg ( cx, lint, expr. span , msg, "try" , sugg, app) ;
120
142
}
121
143
122
- /// Returns `true` if all of the types in the pattern are enums which we know
123
- /// won't be expanded in the future
124
- fn pat_in_candidate_enum < ' a > ( cx : & LateContext < ' a > , ty : Ty < ' a > , pat : & Pat < ' _ > ) -> bool {
125
- let mut paths_and_types = Vec :: new ( ) ;
126
- collect_pat_paths ( & mut paths_and_types, cx, pat, ty) ;
127
- paths_and_types. iter ( ) . all ( |ty| in_candidate_enum ( cx, * ty) )
144
+ struct PatVisitor < ' tcx > {
145
+ typeck : & ' tcx TypeckResults < ' tcx > ,
146
+ has_enum : bool ,
147
+ }
148
+ impl < ' tcx > Visitor < ' tcx > for PatVisitor < ' tcx > {
149
+ type Result = ControlFlow < ( ) > ;
150
+ fn visit_pat ( & mut self , pat : & ' tcx Pat < ' _ > ) -> Self :: Result {
151
+ if matches ! ( pat. kind, PatKind :: Binding ( ..) ) {
152
+ ControlFlow :: Break ( ( ) )
153
+ } else {
154
+ self . has_enum |= self . typeck . pat_ty ( pat) . ty_adt_def ( ) . map_or ( false , AdtDef :: is_enum) ;
155
+ walk_pat ( self , pat)
156
+ }
157
+ }
128
158
}
129
159
130
- /// Returns `true` if the given type is an enum we know won't be expanded in the future
131
- fn in_candidate_enum ( cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
132
- // list of candidate `Enum`s we know will never get any more members
133
- let candidates = [ sym:: Cow , sym:: Option , sym:: Result ] ;
160
+ /// The context needed to manipulate a `PatState`.
161
+ struct PatCtxt < ' tcx > {
162
+ tcx : TyCtxt < ' tcx > ,
163
+ param_env : ParamEnv < ' tcx > ,
164
+ typeck : & ' tcx TypeckResults < ' tcx > ,
165
+ arena : DroplessArena ,
166
+ }
134
167
135
- for candidate_ty in candidates {
136
- if is_type_diagnostic_item ( cx, ty, candidate_ty) {
137
- return true ;
168
+ /// State for tracking whether a match can become non-exhaustive by adding a variant to a contained
169
+ /// enum.
170
+ ///
171
+ /// This treats certain std enums as if they will never be extended.
172
+ enum PatState < ' a > {
173
+ /// Either a wild match or an uninteresting type. Uninteresting types include:
174
+ /// * builtin types (e.g. `i32` or `!`)
175
+ /// * A struct/tuple/array containing only uninteresting types.
176
+ /// * A std enum containing only uninteresting types.
177
+ Wild ,
178
+ /// A std enum we know won't be extended. Tracks the states of each variant separately.
179
+ ///
180
+ /// This is not used for `Option` since it uses the current pattern to track it's state.
181
+ StdEnum ( & ' a mut [ PatState < ' a > ] ) ,
182
+ /// Either the initial state for a pattern or a non-std enum. There is currently no need to
183
+ /// distinguish these cases.
184
+ ///
185
+ /// For non-std enums there's no need to track the state of sub-patterns as the state of just
186
+ /// this pattern on it's own is enough for linting. Consider two cases:
187
+ /// * This enum has no wild match. This case alone is enough to determine we can lint.
188
+ /// * This enum has a wild match and therefore all sub-patterns also have a wild match.
189
+ /// In both cases the sub patterns are not needed to determine whether to lint.
190
+ Other ,
191
+ }
192
+ impl < ' a > PatState < ' a > {
193
+ /// Adds a set of patterns as a product type to the current state. Returns whether or not the
194
+ /// current state is a wild match after the merge.
195
+ fn add_product_pat < ' tcx > (
196
+ & mut self ,
197
+ cx : & ' a PatCtxt < ' tcx > ,
198
+ pats : impl IntoIterator < Item = & ' tcx Pat < ' tcx > > ,
199
+ ) -> bool {
200
+ // Ideally this would actually keep track of the state separately for each pattern. Doing so would
201
+ // require implementing something similar to exhaustiveness checking which is a significant increase
202
+ // in complexity.
203
+ //
204
+ // For now treat this as a wild match only if all the sub-patterns are wild
205
+ let is_wild = pats. into_iter ( ) . all ( |p| {
206
+ let mut state = Self :: Other ;
207
+ state. add_pat ( cx, p)
208
+ } ) ;
209
+ if is_wild {
210
+ * self = Self :: Wild ;
138
211
}
212
+ is_wild
139
213
}
140
- false
141
- }
142
214
143
- /// Collects types from the given pattern
144
- fn collect_pat_paths < ' a > ( acc : & mut Vec < Ty < ' a > > , cx : & LateContext < ' a > , pat : & Pat < ' _ > , ty : Ty < ' a > ) {
145
- match pat. kind {
146
- PatKind :: Tuple ( inner, _) => inner. iter ( ) . for_each ( |p| {
147
- let p_ty = cx. typeck_results ( ) . pat_ty ( p) ;
148
- collect_pat_paths ( acc, cx, p, p_ty) ;
149
- } ) ,
150
- PatKind :: TupleStruct ( ..) | PatKind :: Binding ( BindingMode :: NONE , .., None ) | PatKind :: Path ( _) => {
151
- acc. push ( ty) ;
152
- } ,
153
- _ => { } ,
215
+ /// Attempts to get the state for the enum variant, initializing the current state if necessary.
216
+ fn get_enum_variant < ' tcx > (
217
+ & mut self ,
218
+ cx : & ' a PatCtxt < ' tcx > ,
219
+ adt : AdtDef < ' tcx > ,
220
+ path : & ' tcx QPath < ' _ > ,
221
+ hir_id : HirId ,
222
+ ) -> Option < ( & mut Self , & ' tcx VariantDef ) > {
223
+ let states = match self {
224
+ Self :: Wild => return None ,
225
+ Self :: Other => {
226
+ * self = Self :: StdEnum ( cx. arena . alloc_from_iter ( ( 0 ..adt. variants ( ) . len ( ) ) . map ( |_| Self :: Other ) ) ) ;
227
+ let Self :: StdEnum ( x) = self else {
228
+ unreachable ! ( ) ;
229
+ } ;
230
+ x
231
+ } ,
232
+ Self :: StdEnum ( x) => x,
233
+ } ;
234
+ let i = match cx. typeck . qpath_res ( path, hir_id) {
235
+ Res :: Def ( DefKind :: Ctor ( ..) , id) => adt. variant_index_with_ctor_id ( id) ,
236
+ Res :: Def ( DefKind :: Variant , id) => adt. variant_index_with_id ( id) ,
237
+ _ => return None ,
238
+ } ;
239
+ Some ( ( & mut states[ i. as_usize ( ) ] , adt. variant ( i) ) )
154
240
}
155
- }
156
241
157
- /// Returns true if the given arm of pattern matching contains wildcard patterns.
158
- fn contains_only_wilds ( pat : & Pat < ' _ > ) -> bool {
159
- match pat. kind {
160
- PatKind :: Wild => true ,
161
- PatKind :: Tuple ( inner, _) | PatKind :: TupleStruct ( _, inner, ..) => inner. iter ( ) . all ( contains_only_wilds) ,
162
- _ => false ,
242
+ fn check_all_wild_enum ( & mut self ) -> bool {
243
+ if let Self :: StdEnum ( states) = self
244
+ && states. iter ( ) . all ( |s| matches ! ( s, Self :: Wild ) )
245
+ {
246
+ * self = Self :: Wild ;
247
+ true
248
+ } else {
249
+ false
250
+ }
163
251
}
164
- }
165
252
166
- /// Returns true if the given patterns forms only exhaustive matches that don't contain enum
167
- /// patterns without a wildcard.
168
- fn form_exhaustive_matches < ' a > ( cx : & LateContext < ' a > , ty : Ty < ' a > , left : & Pat < ' _ > , right : & Pat < ' _ > ) -> bool {
169
- match ( & left. kind , & right. kind ) {
170
- ( PatKind :: Wild , _) | ( _, PatKind :: Wild ) => true ,
171
- ( PatKind :: Tuple ( left_in, left_pos) , PatKind :: Tuple ( right_in, right_pos) ) => {
172
- // We don't actually know the position and the presence of the `..` (dotdot) operator
173
- // in the arms, so we need to evaluate the correct offsets here in order to iterate in
174
- // both arms at the same time.
175
- let left_pos = left_pos. as_opt_usize ( ) ;
176
- let right_pos = right_pos. as_opt_usize ( ) ;
177
- let len = max (
178
- left_in. len ( ) + usize:: from ( left_pos. is_some ( ) ) ,
179
- right_in. len ( ) + usize:: from ( right_pos. is_some ( ) ) ,
180
- ) ;
181
- let mut left_pos = left_pos. unwrap_or ( usize:: MAX ) ;
182
- let mut right_pos = right_pos. unwrap_or ( usize:: MAX ) ;
183
- let mut left_dot_space = 0 ;
184
- let mut right_dot_space = 0 ;
185
- for i in 0 ..len {
186
- let mut found_dotdot = false ;
187
- if i == left_pos {
188
- left_dot_space += 1 ;
189
- if left_dot_space < len - left_in. len ( ) {
190
- left_pos += 1 ;
191
- }
192
- found_dotdot = true ;
193
- }
194
- if i == right_pos {
195
- right_dot_space += 1 ;
196
- if right_dot_space < len - right_in. len ( ) {
197
- right_pos += 1 ;
198
- }
199
- found_dotdot = true ;
200
- }
201
- if found_dotdot {
202
- continue ;
253
+ fn add_struct_pats < ' tcx > (
254
+ & mut self ,
255
+ cx : & ' a PatCtxt < ' tcx > ,
256
+ pat : & ' tcx Pat < ' tcx > ,
257
+ path : & ' tcx QPath < ' tcx > ,
258
+ single_pat : Option < & ' tcx Pat < ' tcx > > ,
259
+ pats : impl IntoIterator < Item = & ' tcx Pat < ' tcx > > ,
260
+ ) -> bool {
261
+ let ty:: Adt ( adt, _) = * cx. typeck . pat_ty ( pat) . kind ( ) else {
262
+ // Should never happen
263
+ * self = Self :: Wild ;
264
+ return true ;
265
+ } ;
266
+ if adt. is_struct ( ) {
267
+ return if let Some ( pat) = single_pat
268
+ && adt. non_enum_variant ( ) . fields . len ( ) == 1
269
+ {
270
+ self . add_pat ( cx, pat)
271
+ } else {
272
+ self . add_product_pat ( cx, pats)
273
+ } ;
274
+ }
275
+ match cx. tcx . get_diagnostic_name ( adt. did ( ) ) {
276
+ Some ( sym:: Option ) => {
277
+ if let Some ( pat) = single_pat {
278
+ self . add_pat ( cx, pat)
279
+ } else {
280
+ * self = Self :: Wild ;
281
+ true
203
282
}
204
- if !contains_only_wilds ( & left_in[ i - left_dot_space] )
205
- && !contains_only_wilds ( & right_in[ i - right_dot_space] )
283
+ } ,
284
+ Some ( sym:: Result | sym:: Cow ) => {
285
+ let Some ( ( state, variant) ) = self . get_enum_variant ( cx, adt, path, pat. hir_id ) else {
286
+ return matches ! ( self , Self :: Wild ) ;
287
+ } ;
288
+ let is_wild = if let Some ( pat) = single_pat
289
+ && variant. fields . len ( ) == 1
206
290
{
207
- return false ;
208
- }
209
- }
210
- true
211
- } ,
212
- ( PatKind :: TupleStruct ( ..) , PatKind :: Path ( _) ) => pat_in_candidate_enum ( cx, ty, right) ,
213
- ( PatKind :: TupleStruct ( ..) , PatKind :: TupleStruct ( _, inner, _) ) => {
214
- pat_in_candidate_enum ( cx, ty, right) && inner. iter ( ) . all ( contains_only_wilds)
215
- } ,
216
- _ => false ,
291
+ state. add_pat ( cx, pat)
292
+ } else {
293
+ state. add_product_pat ( cx, pats)
294
+ } ;
295
+ is_wild && self . check_all_wild_enum ( )
296
+ } ,
297
+ _ => matches ! ( self , Self :: Wild ) ,
298
+ }
299
+ }
300
+
301
+ /// Adds the pattern into the current state. Returns whether or not the current state is a wild
302
+ /// match after the merge.
303
+ #[ expect( clippy:: similar_names, clippy:: too_many_lines) ]
304
+ fn add_pat < ' tcx > ( & mut self , cx : & ' a PatCtxt < ' tcx > , pat : & ' tcx Pat < ' _ > ) -> bool {
305
+ match pat. kind {
306
+ PatKind :: Path ( _)
307
+ if match * cx. typeck . pat_ty ( pat) . peel_refs ( ) . kind ( ) {
308
+ ty:: Adt ( adt, _) => adt. is_enum ( ) || ( adt. is_struct ( ) && !adt. non_enum_variant ( ) . fields . is_empty ( ) ) ,
309
+ ty:: Tuple ( tys) => !tys. is_empty ( ) ,
310
+ ty:: Array ( _, len) => len. try_eval_target_usize ( cx. tcx , cx. param_env ) != Some ( 1 ) ,
311
+ ty:: Slice ( ..) => true ,
312
+ _ => false ,
313
+ } =>
314
+ {
315
+ matches ! ( self , Self :: Wild )
316
+ } ,
317
+
318
+ // Patterns for things which can only contain a single sub-pattern.
319
+ PatKind :: Binding ( _, _, _, Some ( pat) ) | PatKind :: Ref ( pat, _) | PatKind :: Box ( pat) | PatKind :: Deref ( pat) => {
320
+ self . add_pat ( cx, pat)
321
+ } ,
322
+ PatKind :: Tuple ( [ sub_pat] , pos)
323
+ if pos. as_opt_usize ( ) . is_none ( ) || cx. typeck . pat_ty ( pat) . tuple_fields ( ) . len ( ) == 1 =>
324
+ {
325
+ self . add_pat ( cx, sub_pat)
326
+ } ,
327
+ PatKind :: Slice ( [ sub_pat] , _, [ ] ) | PatKind :: Slice ( [ ] , _, [ sub_pat] )
328
+ if let ty:: Array ( _, len) = * cx. typeck . pat_ty ( pat) . kind ( )
329
+ && len. try_eval_target_usize ( cx. tcx , cx. param_env ) == Some ( 1 ) =>
330
+ {
331
+ self . add_pat ( cx, sub_pat)
332
+ } ,
333
+
334
+ PatKind :: Or ( pats) => pats. iter ( ) . any ( |p| self . add_pat ( cx, p) ) ,
335
+ PatKind :: Tuple ( pats, _) => self . add_product_pat ( cx, pats) ,
336
+ PatKind :: Slice ( head, _, tail) => self . add_product_pat ( cx, head. iter ( ) . chain ( tail) ) ,
337
+
338
+ PatKind :: TupleStruct ( ref path, pats, _) => self . add_struct_pats (
339
+ cx,
340
+ pat,
341
+ path,
342
+ if let [ pat] = pats { Some ( pat) } else { None } ,
343
+ pats. iter ( ) ,
344
+ ) ,
345
+ PatKind :: Struct ( ref path, pats, _) => self . add_struct_pats (
346
+ cx,
347
+ pat,
348
+ path,
349
+ if let [ pat] = pats { Some ( pat. pat ) } else { None } ,
350
+ pats. iter ( ) . map ( |p| p. pat ) ,
351
+ ) ,
352
+
353
+ PatKind :: Wild
354
+ | PatKind :: Binding ( _, _, _, None )
355
+ | PatKind :: Lit ( _)
356
+ | PatKind :: Range ( ..)
357
+ | PatKind :: Path ( _)
358
+ | PatKind :: Never
359
+ | PatKind :: Err ( _) => {
360
+ * self = PatState :: Wild ;
361
+ true
362
+ } ,
363
+ }
217
364
}
218
365
}
0 commit comments