Skip to content

Commit 7081554

Browse files
committed
Cover Result for question_mark
1 parent 389a74b commit 7081554

File tree

1 file changed

+54
-21
lines changed

1 file changed

+54
-21
lines changed

clippy_lints/src/question_mark.rs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use clippy_utils::is_lang_ctor;
44
use clippy_utils::source::snippet_with_applicability;
55
use clippy_utils::sugg::Sugg;
66
use clippy_utils::ty::is_type_diagnostic_item;
7-
use clippy_utils::{eq_expr_value, path_to_local_id};
7+
use clippy_utils::{eq_expr_value, path_to_local, path_to_local_id};
88
use if_chain::if_chain;
99
use rustc_errors::Applicability;
10-
use rustc_hir::LangItem::{OptionNone, OptionSome};
10+
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
1111
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind};
1212
use rustc_lint::{LateContext, LateLintPass};
1313
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -48,16 +48,25 @@ impl QuestionMark {
4848
/// }
4949
/// ```
5050
///
51+
/// ```ignore
52+
/// if result.is_err() {
53+
/// return result;
54+
/// }
55+
/// ```
56+
///
5157
/// 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<'_>) {
58+
fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
5359
if_chain! {
5460
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
5561
if let ExprKind::MethodCall(segment, _, args, _) = &cond.kind;
56-
if segment.ident.name == sym!(is_none);
57-
if Self::expression_returns_none(cx, then);
5862
if let Some(subject) = args.get(0);
59-
if Self::is_option(cx, subject);
60-
63+
if (Self::is_option(cx, subject)
64+
&& Self::expression_returns_none(cx, then)
65+
&& segment.ident.name == sym!(is_none))
66+
||
67+
(Self::is_result(cx, subject)
68+
&& Self::expression_returns_unmodified_err(cx, then, subject)
69+
&& segment.ident.name == sym!(is_err));
6170
then {
6271
let mut applicability = Applicability::MachineApplicable;
6372
let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
@@ -95,31 +104,29 @@ impl QuestionMark {
95104
}
96105
}
97106

98-
fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
107+
fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
99108
if_chain! {
100109
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
101110
= higher::IfLet::hir(cx, expr);
102-
if Self::is_option(cx, let_expr);
103-
104111
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
105-
if is_lang_ctor(cx, path1, OptionSome);
112+
if (Self::is_option(cx, let_expr)
113+
&& Self::expression_returns_none(cx, if_else)
114+
&& is_lang_ctor(cx, path1, OptionSome))
115+
||
116+
(Self::is_result(cx, let_expr)
117+
&& Self::expression_returns_unmodified_err(cx, if_else, let_expr)
118+
&& is_lang_ctor(cx, path1, ResultOk));
119+
106120
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
107121
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
108-
109122
if let ExprKind::Block(block, None) = if_then.kind;
110123
if block.stmts.is_empty();
111124
if let Some(trailing_expr) = &block.expr;
112125
if path_to_local_id(trailing_expr, bind_id);
113-
114-
if Self::expression_returns_none(cx, if_else);
115126
then {
116127
let mut applicability = Applicability::MachineApplicable;
117128
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
118-
let replacement = format!(
119-
"{}{}?",
120-
receiver_str,
121-
if by_ref { ".as_ref()" } else { "" },
122-
);
129+
let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
123130

124131
span_lint_and_sugg(
125132
cx,
@@ -146,6 +153,12 @@ impl QuestionMark {
146153
is_type_diagnostic_item(cx, expr_ty, sym::Option)
147154
}
148155

156+
fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
157+
let expr_ty = cx.typeck_results().expr_ty(expression);
158+
159+
is_type_diagnostic_item(cx, expr_ty, sym::Result)
160+
}
161+
149162
fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
150163
match expression.kind {
151164
ExprKind::Block(block, _) => {
@@ -161,6 +174,26 @@ impl QuestionMark {
161174
}
162175
}
163176

177+
fn expression_returns_unmodified_err(
178+
cx: &LateContext<'_>,
179+
expression: &Expr<'_>,
180+
origin_hir_id: &Expr<'_>,
181+
) -> bool {
182+
match expression.kind {
183+
ExprKind::Block(block, _) => {
184+
if let Some(return_expression) = Self::return_expression(block) {
185+
return Self::expression_returns_unmodified_err(cx, return_expression, origin_hir_id);
186+
}
187+
188+
false
189+
},
190+
ExprKind::Ret(Some(expr)) => Self::expression_returns_unmodified_err(cx, expr, origin_hir_id),
191+
ExprKind::Call(expr, _) => Self::expression_returns_unmodified_err(cx, expr, origin_hir_id),
192+
ExprKind::Path(_) => path_to_local(expression) == path_to_local(origin_hir_id),
193+
_ => false,
194+
}
195+
}
196+
164197
fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
165198
// Check if last expression is a return statement. Then, return the expression
166199
if_chain! {
@@ -189,7 +222,7 @@ impl QuestionMark {
189222

190223
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
191224
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
192-
Self::check_is_none_and_early_return_none(cx, expr);
193-
Self::check_if_let_some_and_early_return_none(cx, expr);
225+
Self::check_is_none_or_err_and_early_return(cx, expr);
226+
Self::check_if_let_some_or_err_and_early_return(cx, expr);
194227
}
195228
}

0 commit comments

Comments
 (0)