11use clippy_utils:: diagnostics:: span_lint_and_then;
2- use clippy_utils:: { is_res_lang_ctor, is_trait_method, match_trait_method, paths} ;
2+ use clippy_utils:: macros:: { is_panic, root_macro_call_first_node} ;
3+ use clippy_utils:: { is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks} ;
34use hir:: { ExprKind , PatKind } ;
45use rustc_hir as hir;
56use rustc_lint:: { LateContext , LateLintPass } ;
@@ -82,37 +83,72 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
8283 }
8384
8485 if let Some ( exp) = block. expr
85- && matches ! ( exp. kind, hir:: ExprKind :: If ( _, _, _) | hir:: ExprKind :: Match ( _, _, _) )
86+ && matches ! (
87+ exp. kind,
88+ hir:: ExprKind :: If ( _, _, _) | hir:: ExprKind :: Match ( _, _, hir:: MatchSource :: Normal )
89+ )
8690 {
8791 check_expr ( cx, exp) ;
8892 }
8993 }
9094}
9195
96+ fn non_consuming_err_arm < ' a > ( cx : & LateContext < ' a > , arm : & hir:: Arm < ' a > ) -> bool {
97+ // if there is a guard, we consider the result to be consumed
98+ if arm. guard . is_some ( ) {
99+ return false ;
100+ }
101+ if is_unreachable_or_panic ( cx, arm. body ) {
102+ // if the body is unreachable or there is a panic,
103+ // we consider the result to be consumed
104+ return false ;
105+ }
106+
107+ if let PatKind :: TupleStruct ( ref path, [ inner_pat] , _) = arm. pat . kind {
108+ return is_res_lang_ctor ( cx, cx. qpath_res ( path, inner_pat. hir_id ) , hir:: LangItem :: ResultErr ) ;
109+ }
110+
111+ false
112+ }
113+
114+ fn non_consuming_ok_arm < ' a > ( cx : & LateContext < ' a > , arm : & hir:: Arm < ' a > ) -> bool {
115+ // if there is a guard, we consider the result to be consumed
116+ if arm. guard . is_some ( ) {
117+ return false ;
118+ }
119+ if is_unreachable_or_panic ( cx, arm. body ) {
120+ // if the body is unreachable or there is a panic,
121+ // we consider the result to be consumed
122+ return false ;
123+ }
124+
125+ if is_ok_wild_or_dotdot_pattern ( cx, arm. pat ) {
126+ return true ;
127+ }
128+ false
129+ }
130+
92131fn check_expr < ' a > ( cx : & LateContext < ' a > , expr : & ' a hir:: Expr < ' a > ) {
93132 match expr. kind {
94133 hir:: ExprKind :: If ( cond, _, _)
95134 if let ExprKind :: Let ( hir:: Let { pat, init, .. } ) = cond. kind
96- && pattern_is_ignored_ok ( cx, pat)
135+ && is_ok_wild_or_dotdot_pattern ( cx, pat)
97136 && let Some ( op) = should_lint ( cx, init) =>
98137 {
99138 emit_lint ( cx, cond. span , op, & [ pat. span ] ) ;
100139 } ,
101- hir:: ExprKind :: Match ( expr, arms, hir:: MatchSource :: Normal ) if let Some ( op) = should_lint ( cx, expr) => {
102- let found_arms: Vec < _ > = arms
103- . iter ( )
104- . filter_map ( |arm| {
105- if pattern_is_ignored_ok ( cx, arm. pat ) {
106- Some ( arm. span )
107- } else {
108- None
109- }
110- } )
111- . collect ( ) ;
112- if !found_arms. is_empty ( ) {
113- emit_lint ( cx, expr. span , op, found_arms. as_slice ( ) ) ;
140+ // we will capture only the case where the match is Ok( ) or Err( )
141+ // prefer to match the minimum possible, and expand later if needed
142+ // to avoid false positives on something as used as this
143+ hir:: ExprKind :: Match ( expr, [ arm1, arm2] , hir:: MatchSource :: Normal ) if let Some ( op) = should_lint ( cx, expr) => {
144+ if non_consuming_ok_arm ( cx, arm1) && non_consuming_err_arm ( cx, arm2) {
145+ emit_lint ( cx, expr. span , op, & [ arm1. pat . span ] ) ;
146+ }
147+ if non_consuming_ok_arm ( cx, arm2) && non_consuming_err_arm ( cx, arm1) {
148+ emit_lint ( cx, expr. span , op, & [ arm2. pat . span ] ) ;
114149 }
115150 } ,
151+ hir:: ExprKind :: Match ( _, _, hir:: MatchSource :: Normal ) => { } ,
116152 _ if let Some ( op) = should_lint ( cx, expr) => {
117153 emit_lint ( cx, expr. span , op, & [ ] ) ;
118154 } ,
@@ -130,25 +166,40 @@ fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option
130166 check_io_mode ( cx, inner)
131167}
132168
133- fn pattern_is_ignored_ok ( cx : & LateContext < ' _ > , pat : & hir:: Pat < ' _ > ) -> bool {
169+ fn is_ok_wild_or_dotdot_pattern < ' a > ( cx : & LateContext < ' a > , pat : & hir:: Pat < ' a > ) -> bool {
134170 // the if checks whether we are in a result Ok( ) pattern
135171 // and the return checks whether it is unhandled
136172
137- if let PatKind :: TupleStruct ( ref path, inner_pat, ddp ) = pat. kind
173+ if let PatKind :: TupleStruct ( ref path, inner_pat, _ ) = pat. kind
138174 // we check against Result::Ok to avoid linting on Err(_) or something else.
139175 && is_res_lang_ctor ( cx, cx. qpath_res ( path, pat. hir_id ) , hir:: LangItem :: ResultOk )
140176 {
141- return match ( inner_pat, ddp. as_opt_usize ( ) ) {
142- // Ok(_) pattern
143- ( [ inner_pat] , None ) if matches ! ( inner_pat. kind, PatKind :: Wild ) => true ,
144- // Ok(..) pattern
145- ( [ ] , Some ( 0 ) ) => true ,
146- _ => false ,
147- } ;
177+ if matches ! ( inner_pat, [ ] ) {
178+ return true ;
179+ }
180+
181+ if let [ cons_pat] = inner_pat
182+ && matches ! ( cons_pat. kind, PatKind :: Wild )
183+ {
184+ return true ;
185+ }
186+ return false ;
148187 }
149188 false
150189}
151190
191+ // this is partially taken from panic_unimplemented
192+ fn is_unreachable_or_panic ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
193+ let expr = peel_blocks ( expr) ;
194+ let Some ( macro_call) = root_macro_call_first_node ( cx, expr) else {
195+ return false ;
196+ } ;
197+ if is_panic ( cx, macro_call. def_id ) {
198+ return !cx. tcx . hir ( ) . is_inside_const_context ( expr. hir_id ) ;
199+ }
200+ matches ! ( cx. tcx. item_name( macro_call. def_id) . as_str( ) , "unreachable" )
201+ }
202+
152203fn unpack_call_chain < ' a > ( mut expr : & ' a hir:: Expr < ' a > ) -> & ' a hir:: Expr < ' a > {
153204 while let hir:: ExprKind :: MethodCall ( path, receiver, ..) = expr. kind {
154205 if matches ! (
0 commit comments