1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::higher
;
3 use clippy_utils
::is_lang_ctor
;
4 use clippy_utils
::source
::snippet_with_applicability
;
5 use clippy_utils
::sugg
::Sugg
;
6 use clippy_utils
::ty
::is_type_diagnostic_item
;
7 use clippy_utils
::{eq_expr_value, path_to_local_id}
;
8 use if_chain
::if_chain
;
9 use rustc_errors
::Applicability
;
10 use rustc_hir
::LangItem
::{OptionNone, OptionSome}
;
11 use rustc_hir
::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind}
;
12 use rustc_lint
::{LateContext, LateLintPass}
;
13 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 declare_clippy_lint
! {
18 /// Checks for expressions that could be replaced by the question mark operator.
20 /// ### Why is this bad?
21 /// Question mark usage is more idiomatic.
25 /// if option.is_none() {
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() {
51 /// If it matches, it will suggest to use the question mark operator instead
52 fn check_is_none_and_early_return_none(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
54 if let Some(higher
::If { cond, then, r#else }
) = higher
::If
::hir(expr
);
55 if let ExprKind
::MethodCall(segment
, _
, args
, _
) = &cond
.kind
;
56 if segment
.ident
.name
== sym
!(is_none
);
57 if Self::expression_returns_none(cx
, then
);
58 if let Some(subject
) = args
.get(0);
59 if Self::is_option(cx
, subject
);
62 let mut applicability
= Applicability
::MachineApplicable
;
63 let receiver_str
= &Sugg
::hir_with_applicability(cx
, subject
, "..", &mut applicability
);
64 let mut replacement
: Option
<String
> = None
;
65 if let Some(else_inner
) = r
#else {
67 if let ExprKind
::Block(block
, None
) = &else_inner
.kind
;
68 if block
.stmts
.is_empty();
69 if let Some(block_expr
) = &block
.expr
;
70 if eq_expr_value(cx
, subject
, block_expr
);
72 replacement
= Some(format
!("Some({}?)", receiver_str
));
75 } else if Self::moves_by_default(cx
, subject
)
76 && !matches
!(subject
.kind
, ExprKind
::Call(..) | ExprKind
::MethodCall(..))
78 replacement
= Some(format
!("{}.as_ref()?;", receiver_str
));
80 replacement
= Some(format
!("{}?;", receiver_str
));
83 if let Some(replacement_str
) = replacement
{
88 "this block may be rewritten with the `?` operator",
98 fn check_if_let_some_and_early_return_none(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
100 if let Some(higher
::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }
) = higher
::IfLet
::hir(cx
, expr
);
101 if Self::is_option(cx
, let_expr
);
103 if let PatKind
::TupleStruct(ref path1
, fields
, None
) = let_pat
.kind
;
104 if is_lang_ctor(cx
, path1
, OptionSome
);
105 if let PatKind
::Binding(annot
, bind_id
, _
, _
) = fields
[0].kind
;
106 let by_ref
= matches
!(annot
, BindingAnnotation
::Ref
| BindingAnnotation
::RefMut
);
108 if let ExprKind
::Block(ref block
, None
) = if_then
.kind
;
109 if block
.stmts
.is_empty();
110 if let Some(trailing_expr
) = &block
.expr
;
111 if path_to_local_id(trailing_expr
, bind_id
);
113 if Self::expression_returns_none(cx
, if_else
);
115 let mut applicability
= Applicability
::MachineApplicable
;
116 let receiver_str
= snippet_with_applicability(cx
, let_expr
.span
, "..", &mut applicability
);
117 let replacement
= format
!(
120 if by_ref { ".as_ref()" }
else { "" }
,
127 "this if-let-else may be rewritten with the `?` operator",
136 fn moves_by_default(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
137 let expr_ty
= cx
.typeck_results().expr_ty(expression
);
139 !expr_ty
.is_copy_modulo_regions(cx
.tcx
.at(expression
.span
), cx
.param_env
)
142 fn is_option(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
143 let expr_ty
= cx
.typeck_results().expr_ty(expression
);
145 is_type_diagnostic_item(cx
, expr_ty
, sym
::option_type
)
148 fn expression_returns_none(cx
: &LateContext
<'_
>, expression
: &Expr
<'_
>) -> bool
{
149 match expression
.kind
{
150 ExprKind
::Block(block
, _
) => {
151 if let Some(return_expression
) = Self::return_expression(block
) {
152 return Self::expression_returns_none(cx
, return_expression
);
157 ExprKind
::Ret(Some(expr
)) => Self::expression_returns_none(cx
, expr
),
158 ExprKind
::Path(ref qpath
) => is_lang_ctor(cx
, qpath
, OptionNone
),
163 fn return_expression
<'tcx
>(block
: &Block
<'tcx
>) -> Option
<&'tcx Expr
<'tcx
>> {
164 // Check if last expression is a return statement. Then, return the expression
166 if block
.stmts
.len() == 1;
167 if let Some(expr
) = block
.stmts
.iter().last();
168 if let StmtKind
::Semi(expr
) = expr
.kind
;
169 if let ExprKind
::Ret(Some(ret_expr
)) = expr
.kind
;
172 return Some(ret_expr
);
176 // Check for `return` without a semicolon.
178 if block
.stmts
.is_empty();
179 if let Some(ExprKind
::Ret(Some(ret_expr
))) = block
.expr
.as_ref().map(|e
| &e
.kind
);
181 return Some(ret_expr
);
189 impl<'tcx
> LateLintPass
<'tcx
> for QuestionMark
{
190 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
191 Self::check_is_none_and_early_return_none(cx
, expr
);
192 Self::check_if_let_some_and_early_return_none(cx
, expr
);