11use clippy_utils:: diagnostics:: span_lint_and_sugg;
22use 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 ;
67use 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 } ;
811use rustc_lint:: LateContext ;
9- use rustc_middle:: ty:: { self , Ty } ;
12+ use rustc_middle:: ty:: { self , AdtDef , ParamEnv , TyCtxt , TypeckResults , VariantDef } ;
1013use rustc_span:: { sym, Span } ;
1114
1215use super :: { MATCH_BOOL , SINGLE_MATCH , SINGLE_MATCH_ELSE } ;
@@ -27,7 +30,7 @@ fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
2730}
2831
2932#[ 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 < ' _ > ) {
3134 if let [ arm1, arm2] = arms
3235 && arm1. guard . is_none ( )
3336 && arm2. guard . is_none ( )
@@ -50,10 +53,29 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
5053 return ;
5154 } ;
5255
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+
5779 report_single_pattern ( cx, ex, arm1, expr, els) ;
5880 }
5981 }
@@ -119,100 +141,225 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
119141 span_lint_and_sugg ( cx, lint, expr. span , msg, "try" , sugg, app) ;
120142}
121143
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+ }
128158}
129159
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+ }
134167
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 ;
138211 }
212+ is_wild
139213 }
140- false
141- }
142214
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) ) )
154240 }
155- }
156241
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+ }
163251 }
164- }
165252
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
203282 }
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
206290 {
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+ }
217364 }
218365}
0 commit comments