1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::higher
;
3 use clippy_utils
::source
::snippet_with_applicability
;
4 use clippy_utils
::ty
::is_type_diagnostic_item
;
6 eq_expr_value
, get_parent_node
, is_else_clause
, is_lang_ctor
, path_to_local
, path_to_local_id
, peel_blocks
,
9 use if_chain
::if_chain
;
10 use rustc_errors
::Applicability
;
11 use rustc_hir
::LangItem
::{OptionNone, OptionSome, ResultErr, ResultOk}
;
12 use rustc_hir
::{BindingAnnotation, Expr, ExprKind, Node, PatKind, PathSegment, QPath}
;
13 use rustc_lint
::{LateContext, LateLintPass}
;
14 use rustc_middle
::ty
::Ty
;
15 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 use rustc_span
::{sym, symbol::Symbol}
;
18 declare_clippy_lint
! {
20 /// Checks for expressions that could be replaced by the question mark operator.
22 /// ### Why is this bad?
23 /// Question mark usage is more idiomatic.
27 /// if option.is_none() {
37 #[clippy::version = "pre 1.29.0"]
40 "checks for expressions that could be replaced by the question mark operator"
43 declare_lint_pass
!(QuestionMark
=> [QUESTION_MARK
]);
45 enum IfBlockType
<'hir
> {
46 /// An `if x.is_xxx() { a } else { b } ` expression.
48 /// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b)
54 Option
<&'hir Expr
<'hir
>>,
56 /// An `if let Xxx(a) = b { c } else { d }` expression.
58 /// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
66 Option
<&'hir Expr
<'hir
>>,
70 /// Checks if the given expression on the given context matches the following structure:
73 /// if option.is_none() {
79 /// if result.is_err() {
84 /// If it matches, it will suggest to use the question mark operator instead
85 fn check_is_none_or_err_and_early_return
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &Expr
<'tcx
>) {
87 if let Some(higher
::If { cond, then, r#else }
) = higher
::If
::hir(expr
);
88 if !is_else_clause(cx
.tcx
, expr
);
89 if let ExprKind
::MethodCall(segment
, args
, _
) = &cond
.kind
;
90 if let Some(caller
) = args
.get(0);
91 let caller_ty
= cx
.typeck_results().expr_ty(caller
);
92 let if_block
= IfBlockType
::IfIs(caller
, caller_ty
, segment
.ident
.name
, then
, r
#else);
93 if is_early_return(sym
::Option
, cx
, &if_block
) || is_early_return(sym
::Result
, cx
, &if_block
);
95 let mut applicability
= Applicability
::MachineApplicable
;
96 let receiver_str
= snippet_with_applicability(cx
, caller
.span
, "..", &mut applicability
);
97 let by_ref
= !caller_ty
.is_copy_modulo_regions(cx
.tcx
.at(caller
.span
), cx
.param_env
) &&
98 !matches
!(caller
.kind
, ExprKind
::Call(..) | ExprKind
::MethodCall(..));
99 let sugg
= if let Some(else_inner
) = r
#else {
100 if eq_expr_value(cx
, caller
, peel_blocks(else_inner
)) {
101 format
!("Some({}?)", receiver_str
)
106 format
!("{}{}?;", receiver_str
, if by_ref { ".as_ref()" }
else { "" }
)
113 "this block may be rewritten with the `?` operator",
122 fn check_if_let_some_or_err_and_early_return
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &Expr
<'tcx
>) {
124 if let Some(higher
::IfLet { let_pat, let_expr, if_then, if_else }
) = higher
::IfLet
::hir(cx
, expr
);
125 if !is_else_clause(cx
.tcx
, expr
);
126 if let PatKind
::TupleStruct(ref path1
, [field
], None
) = let_pat
.kind
;
127 if let PatKind
::Binding(annot
, bind_id
, ident
, _
) = field
.kind
;
128 let caller_ty
= cx
.typeck_results().expr_ty(let_expr
);
129 let if_block
= IfBlockType
::IfLet(path1
, caller_ty
, ident
.name
, let_expr
, if_then
, if_else
);
130 if (is_early_return(sym
::Option
, cx
, &if_block
) && path_to_local_id(peel_blocks(if_then
), bind_id
))
131 || is_early_return(sym
::Result
, cx
, &if_block
);
132 if if_else
.map(|e
| eq_expr_value(cx
, let_expr
, peel_blocks(e
))).filter(|e
| *e
).is_none();
134 let mut applicability
= Applicability
::MachineApplicable
;
135 let receiver_str
= snippet_with_applicability(cx
, let_expr
.span
, "..", &mut applicability
);
136 let by_ref
= matches
!(annot
, BindingAnnotation
::Ref
| BindingAnnotation
::RefMut
);
137 let requires_semi
= matches
!(get_parent_node(cx
.tcx
, expr
.hir_id
), Some(Node
::Stmt(_
)));
141 if by_ref { ".as_ref()" }
else { "" }
,
142 if requires_semi { ";" }
else { "" }
148 "this block may be rewritten with the `?` operator",
157 fn is_early_return(smbl
: Symbol
, cx
: &LateContext
<'_
>, if_block
: &IfBlockType
<'_
>) -> bool
{
159 IfBlockType
::IfIs(caller
, caller_ty
, call_sym
, if_then
, _
) => {
160 // If the block could be identified as `if x.is_none()/is_err()`,
161 // we then only need to check the if_then return to see if it is none/err.
162 is_type_diagnostic_item(cx
, caller_ty
, smbl
)
163 && expr_return_none_or_err(smbl
, cx
, if_then
, caller
, None
)
165 sym
::Option
=> call_sym
== sym
!(is_none
),
166 sym
::Result
=> call_sym
== sym
!(is_err
),
170 IfBlockType
::IfLet(qpath
, let_expr_ty
, let_pat_sym
, let_expr
, if_then
, if_else
) => {
171 is_type_diagnostic_item(cx
, let_expr_ty
, smbl
)
174 // We only need to check `if let Some(x) = option` not `if let None = option`,
175 // because the later one will be suggested as `if option.is_none()` thus causing conflict.
176 is_lang_ctor(cx
, qpath
, OptionSome
)
178 && expr_return_none_or_err(smbl
, cx
, if_else
.unwrap(), let_expr
, None
)
181 (is_lang_ctor(cx
, qpath
, ResultOk
)
183 && expr_return_none_or_err(smbl
, cx
, if_else
.unwrap(), let_expr
, Some(let_pat_sym
)))
184 || is_lang_ctor(cx
, qpath
, ResultErr
)
185 && expr_return_none_or_err(smbl
, cx
, if_then
, let_expr
, Some(let_pat_sym
))
193 fn expr_return_none_or_err(
195 cx
: &LateContext
<'_
>,
197 cond_expr
: &Expr
<'_
>,
198 err_sym
: Option
<Symbol
>,
200 match peel_blocks_with_stmt(expr
).kind
{
201 ExprKind
::Ret(Some(ret_expr
)) => expr_return_none_or_err(smbl
, cx
, ret_expr
, cond_expr
, err_sym
),
202 ExprKind
::Path(ref qpath
) => match smbl
{
203 sym
::Option
=> is_lang_ctor(cx
, qpath
, OptionNone
),
204 sym
::Result
=> path_to_local(expr
).is_some() && path_to_local(expr
) == path_to_local(cond_expr
),
207 ExprKind
::Call(call_expr
, args_expr
) => {
209 if smbl
== sym
::Result
;
210 if let ExprKind
::Path(QPath
::Resolved(_
, path
)) = &call_expr
.kind
;
211 if let Some(segment
) = path
.segments
.first();
212 if let Some(err_sym
) = err_sym
;
213 if let Some(arg
) = args_expr
.first();
214 if let ExprKind
::Path(QPath
::Resolved(_
, arg_path
)) = &arg
.kind
;
215 if let Some(PathSegment { ident, .. }
) = arg_path
.segments
.first();
217 return segment
.ident
.name
== sym
::Err
&& err_sym
== ident
.name
;
226 impl<'tcx
> LateLintPass
<'tcx
> for QuestionMark
{
227 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
228 check_is_none_or_err_and_early_return(cx
, expr
);
229 check_if_let_some_or_err_and_early_return(cx
, expr
);