1 use clippy_utils::ty::is_type_diagnostic_item;
2 use rustc_errors::Applicability;
3 use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
4 use rustc_lint::{LateContext, LateLintPass, LintContext};
5 use rustc_semver::RustcVersion;
6 use rustc_session::{declare_tool_lint, impl_lint_pass};
10 use if_chain::if_chain;
12 declare_clippy_lint! {
14 /// Suggests alternatives for useless applications of `?` in terminating expressions
16 /// **Why is this bad?** There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
18 /// **Known problems:** None.
24 /// magic: Option<usize>,
27 /// fn f(to: TO) -> Option<usize> {
32 /// magic: Result<usize, bool>,
35 /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
36 /// tr.and_then(|t| Ok(t.magic?))
43 /// magic: Option<usize>,
46 /// fn f(to: TO) -> Option<usize> {
51 /// magic: Result<usize, bool>,
54 /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
55 /// tr.and_then(|t| t.magic)
58 pub NEEDLESS_QUESTION_MARK,
60 "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result<T, E>`."
63 const NEEDLESS_QUESTION_MARK_RESULT_MSRV: RustcVersion = RustcVersion::new(1, 13, 0);
64 const NEEDLESS_QUESTION_MARK_OPTION_MSRV: RustcVersion = RustcVersion::new(1, 22, 0);
66 pub struct NeedlessQuestionMark {
67 msrv: Option<RustcVersion>,
70 impl NeedlessQuestionMark {
72 pub fn new(msrv: Option<RustcVersion>) -> Self {
77 impl_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
81 SomeCall(&'a Expr<'a>, &'a Expr<'a>),
82 OkCall(&'a Expr<'a>, &'a Expr<'a>),
85 impl LateLintPass<'_> for NeedlessQuestionMark {
87 * The question mark operator is compatible with both Result<T, E> and Option<T>,
88 * from Rust 1.13 and 1.22 respectively.
93 * Expressions that look like this:
94 * Some(option?), Ok(result?)
97 * Last expression of a body
99 * A body's value (single line closure)
101 * What do we not match:
102 * Implicit calls to `from(..)` on the error value
105 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
106 let e = match &expr.kind {
107 ExprKind::Ret(Some(e)) => e,
111 if let Some(ok_some_call) = is_some_or_ok_call(self, cx, e) {
112 emit_lint(cx, &ok_some_call);
116 fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
117 // Function / Closure block
118 let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind {
121 // Single line closure
126 if let Some(expr) = expr_opt;
127 if let Some(ok_some_call) = is_some_or_ok_call(self, cx, expr);
129 emit_lint(cx, &ok_some_call);
134 extract_msrv_attr!(LateContext);
137 fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) {
138 let (entire_expr, inner_expr) = match expr {
139 SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner),
142 utils::span_lint_and_sugg(
144 NEEDLESS_QUESTION_MARK,
146 "question mark operator is useless here",
148 format!("{}", utils::snippet(cx, inner_expr.span, r#""...""#)),
149 Applicability::MachineApplicable,
153 fn is_some_or_ok_call<'a>(
154 nqml: &NeedlessQuestionMark,
155 cx: &'a LateContext<'_>,
157 ) -> Option<SomeOkCall<'a>> {
159 // Check outer expression matches CALL_IDENT(ARGUMENT) format
160 if let ExprKind::Call(path, args) = &expr.kind;
161 if let ExprKind::Path(QPath::Resolved(None, path)) = &path.kind;
162 if utils::is_some_ctor(cx, path.res) || utils::is_ok_ctor(cx, path.res);
164 // Extract inner expression from ARGUMENT
165 if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind;
166 if let ExprKind::Call(called, args) = &inner_expr_with_q.kind;
169 if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind;
171 // Extract inner expr type from match argument generated by
172 // question mark operator
173 let inner_expr = &args[0];
175 let inner_ty = cx.typeck_results().expr_ty(inner_expr);
176 let outer_ty = cx.typeck_results().expr_ty(expr);
178 // Check if outer and inner type are Option
179 let outer_is_some = is_type_diagnostic_item(cx, outer_ty, sym::option_type);
180 let inner_is_some = is_type_diagnostic_item(cx, inner_ty, sym::option_type);
182 // Check for Option MSRV
183 let meets_option_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_OPTION_MSRV);
184 if outer_is_some && inner_is_some && meets_option_msrv {
185 return Some(SomeOkCall::SomeCall(expr, inner_expr));
188 // Check if outer and inner type are Result
189 let outer_is_result = is_type_diagnostic_item(cx, outer_ty, sym::result_type);
190 let inner_is_result = is_type_diagnostic_item(cx, inner_ty, sym::result_type);
192 // Additional check: if the error type of the Result can be converted
193 // via the From trait, then don't match
194 let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr);
196 // Must meet Result MSRV
197 let meets_result_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_RESULT_MSRV);
198 if outer_is_result && inner_is_result && does_not_call_from && meets_result_msrv {
199 return Some(SomeOkCall::OkCall(expr, inner_expr));
207 fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool {
208 return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr);