233 lines
7.4 KiB
Rust
233 lines
7.4 KiB
Rust
|
use rustc_errors::Applicability;
|
||
|
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||
|
use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
|
||
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||
|
use rustc_middle::ty::DefIdTree;
|
||
|
use rustc_semver::RustcVersion;
|
||
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||
|
use rustc_span::sym;
|
||
|
|
||
|
use crate::utils;
|
||
|
use if_chain::if_chain;
|
||
|
|
||
|
declare_clippy_lint! {
|
||
|
/// **What it does:**
|
||
|
/// Suggests alternatives for useless applications of `?` in terminating expressions
|
||
|
///
|
||
|
/// **Why is this bad?** There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
|
||
|
///
|
||
|
/// **Known problems:** None.
|
||
|
///
|
||
|
/// **Example:**
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// struct TO {
|
||
|
/// magic: Option<usize>,
|
||
|
/// }
|
||
|
///
|
||
|
/// fn f(to: TO) -> Option<usize> {
|
||
|
/// Some(to.magic?)
|
||
|
/// }
|
||
|
///
|
||
|
/// struct TR {
|
||
|
/// magic: Result<usize, bool>,
|
||
|
/// }
|
||
|
///
|
||
|
/// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
|
||
|
/// tr.and_then(|t| Ok(t.magic?))
|
||
|
/// }
|
||
|
///
|
||
|
/// ```
|
||
|
/// Use instead:
|
||
|
/// ```rust
|
||
|
/// struct TO {
|
||
|
/// magic: Option<usize>,
|
||
|
/// }
|
||
|
///
|
||
|
/// fn f(to: TO) -> Option<usize> {
|
||
|
/// to.magic
|
||
|
/// }
|
||
|
///
|
||
|
/// struct TR {
|
||
|
/// magic: Result<usize, bool>,
|
||
|
/// }
|
||
|
///
|
||
|
/// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
|
||
|
/// tr.and_then(|t| t.magic)
|
||
|
/// }
|
||
|
/// ```
|
||
|
pub NEEDLESS_QUESTION_MARK,
|
||
|
complexity,
|
||
|
"Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result<T, E>`."
|
||
|
}
|
||
|
|
||
|
const NEEDLESS_QUESTION_MARK_RESULT_MSRV: RustcVersion = RustcVersion::new(1, 13, 0);
|
||
|
const NEEDLESS_QUESTION_MARK_OPTION_MSRV: RustcVersion = RustcVersion::new(1, 22, 0);
|
||
|
|
||
|
pub struct NeedlessQuestionMark {
|
||
|
msrv: Option<RustcVersion>,
|
||
|
}
|
||
|
|
||
|
impl NeedlessQuestionMark {
|
||
|
#[must_use]
|
||
|
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
||
|
Self { msrv }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
enum SomeOkCall<'a> {
|
||
|
SomeCall(&'a Expr<'a>, &'a Expr<'a>),
|
||
|
OkCall(&'a Expr<'a>, &'a Expr<'a>),
|
||
|
}
|
||
|
|
||
|
impl LateLintPass<'_> for NeedlessQuestionMark {
|
||
|
/*
|
||
|
* The question mark operator is compatible with both Result<T, E> and Option<T>,
|
||
|
* from Rust 1.13 and 1.22 respectively.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* What do we match:
|
||
|
* Expressions that look like this:
|
||
|
* Some(option?), Ok(result?)
|
||
|
*
|
||
|
* Where do we match:
|
||
|
* Last expression of a body
|
||
|
* Return statement
|
||
|
* A body's value (single line closure)
|
||
|
*
|
||
|
* What do we not match:
|
||
|
* Implicit calls to `from(..)` on the error value
|
||
|
*/
|
||
|
|
||
|
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
|
||
|
let e = match &expr.kind {
|
||
|
ExprKind::Ret(Some(e)) => e,
|
||
|
_ => return,
|
||
|
};
|
||
|
|
||
|
if let Some(ok_some_call) = is_some_or_ok_call(self, cx, e) {
|
||
|
emit_lint(cx, &ok_some_call);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
|
||
|
// Function / Closure block
|
||
|
let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind {
|
||
|
block.expr
|
||
|
} else {
|
||
|
// Single line closure
|
||
|
Some(&body.value)
|
||
|
};
|
||
|
|
||
|
if_chain! {
|
||
|
if let Some(expr) = expr_opt;
|
||
|
if let Some(ok_some_call) = is_some_or_ok_call(self, cx, expr);
|
||
|
then {
|
||
|
emit_lint(cx, &ok_some_call);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
extract_msrv_attr!(LateContext);
|
||
|
}
|
||
|
|
||
|
fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) {
|
||
|
let (entire_expr, inner_expr) = match expr {
|
||
|
SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner),
|
||
|
};
|
||
|
|
||
|
utils::span_lint_and_sugg(
|
||
|
cx,
|
||
|
NEEDLESS_QUESTION_MARK,
|
||
|
entire_expr.span,
|
||
|
"Question mark operator is useless here",
|
||
|
"try",
|
||
|
format!("{}", utils::snippet(cx, inner_expr.span, r#""...""#)),
|
||
|
Applicability::MachineApplicable,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
fn is_some_or_ok_call<'a>(
|
||
|
nqml: &NeedlessQuestionMark,
|
||
|
cx: &'a LateContext<'_>,
|
||
|
expr: &'a Expr<'_>,
|
||
|
) -> Option<SomeOkCall<'a>> {
|
||
|
if_chain! {
|
||
|
// Check outer expression matches CALL_IDENT(ARGUMENT) format
|
||
|
if let ExprKind::Call(path, args) = &expr.kind;
|
||
|
if let ExprKind::Path(QPath::Resolved(None, path)) = &path.kind;
|
||
|
if is_some_ctor(cx, path.res) || is_ok_ctor(cx, path.res);
|
||
|
|
||
|
// Extract inner expression from ARGUMENT
|
||
|
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind;
|
||
|
if let ExprKind::Call(called, args) = &inner_expr_with_q.kind;
|
||
|
if args.len() == 1;
|
||
|
|
||
|
if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind;
|
||
|
then {
|
||
|
// Extract inner expr type from match argument generated by
|
||
|
// question mark operator
|
||
|
let inner_expr = &args[0];
|
||
|
|
||
|
let inner_ty = cx.typeck_results().expr_ty(inner_expr);
|
||
|
let outer_ty = cx.typeck_results().expr_ty(expr);
|
||
|
|
||
|
// Check if outer and inner type are Option
|
||
|
let outer_is_some = utils::is_type_diagnostic_item(cx, outer_ty, sym::option_type);
|
||
|
let inner_is_some = utils::is_type_diagnostic_item(cx, inner_ty, sym::option_type);
|
||
|
|
||
|
// Check for Option MSRV
|
||
|
let meets_option_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_OPTION_MSRV);
|
||
|
if outer_is_some && inner_is_some && meets_option_msrv {
|
||
|
return Some(SomeOkCall::SomeCall(expr, inner_expr));
|
||
|
}
|
||
|
|
||
|
// Check if outer and inner type are Result
|
||
|
let outer_is_result = utils::is_type_diagnostic_item(cx, outer_ty, sym::result_type);
|
||
|
let inner_is_result = utils::is_type_diagnostic_item(cx, inner_ty, sym::result_type);
|
||
|
|
||
|
// Additional check: if the error type of the Result can be converted
|
||
|
// via the From trait, then don't match
|
||
|
let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr);
|
||
|
|
||
|
// Must meet Result MSRV
|
||
|
let meets_result_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_RESULT_MSRV);
|
||
|
if outer_is_result && inner_is_result && does_not_call_from && meets_result_msrv {
|
||
|
return Some(SomeOkCall::OkCall(expr, inner_expr));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
None
|
||
|
}
|
||
|
|
||
|
fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool {
|
||
|
return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr);
|
||
|
}
|
||
|
|
||
|
fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool {
|
||
|
if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() {
|
||
|
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
|
||
|
if let Some(variant_id) = cx.tcx.parent(id) {
|
||
|
return variant_id == ok_id;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
false
|
||
|
}
|
||
|
|
||
|
fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool {
|
||
|
if let Some(some_id) = cx.tcx.lang_items().option_some_variant() {
|
||
|
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
|
||
|
if let Some(variant_id) = cx.tcx.parent(id) {
|
||
|
return variant_id == some_id;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
false
|
||
|
}
|