@@ -4,11 +4,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
44use clippy_utils:: higher:: IfLetOrMatch ;
55use clippy_utils:: source:: snippet_with_context;
66use clippy_utils:: ty:: is_type_diagnostic_item;
7- use clippy_utils:: { is_lint_allowed, is_never_expr, msrvs, pat_and_expr_can_be_question_mark, peel_blocks} ;
7+ use clippy_utils:: {
8+ MaybePath , is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, path_res, peel_blocks,
9+ } ;
810use rustc_ast:: BindingMode ;
911use rustc_data_structures:: fx:: FxHashMap ;
1012use rustc_errors:: Applicability ;
11- use rustc_hir:: { Expr , ExprKind , MatchSource , Pat , PatExpr , PatExprKind , PatKind , QPath , Stmt , StmtKind } ;
13+ use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
14+ use rustc_hir:: { Arm , Expr , ExprKind , HirId , MatchSource , Pat , PatExpr , PatExprKind , PatKind , QPath , Stmt , StmtKind } ;
1215use rustc_lint:: { LateContext , LintContext } ;
1316
1417use rustc_span:: Span ;
@@ -91,14 +94,15 @@ impl<'tcx> QuestionMark {
9194 let Some ( ( idx, diverging_arm) ) = diverging_arm_opt else {
9295 return ;
9396 } ;
97+
98+ let pat_arm = & arms[ 1 - idx] ;
9499 // If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
95100 // However, if it arrives in second position, its pattern may cover some cases already covered
96101 // by the diverging one.
97- // TODO: accept the non-diverging arm as a second position if patterns are disjointed.
98- if idx == 0 {
102+ if idx == 0 && !is_arms_disjointed ( cx, diverging_arm, pat_arm) {
99103 return ;
100104 }
101- let pat_arm = & arms [ 1 - idx ] ;
105+
102106 let Some ( ident_map) = expr_simple_identity_map ( local. pat , pat_arm. pat , pat_arm. body ) else {
103107 return ;
104108 } ;
@@ -110,6 +114,63 @@ impl<'tcx> QuestionMark {
110114 }
111115}
112116
117+ /// Checks if the patterns of the arms are disjointed. Currently, we only support patterns of simple
118+ /// enum variants without nested patterns or bindings.
119+ ///
120+ /// TODO: Support more complex patterns.
121+ fn is_arms_disjointed ( cx : & LateContext < ' _ > , arm1 : & Arm < ' _ > , arm2 : & Arm < ' _ > ) -> bool {
122+ if arm1. guard . is_some ( ) || arm2. guard . is_some ( ) {
123+ return false ;
124+ }
125+
126+ if !is_enum_variant ( cx, arm1. pat ) || !is_enum_variant ( cx, arm2. pat ) {
127+ return false ;
128+ }
129+
130+ true
131+ }
132+
133+ /// Returns `true` if the given pattern is a variant of an enum.
134+ pub fn is_enum_variant ( cx : & LateContext < ' _ > , pat : & Pat < ' _ > ) -> bool {
135+ struct Pat < ' hir > ( & ' hir rustc_hir:: Pat < ' hir > ) ;
136+
137+ impl < ' hir > MaybePath < ' hir > for Pat < ' hir > {
138+ fn qpath_opt ( & self ) -> Option < & QPath < ' hir > > {
139+ match self . 0 . kind {
140+ PatKind :: Struct ( ref qpath, fields, _)
141+ if fields
142+ . iter ( )
143+ . all ( |field| is_wild ( field. pat ) || matches ! ( field. pat. kind, PatKind :: Binding ( ..) ) ) =>
144+ {
145+ Some ( qpath)
146+ } ,
147+ PatKind :: TupleStruct ( ref qpath, pats, _)
148+ if pats
149+ . iter ( )
150+ . all ( |pat| is_wild ( pat) || matches ! ( pat. kind, PatKind :: Binding ( ..) ) ) =>
151+ {
152+ Some ( qpath)
153+ } ,
154+ PatKind :: Expr ( & PatExpr {
155+ kind : PatExprKind :: Path ( ref qpath) ,
156+ ..
157+ } ) => Some ( qpath) ,
158+ _ => None ,
159+ }
160+ }
161+
162+ fn hir_id ( & self ) -> HirId {
163+ self . 0 . hir_id
164+ }
165+ }
166+
167+ let res = path_res ( cx, & Pat ( pat) ) ;
168+ matches ! (
169+ res,
170+ Res :: Def ( DefKind :: Variant , ..) | Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , _) , _)
171+ )
172+ }
173+
113174fn emit_manual_let_else (
114175 cx : & LateContext < ' _ > ,
115176 span : Span ,
0 commit comments