1
- use clippy_utils:: sugg:: Sugg ;
2
1
use rustc_errors:: Applicability ;
3
2
use rustc_hir:: def:: Res ;
4
3
use rustc_hir:: { Arm , Expr , ExprKind , HirId , LangItem , MatchSource , Pat , PatKind , QPath } ;
5
4
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
5
+ use rustc_middle:: ty:: GenericArgKind ;
6
6
use rustc_session:: declare_lint_pass;
7
7
use rustc_span:: sym;
8
8
9
9
use 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 ;
11
12
use clippy_utils:: ty:: implements_trait;
12
13
use clippy_utils:: { in_constant, is_default_equivalent, peel_blocks, span_contains_comment} ;
13
14
@@ -105,28 +106,49 @@ fn get_some_and_none_bodies<'tcx>(
105
106
}
106
107
}
107
108
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
+ } ,
111
132
} ;
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 , & [ ] )
121
142
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
122
143
&& let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = peel_blocks ( body_some) . kind
123
144
&& let Res :: Local ( local_id) = path. res
124
145
&& local_id == binding_id
125
146
// We now check the `None` arm is calling a method equivalent to `Default::default`.
126
147
&& let body_none = peel_blocks ( body_none)
127
148
&& 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)
129
150
{
151
+ // Machine applicable only if there are no comments present
130
152
let applicability = if span_contains_comment ( cx. sess ( ) . source_map ( ) , expr. span ) {
131
153
Applicability :: MaybeIncorrect
132
154
} else {
@@ -136,57 +158,22 @@ fn handle_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
136
158
cx,
137
159
MANUAL_UNWRAP_OR_DEFAULT ,
138
160
expr. span ,
139
- "match can be simplified with `.unwrap_or_default()`",
161
+ format ! ( "{expr_name} can be simplified with `.unwrap_or_default()`") ,
140
162
"replace it with" ,
141
163
format ! ( "{receiver}.unwrap_or_default()" ) ,
142
164
applicability,
143
165
) ;
144
166
}
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
- }
181
167
}
182
168
183
169
impl < ' tcx > LateLintPass < ' tcx > for ManualUnwrapOrDefault {
184
170
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
185
171
if expr. span . from_expansion ( ) || in_constant ( cx, expr. hir_id ) {
186
172
return ;
187
173
}
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) ;
190
177
}
191
178
}
192
179
}
0 commit comments