1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::sugg
::Sugg
;
4 can_move_expr_to_closure
, eager_or_lazy
, higher
, in_constant
, is_else_clause
, is_res_lang_ctor
, peel_blocks
,
5 peel_hir_expr_while
, CaptureKind
,
7 use if_chain
::if_chain
;
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::LangItem
::{OptionNone, OptionSome, ResultErr, ResultOk}
;
11 def
::Res
, Arm
, BindingAnnotation
, Expr
, ExprKind
, MatchSource
, Mutability
, Pat
, PatKind
, Path
, QPath
, UnOp
,
13 use rustc_lint
::{LateContext, LateLintPass}
;
14 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 declare_clippy_lint
! {
18 /// Lints usage of `if let Some(v) = ... { y } else { x }` and
19 /// `match .. { Some(v) => y, None/_ => x }` which are more
20 /// idiomatically done with `Option::map_or` (if the else bit is a pure
21 /// expression) or `Option::map_or_else` (if the else bit is an impure
24 /// ### Why is this bad?
25 /// Using the dedicated functions of the `Option` type is clearer and
26 /// more concise than an `if let` expression.
28 /// ### Known problems
29 /// This lint uses a deliberately conservative metric for checking
30 /// if the inside of either body contains breaks or continues which will
31 /// cause it to not suggest a fix if either block contains a loop with
32 /// continues or breaks contained within the loop.
36 /// # let optional: Option<u32> = Some(0);
37 /// # fn do_complicated_function() -> u32 { 5 };
38 /// let _ = if let Some(foo) = optional {
43 /// let _ = match optional {
44 /// Some(val) => val + 1,
47 /// let _ = if let Some(foo) = optional {
50 /// let y = do_complicated_function();
58 /// # let optional: Option<u32> = Some(0);
59 /// # fn do_complicated_function() -> u32 { 5 };
60 /// let _ = optional.map_or(5, |foo| foo);
61 /// let _ = optional.map_or(5, |val| val + 1);
62 /// let _ = optional.map_or_else(||{
63 /// let y = do_complicated_function();
67 // FIXME: Before moving this lint out of nursery, the lint name needs to be updated. It now also
68 // covers matches and `Result`.
69 #[clippy::version = "1.47.0"]
70 pub OPTION_IF_LET_ELSE
,
72 "reimplementation of Option::map_or"
75 declare_lint_pass
!(OptionIfLetElse
=> [OPTION_IF_LET_ELSE
]);
77 /// A struct containing information about occurrences of construct that this lint detects
82 /// if let Some(..) = {..} else {..}
91 struct OptionOccurrence
{
98 fn format_option_in_sugg(cx
: &LateContext
<'_
>, cond_expr
: &Expr
<'_
>, as_ref
: bool
, as_mut
: bool
) -> String
{
101 Sugg
::hir_with_macro_callsite(cx
, cond_expr
, "..").maybe_par(),
112 fn try_get_option_occurrence
<'tcx
>(
113 cx
: &LateContext
<'tcx
>,
116 if_then
: &'tcx Expr
<'_
>,
117 if_else
: &'tcx Expr
<'_
>,
118 ) -> Option
<OptionOccurrence
> {
119 let cond_expr
= match expr
.kind
{
120 ExprKind
::Unary(UnOp
::Deref
, inner_expr
) | ExprKind
::AddrOf(_
, _
, inner_expr
) => inner_expr
,
123 let inner_pat
= try_get_inner_pat(cx
, pat
)?
;
125 if let PatKind
::Binding(bind_annotation
, _
, id
, None
) = inner_pat
.kind
;
126 if let Some(some_captures
) = can_move_expr_to_closure(cx
, if_then
);
127 if let Some(none_captures
) = can_move_expr_to_closure(cx
, if_else
);
130 .filter_map(|(id
, &c
)| none_captures
.get(id
).map(|&c2
| (c
, c2
)))
131 .all(|(x
, y
)| x
.is_imm_ref() && y
.is_imm_ref());
133 let capture_mut
= if bind_annotation
== BindingAnnotation
::MUT { "mut " }
else { "" }
;
134 let some_body
= peel_blocks(if_then
);
135 let none_body
= peel_blocks(if_else
);
136 let method_sugg
= if eager_or_lazy
::switch_to_eager_eval(cx
, none_body
) { "map_or" }
else { "map_or_else" }
;
137 let capture_name
= id
.name
.to_ident_string();
138 let (as_ref
, as_mut
) = match &expr
.kind
{
139 ExprKind
::AddrOf(_
, Mutability
::Not
, _
) => (true, false),
140 ExprKind
::AddrOf(_
, Mutability
::Mut
, _
) => (false, true),
141 _
=> (bind_annotation
== BindingAnnotation
::REF
, bind_annotation
== BindingAnnotation
::REF_MUT
),
144 // Check if captures the closure will need conflict with borrows made in the scrutinee.
145 // TODO: check all the references made in the scrutinee expression. This will require interacting
146 // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
147 if as_ref
|| as_mut
{
148 let e
= peel_hir_expr_while(cond_expr
, |e
| match e
.kind
{
149 ExprKind
::Field(e
, _
) | ExprKind
::AddrOf(_
, _
, e
) => Some(e
),
152 if let ExprKind
::Path(QPath
::Resolved(None
, Path { res: Res::Local(local_id), .. }
)) = e
.kind
{
153 match some_captures
.get(local_id
)
154 .or_else(|| (method_sugg
== "map_or_else").then_some(()).and_then(|_
| none_captures
.get(local_id
)))
156 Some(CaptureKind
::Value
| CaptureKind
::Ref(Mutability
::Mut
)) => return None
,
157 Some(CaptureKind
::Ref(Mutability
::Not
)) if as_mut
=> return None
,
158 Some(CaptureKind
::Ref(Mutability
::Not
)) | None
=> (),
163 return Some(OptionOccurrence
{
164 option
: format_option_in_sugg(cx
, cond_expr
, as_ref
, as_mut
),
165 method_sugg
: method_sugg
.to_string(),
166 some_expr
: format
!("|{capture_mut}{capture_name}| {}", Sugg
::hir_with_macro_callsite(cx
, some_body
, "..")),
167 none_expr
: format
!("{}{}", if method_sugg
== "map_or" { "" }
else { "|| " }
, Sugg
::hir_with_macro_callsite(cx
, none_body
, "..")),
175 fn try_get_inner_pat
<'tcx
>(cx
: &LateContext
<'tcx
>, pat
: &Pat
<'tcx
>) -> Option
<&'tcx Pat
<'tcx
>> {
176 if let PatKind
::TupleStruct(ref qpath
, [inner_pat
], ..) = pat
.kind
{
177 let res
= cx
.qpath_res(qpath
, pat
.hir_id
);
178 if is_res_lang_ctor(cx
, res
, OptionSome
) || is_res_lang_ctor(cx
, res
, ResultOk
) {
179 return Some(inner_pat
);
185 /// If this expression is the option if let/else construct we're detecting, then
186 /// this function returns an `OptionOccurrence` struct with details if
187 /// this construct is found, or None if this construct is not found.
188 fn detect_option_if_let_else
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &Expr
<'tcx
>) -> Option
<OptionOccurrence
> {
189 if let Some(higher
::IfLet
{
193 if_else
: Some(if_else
),
194 }) = higher
::IfLet
::hir(cx
, expr
)
196 if !is_else_clause(cx
.tcx
, expr
) {
197 return try_get_option_occurrence(cx
, let_pat
, let_expr
, if_then
, if_else
);
203 fn detect_option_match
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &Expr
<'tcx
>) -> Option
<OptionOccurrence
> {
204 if let ExprKind
::Match(ex
, arms
, MatchSource
::Normal
) = expr
.kind
{
205 if let Some((let_pat
, if_then
, if_else
)) = try_convert_match(cx
, arms
) {
206 return try_get_option_occurrence(cx
, let_pat
, ex
, if_then
, if_else
);
212 fn try_convert_match
<'tcx
>(
213 cx
: &LateContext
<'tcx
>,
215 ) -> Option
<(&'tcx Pat
<'tcx
>, &'tcx Expr
<'tcx
>, &'tcx Expr
<'tcx
>)> {
217 return if is_none_or_err_arm(cx
, &arms
[1]) {
218 Some((arms
[0].pat
, arms
[0].body
, arms
[1].body
))
219 } else if is_none_or_err_arm(cx
, &arms
[0]) {
220 Some((arms
[1].pat
, arms
[1].body
, arms
[0].body
))
228 fn is_none_or_err_arm(cx
: &LateContext
<'_
>, arm
: &Arm
<'_
>) -> bool
{
230 PatKind
::Path(ref qpath
) => is_res_lang_ctor(cx
, cx
.qpath_res(qpath
, arm
.pat
.hir_id
), OptionNone
),
231 PatKind
::TupleStruct(ref qpath
, [first_pat
], _
) => {
232 is_res_lang_ctor(cx
, cx
.qpath_res(qpath
, arm
.pat
.hir_id
), ResultErr
)
233 && matches
!(first_pat
.kind
, PatKind
::Wild
)
235 PatKind
::Wild
=> true,
240 impl<'tcx
> LateLintPass
<'tcx
> for OptionIfLetElse
{
241 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &Expr
<'tcx
>) {
242 // Don't lint macros and constants
243 if expr
.span
.from_expansion() || in_constant(cx
, expr
.hir_id
) {
247 let detection
= detect_option_if_let_else(cx
, expr
).or_else(|| detect_option_match(cx
, expr
));
248 if let Some(det
) = detection
{
253 format
!("use Option::{} instead of an if let/else", det
.method_sugg
).as_str(),
257 det
.option
, det
.method_sugg
, det
.none_expr
, det
.some_expr
259 Applicability
::MaybeIncorrect
,