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
::sugg
::Sugg
;
5 use clippy_utils
::ty
::is_type_diagnostic_item
;
6 use clippy_utils
::{eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt}
;
7 use if_chain
::if_chain
;
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::LangItem
::{OptionNone, OptionSome, ResultOk}
;
10 use rustc_hir
::{BindingAnnotation, Expr, ExprKind, PatKind}
;
11 use rustc_lint
::{LateContext, LateLintPass}
;
12 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
15 declare_clippy_lint
! {
17 /// Checks for expressions that could be replaced by the question mark operator.
19 /// ### Why is this bad?
20 /// Question mark usage is more idiomatic.
24 /// if option.is_none() {
34 #[clippy::version = "pre 1.29.0"]
37 "checks for expressions that could be replaced by the question mark operator"
40 declare_lint_pass
!(QuestionMark
=> [QUESTION_MARK
]);
43 /// Checks if the given expression on the given context matches the following structure:
46 /// if option.is_none() {
52 /// if result.is_err() {
57 /// If it matches, it will suggest to use the question mark operator instead
58 fn check_is_none_or_err_and_early_return(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
60 if let Some(higher
::If { cond, then, r#else }
) = higher
::If
::hir(expr
);
61 if let ExprKind
::MethodCall(segment
, args
, _
) = &cond
.kind
;
62 if let Some(subject
) = args
.get(0);
63 if (Self::option_check_and_early_return(cx
, subject
, then
) && segment
.ident
.name
== sym
!(is_none
)) ||
64 (Self::result_check_and_early_return(cx
, subject
, then
) && segment
.ident
.name
== sym
!(is_err
));
66 let mut applicability
= Applicability
::MachineApplicable
;
67 let receiver_str
= &Sugg
::hir_with_applicability(cx
, subject
, "..", &mut applicability
);
68 let mut replacement
: Option
<String
> = None
;
69 if let Some(else_inner
) = r
#else {
70 if eq_expr_value(cx
, subject
, peel_blocks(else_inner
)) {
71 replacement
= Some(format
!("Some({}?)", receiver_str
));
73 } else if Self::moves_by_default(cx
, subject
)
74 && !matches
!(subject
.kind
, ExprKind
::Call(..) | ExprKind
::MethodCall(..))
76 replacement
= Some(format
!("{}.as_ref()?;", receiver_str
));
78 replacement
= Some(format
!("{}?;", receiver_str
));
81 if let Some(replacement_str
) = replacement
{
86 "this block may be rewritten with the `?` operator",
96 fn check_if_let_some_or_err_and_early_return(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
98 if let Some(higher
::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }
)
99 = higher
::IfLet
::hir(cx
, expr
);
100 if let PatKind
::TupleStruct(ref path1
, fields
, None
) = let_pat
.kind
;
101 if (Self::option_check_and_early_return(cx
, let_expr
, if_else
) && is_lang_ctor(cx
, path1
, OptionSome
)) ||
102 (Self::result_check_and_early_return(cx
, let_expr
, if_else
) && is_lang_ctor(cx
, path1
, ResultOk
));
104 if let PatKind
::Binding(annot
, bind_id
, _
, _
) = fields
[0].kind
;
105 let by_ref
= matches
!(annot
, BindingAnnotation
::Ref
| BindingAnnotation
::RefMut
);
106 if path_to_local_id(peel_blocks(if_then
), bind_id
);
108 let mut applicability
= Applicability
::MachineApplicable
;
109 let receiver_str
= snippet_with_applicability(cx
, let_expr
.span
, "..", &mut applicability
);
110 let replacement
= format
!("{}{}?", receiver_str
, if by_ref { ".as_ref()" }
else { "" }
,);
116 "this if-let-else may be rewritten with the `?` operator",
125 fn result_check_and_early_return(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, nested_expr
: &Expr
<'_
>) -> bool
{
126 Self::is_result(cx
, expr
) && Self::expression_returns_unmodified_err(nested_expr
, expr
)
129 fn option_check_and_early_return(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, nested_expr
: &Expr
<'_
>) -> bool
{
130 Self::is_option(cx
, expr
) && Self::expression_returns_none(cx
, nested_expr
)
133 fn moves_by_default(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
134 let expr_ty
= cx
.typeck_results().expr_ty(expression
);
136 !expr_ty
.is_copy_modulo_regions(cx
.tcx
.at(expression
.span
), cx
.param_env
)
139 fn is_option(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
140 let expr_ty
= cx
.typeck_results().expr_ty(expression
);
142 is_type_diagnostic_item(cx
, expr_ty
, sym
::Option
)
145 fn is_result(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
146 let expr_ty
= cx
.typeck_results().expr_ty(expression
);
148 is_type_diagnostic_item(cx
, expr_ty
, sym
::Result
)
151 fn expression_returns_none(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
152 match peel_blocks_with_stmt(expression
).kind
{
153 ExprKind
::Ret(Some(expr
)) => Self::expression_returns_none(cx
, expr
),
154 ExprKind
::Path(ref qpath
) => is_lang_ctor(cx
, qpath
, OptionNone
),
159 fn expression_returns_unmodified_err(expr
: &Expr
<'_
>, cond_expr
: &Expr
<'_
>) -> bool
{
160 match peel_blocks_with_stmt(expr
).kind
{
161 ExprKind
::Ret(Some(ret_expr
)) => Self::expression_returns_unmodified_err(ret_expr
, cond_expr
),
162 ExprKind
::Path(_
) => path_to_local(expr
).is_some() && path_to_local(expr
) == path_to_local(cond_expr
),
168 impl<'tcx
> LateLintPass
<'tcx
> for QuestionMark
{
169 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
170 Self::check_is_none_or_err_and_early_return(cx
, expr
);
171 Self::check_if_let_some_or_err_and_early_return(cx
, expr
);