Skip to content

Commit 65951c9

Browse files
committed
Auto merge of #7165 - camsteffen:question-mark, r=Manishearth
Fix needless_quesiton_mark false positive changelog: Fix [`needless_question_mark`] false positive where the inner value is implicity dereferenced by the question mark. Fixes #7107
2 parents af8cf94 + 919ed2b commit 65951c9

File tree

4 files changed

+30
-94
lines changed

4 files changed

+30
-94
lines changed
Lines changed: 19 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_lang_ctor;
23
use clippy_utils::source::snippet;
3-
use clippy_utils::ty::is_type_diagnostic_item;
4-
use clippy_utils::{differing_macro_contexts, is_lang_ctor};
54
use if_chain::if_chain;
65
use rustc_errors::Applicability;
76
use rustc_hir::LangItem::{OptionSome, ResultOk};
87
use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
98
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::TyS;
1010
use rustc_session::{declare_lint_pass, declare_tool_lint};
11-
use rustc_span::sym;
1211

1312
declare_clippy_lint! {
1413
/// **What it does:**
@@ -63,12 +62,6 @@ declare_clippy_lint! {
6362

6463
declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
6564

66-
#[derive(Debug)]
67-
enum SomeOkCall<'a> {
68-
SomeCall(&'a Expr<'a>, &'a Expr<'a>),
69-
OkCall(&'a Expr<'a>, &'a Expr<'a>),
70-
}
71-
7265
impl LateLintPass<'_> for NeedlessQuestionMark {
7366
/*
7467
* The question mark operator is compatible with both Result<T, E> and Option<T>,
@@ -90,104 +83,37 @@ impl LateLintPass<'_> for NeedlessQuestionMark {
9083
*/
9184

9285
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
93-
let e = match &expr.kind {
94-
ExprKind::Ret(Some(e)) => e,
95-
_ => return,
96-
};
97-
98-
if let Some(ok_some_call) = is_some_or_ok_call(cx, e) {
99-
emit_lint(cx, &ok_some_call);
86+
if let ExprKind::Ret(Some(e)) = expr.kind {
87+
check(cx, e);
10088
}
10189
}
10290

10391
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
104-
// Function / Closure block
105-
let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind {
106-
block.expr
107-
} else {
108-
// Single line closure
109-
Some(&body.value)
110-
};
111-
112-
if_chain! {
113-
if let Some(expr) = expr_opt;
114-
if let Some(ok_some_call) = is_some_or_ok_call(cx, expr);
115-
then {
116-
emit_lint(cx, &ok_some_call);
117-
}
118-
};
92+
check(cx, body.value.peel_blocks());
11993
}
12094
}
12195

122-
fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) {
123-
let (entire_expr, inner_expr) = match expr {
124-
SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner),
96+
fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
97+
let inner_expr = if_chain! {
98+
if let ExprKind::Call(path, [arg]) = &expr.kind;
99+
if let ExprKind::Path(ref qpath) = &path.kind;
100+
if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
101+
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind;
102+
if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
103+
if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind;
104+
if expr.span.ctxt() == inner_expr.span.ctxt();
105+
let expr_ty = cx.typeck_results().expr_ty(expr);
106+
let inner_ty = cx.typeck_results().expr_ty(inner_expr);
107+
if TyS::same_type(expr_ty, inner_ty);
108+
then { inner_expr } else { return; }
125109
};
126-
127110
span_lint_and_sugg(
128111
cx,
129112
NEEDLESS_QUESTION_MARK,
130-
entire_expr.span,
113+
expr.span,
131114
"question mark operator is useless here",
132115
"try",
133116
format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
134117
Applicability::MachineApplicable,
135118
);
136119
}
137-
138-
fn is_some_or_ok_call<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<SomeOkCall<'a>> {
139-
if_chain! {
140-
// Check outer expression matches CALL_IDENT(ARGUMENT) format
141-
if let ExprKind::Call(path, args) = &expr.kind;
142-
if let ExprKind::Path(ref qpath) = &path.kind;
143-
if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
144-
145-
// Extract inner expression from ARGUMENT
146-
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind;
147-
if let ExprKind::Call(called, args) = &inner_expr_with_q.kind;
148-
if args.len() == 1;
149-
150-
if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind;
151-
then {
152-
// Extract inner expr type from match argument generated by
153-
// question mark operator
154-
let inner_expr = &args[0];
155-
156-
// if the inner expr is inside macro but the outer one is not, do not lint (#6921)
157-
if differing_macro_contexts(expr.span, inner_expr.span) {
158-
return None;
159-
}
160-
161-
let inner_ty = cx.typeck_results().expr_ty(inner_expr);
162-
let outer_ty = cx.typeck_results().expr_ty(expr);
163-
164-
// Check if outer and inner type are Option
165-
let outer_is_some = is_type_diagnostic_item(cx, outer_ty, sym::option_type);
166-
let inner_is_some = is_type_diagnostic_item(cx, inner_ty, sym::option_type);
167-
168-
// Check for Option MSRV
169-
if outer_is_some && inner_is_some {
170-
return Some(SomeOkCall::SomeCall(expr, inner_expr));
171-
}
172-
173-
// Check if outer and inner type are Result
174-
let outer_is_result = is_type_diagnostic_item(cx, outer_ty, sym::result_type);
175-
let inner_is_result = is_type_diagnostic_item(cx, inner_ty, sym::result_type);
176-
177-
// Additional check: if the error type of the Result can be converted
178-
// via the From trait, then don't match
179-
let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr);
180-
181-
// Must meet Result MSRV
182-
if outer_is_result && inner_is_result && does_not_call_from {
183-
return Some(SomeOkCall::OkCall(expr, inner_expr));
184-
}
185-
}
186-
}
187-
188-
None
189-
}
190-
191-
fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool {
192-
return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr);
193-
}

tests/ui/needless_question_mark.fixed

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ where
9494
Ok(x?)
9595
}
9696

97+
// not quite needless
98+
fn deref_ref(s: Option<&String>) -> Option<&str> {
99+
Some(s?)
100+
}
101+
97102
fn main() {}
98103

99104
// #6921 if a macro wraps an expr in Some( ) and the ? is in the macro use,

tests/ui/needless_question_mark.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ where
9494
Ok(x?)
9595
}
9696

97+
// not quite needless
98+
fn deref_ref(s: Option<&String>) -> Option<&str> {
99+
Some(s?)
100+
}
101+
97102
fn main() {}
98103

99104
// #6921 if a macro wraps an expr in Some( ) and the ? is in the macro use,

tests/ui/needless_question_mark.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ LL | return Ok(t.magic?);
6767
| ^^^^^^^^^^^^ help: try: `t.magic`
6868

6969
error: question mark operator is useless here
70-
--> $DIR/needless_question_mark.rs:115:27
70+
--> $DIR/needless_question_mark.rs:120:27
7171
|
7272
LL | || -> Option<_> { Some(Some($expr)?) }()
7373
| ^^^^^^^^^^^^^^^^^^ help: try: `Some($expr)`

0 commit comments

Comments
 (0)