1- use clippy_utils:: sugg:: Sugg ;
21use rustc_errors:: Applicability ;
32use rustc_hir:: def:: Res ;
43use rustc_hir:: { Arm , Expr , ExprKind , HirId , LangItem , MatchSource , Pat , PatKind , QPath } ;
54use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
5+ use rustc_middle:: ty:: GenericArgKind ;
66use rustc_session:: declare_lint_pass;
77use rustc_span:: sym;
88
99use clippy_utils:: diagnostics:: span_lint_and_sugg;
10- use clippy_utils:: source:: snippet_opt;
10+ use clippy_utils:: higher:: IfLetOrMatch ;
11+ use clippy_utils:: sugg:: Sugg ;
1112use clippy_utils:: ty:: implements_trait;
1213use clippy_utils:: { in_constant, is_default_equivalent, peel_blocks, span_contains_comment} ;
1314
@@ -105,28 +106,49 @@ fn get_some_and_none_bodies<'tcx>(
105106 }
106107}
107108
108- fn handle_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> bool {
109- let ExprKind :: Match ( match_expr, [ arm1, arm2] , MatchSource :: Normal | MatchSource :: ForLoopDesugar ) = expr. kind else {
110- return false ;
109+ #[ allow( clippy:: needless_pass_by_value) ]
110+ fn handle < ' tcx > ( cx : & LateContext < ' tcx > , if_let_or_match : IfLetOrMatch < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
111+ // Get expr_name ("if let" or "match" depending on kind of expression), the condition, the body for
112+ // the some arm, the body for the none arm and the binding id of the some arm
113+ let ( expr_name, condition, body_some, body_none, binding_id) = match if_let_or_match {
114+ IfLetOrMatch :: Match ( condition, [ arm1, arm2] , MatchSource :: Normal | MatchSource :: ForLoopDesugar )
115+ // Make sure there are no guards to keep things simple
116+ if arm1. guard . is_none ( )
117+ && arm2. guard . is_none ( )
118+ // Get the some and none bodies and the binding id of the some arm
119+ && let Some ( ( ( body_some, binding_id) , body_none) ) = get_some_and_none_bodies ( cx, arm1, arm2) =>
120+ {
121+ ( "match" , condition, body_some, body_none, binding_id)
122+ } ,
123+ IfLetOrMatch :: IfLet ( condition, pat, if_expr, Some ( else_expr) , _)
124+ if let Some ( binding_id) = get_some ( cx, pat) =>
125+ {
126+ ( "if let" , condition, if_expr, else_expr, binding_id)
127+ } ,
128+ _ => {
129+ // All other cases (match with number of arms != 2, if let without else, etc.)
130+ return ;
131+ } ,
111132 } ;
112- // We don't want conditions on the arms to simplify things.
113- if arm1 . guard . is_none ( )
114- && arm2 . guard . is_none ( )
115- // We check that the returned type implements the `Default` trait.
116- && let match_ty = cx . typeck_results ( ) . expr_ty ( expr )
117- && let Some ( default_trait_id ) = cx . tcx . get_diagnostic_item ( sym :: Default )
118- && implements_trait ( cx , match_ty , default_trait_id , & [ ] )
119- // We now get the bodies for both the `Some` and `None` arms.
120- && let Some ( ( ( body_some , binding_id ) , body_none ) ) = get_some_and_none_bodies ( cx , arm1 , arm2 )
133+
134+ // We check if the return type of the expression implements Default.
135+ let expr_type = cx . typeck_results ( ) . expr_ty ( expr ) ;
136+ if let Some ( default_trait_id ) = cx . tcx . get_diagnostic_item ( sym :: Default )
137+ && implements_trait ( cx , expr_type , default_trait_id , & [ ] )
138+ // We check if the initial condition implements Default.
139+ && let Some ( condition_ty ) = cx . typeck_results ( ) . expr_ty ( condition ) . walk ( ) . nth ( 1 )
140+ && let GenericArgKind :: Type ( condition_ty ) = condition_ty . unpack ( )
141+ && implements_trait ( cx , condition_ty , default_trait_id , & [ ] )
121142 // We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
122143 && let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = peel_blocks ( body_some) . kind
123144 && let Res :: Local ( local_id) = path. res
124145 && local_id == binding_id
125146 // We now check the `None` arm is calling a method equivalent to `Default::default`.
126147 && let body_none = peel_blocks ( body_none)
127148 && is_default_equivalent ( cx, body_none)
128- && let Some ( receiver) = Sugg :: hir_opt ( cx, match_expr ) . map ( Sugg :: maybe_par)
149+ && let Some ( receiver) = Sugg :: hir_opt ( cx, condition ) . map ( Sugg :: maybe_par)
129150 {
151+ // Machine applicable only if there are no comments present
130152 let applicability = if span_contains_comment ( cx. sess ( ) . source_map ( ) , expr. span ) {
131153 Applicability :: MaybeIncorrect
132154 } else {
@@ -136,57 +158,22 @@ fn handle_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
136158 cx,
137159 MANUAL_UNWRAP_OR_DEFAULT ,
138160 expr. span ,
139- "match can be simplified with `.unwrap_or_default()`",
161+ format ! ( "{expr_name} can be simplified with `.unwrap_or_default()`") ,
140162 "replace it with" ,
141163 format ! ( "{receiver}.unwrap_or_default()" ) ,
142164 applicability,
143165 ) ;
144166 }
145- true
146- }
147-
148- fn handle_if_let < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
149- if let ExprKind :: If ( cond, if_block, Some ( else_expr) ) = expr. kind
150- && let ExprKind :: Let ( let_) = cond. kind
151- && let ExprKind :: Block ( _, _) = else_expr. kind
152- // We check that the returned type implements the `Default` trait.
153- && let match_ty = cx. typeck_results ( ) . expr_ty ( expr)
154- && let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
155- && implements_trait ( cx, match_ty, default_trait_id, & [ ] )
156- && let Some ( binding_id) = get_some ( cx, let_. pat )
157- // We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
158- && let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = peel_blocks ( if_block) . kind
159- && let Res :: Local ( local_id) = path. res
160- && local_id == binding_id
161- // We now check the `None` arm is calling a method equivalent to `Default::default`.
162- && let body_else = peel_blocks ( else_expr)
163- && is_default_equivalent ( cx, body_else)
164- && let Some ( if_let_expr_snippet) = snippet_opt ( cx, let_. init . span )
165- {
166- let applicability = if span_contains_comment ( cx. sess ( ) . source_map ( ) , expr. span ) {
167- Applicability :: MaybeIncorrect
168- } else {
169- Applicability :: MachineApplicable
170- } ;
171- span_lint_and_sugg (
172- cx,
173- MANUAL_UNWRAP_OR_DEFAULT ,
174- expr. span ,
175- "if let can be simplified with `.unwrap_or_default()`" ,
176- "replace it with" ,
177- format ! ( "{if_let_expr_snippet}.unwrap_or_default()" ) ,
178- applicability,
179- ) ;
180- }
181167}
182168
183169impl < ' tcx > LateLintPass < ' tcx > for ManualUnwrapOrDefault {
184170 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
185171 if expr. span . from_expansion ( ) || in_constant ( cx, expr. hir_id ) {
186172 return ;
187173 }
188- if !handle_match ( cx, expr) {
189- handle_if_let ( cx, expr) ;
174+ // Call handle only if the expression is `if let` or `match`
175+ if let Some ( if_let_or_match) = IfLetOrMatch :: parse ( cx, expr) {
176+ handle ( cx, if_let_or_match, expr) ;
190177 }
191178 }
192179}
0 commit comments