Skip to content

Commit ee8c155

Browse files
committed
Auto merge of #7840 - dswij:question-mark-result, r=llogiq
Cover `Result` for `question_mark` closes #7135 changelog: [`question_mark`] now covers `Result`
2 parents 5bdd2ce + 083a454 commit ee8c155

File tree

4 files changed

+104
-22
lines changed

4 files changed

+104
-22
lines changed

clippy_lints/src/question_mark.rs

Lines changed: 53 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,20 @@ 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::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));
6165
then {
6266
let mut applicability = Applicability::MachineApplicable;
6367
let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
@@ -95,31 +99,24 @@ impl QuestionMark {
9599
}
96100
}
97101

98-
fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
102+
fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
99103
if_chain! {
100104
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
101105
= higher::IfLet::hir(cx, expr);
102-
if Self::is_option(cx, let_expr);
103-
104106
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
105-
if is_lang_ctor(cx, path1, OptionSome);
107+
if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
108+
(Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
109+
106110
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
107111
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
108-
109112
if let ExprKind::Block(block, None) = if_then.kind;
110113
if block.stmts.is_empty();
111114
if let Some(trailing_expr) = &block.expr;
112115
if path_to_local_id(trailing_expr, bind_id);
113-
114-
if Self::expression_returns_none(cx, if_else);
115116
then {
116117
let mut applicability = Applicability::MachineApplicable;
117118
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-
);
119+
let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
123120

124121
span_lint_and_sugg(
125122
cx,
@@ -134,6 +131,14 @@ impl QuestionMark {
134131
}
135132
}
136133

134+
fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
135+
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
136+
}
137+
138+
fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
139+
Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
140+
}
141+
137142
fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
138143
let expr_ty = cx.typeck_results().expr_ty(expression);
139144

@@ -146,6 +151,12 @@ impl QuestionMark {
146151
is_type_diagnostic_item(cx, expr_ty, sym::Option)
147152
}
148153

154+
fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
155+
let expr_ty = cx.typeck_results().expr_ty(expression);
156+
157+
is_type_diagnostic_item(cx, expr_ty, sym::Result)
158+
}
159+
149160
fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
150161
match expression.kind {
151162
ExprKind::Block(block, _) => {
@@ -161,6 +172,27 @@ impl QuestionMark {
161172
}
162173
}
163174

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

190222
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
191223
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);
224+
Self::check_is_none_or_err_and_early_return(cx, expr);
225+
Self::check_if_let_some_or_err_and_early_return(cx, expr);
194226
}
195227
}

tests/ui/question_mark.fixed

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ fn func() -> Option<i32> {
104104
Some(0)
105105
}
106106

107+
fn result_func(x: Result<i32, &str>) -> Result<i32, &str> {
108+
let _ = x?;
109+
110+
x?;
111+
112+
// No warning
113+
let y = if let Ok(x) = x {
114+
x
115+
} else {
116+
return Err("some error");
117+
};
118+
119+
Ok(y)
120+
}
121+
107122
fn main() {
108123
some_func(Some(42));
109124
some_func(None);
@@ -123,4 +138,6 @@ fn main() {
123138
returns_something_similar_to_option(so);
124139

125140
func();
141+
142+
let _ = result_func(Ok(42));
126143
}

tests/ui/question_mark.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,23 @@ fn func() -> Option<i32> {
134134
Some(0)
135135
}
136136

137+
fn result_func(x: Result<i32, &str>) -> Result<i32, &str> {
138+
let _ = if let Ok(x) = x { x } else { return x };
139+
140+
if x.is_err() {
141+
return x;
142+
}
143+
144+
// No warning
145+
let y = if let Ok(x) = x {
146+
x
147+
} else {
148+
return Err("some error");
149+
};
150+
151+
Ok(y)
152+
}
153+
137154
fn main() {
138155
some_func(Some(42));
139156
some_func(None);
@@ -153,4 +170,6 @@ fn main() {
153170
returns_something_similar_to_option(so);
154171

155172
func();
173+
174+
let _ = result_func(Ok(42));
156175
}

tests/ui/question_mark.stderr

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,19 @@ LL | | return None;
100100
LL | | }
101101
| |_____^ help: replace it with: `f()?;`
102102

103-
error: aborting due to 11 previous errors
103+
error: this if-let-else may be rewritten with the `?` operator
104+
--> $DIR/question_mark.rs:138:13
105+
|
106+
LL | let _ = if let Ok(x) = x { x } else { return x };
107+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?`
108+
109+
error: this block may be rewritten with the `?` operator
110+
--> $DIR/question_mark.rs:140:5
111+
|
112+
LL | / if x.is_err() {
113+
LL | | return x;
114+
LL | | }
115+
| |_____^ help: replace it with: `x?;`
116+
117+
error: aborting due to 13 previous errors
104118

0 commit comments

Comments
 (0)