Skip to content

Commit

Permalink
Find offending expressions from Issue rust-lang#6410
Browse files Browse the repository at this point in the history
  • Loading branch information
bengsparks committed Dec 26, 2020
1 parent 02399f4 commit 2f4c16a
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,7 @@ Released 2018-09-13
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
Expand Down
5 changes: 5 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ mod needless_borrow;
mod needless_borrowed_ref;
mod needless_continue;
mod needless_pass_by_value;
mod needless_question_mark;
mod needless_update;
mod neg_cmp_op_on_partial_ord;
mod neg_multiply;
Expand Down Expand Up @@ -799,6 +800,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
&needless_continue::NEEDLESS_CONTINUE,
&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
&needless_question_mark::NEEDLESS_QUESTION_MARK,
&needless_update::NEEDLESS_UPDATE,
&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
&neg_multiply::NEG_MULTIPLY,
Expand Down Expand Up @@ -1187,6 +1189,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box future_not_send::FutureNotSend);
store.register_late_pass(|| box if_let_mutex::IfLetMutex);
store.register_late_pass(|| box mut_mutex_lock::MutMutexLock);
store.register_late_pass(|| box needless_question_mark::NeedlessQuestionMark);
store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
store.register_late_pass(|| box manual_async_fn::ManualAsyncFn);
store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero);
Expand Down Expand Up @@ -1545,6 +1548,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&needless_bool::BOOL_COMPARISON),
LintId::of(&needless_bool::NEEDLESS_BOOL),
LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(&needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(&needless_update::NEEDLESS_UPDATE),
LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
LintId::of(&neg_multiply::NEG_MULTIPLY),
Expand Down Expand Up @@ -1724,6 +1728,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&misc_early::REDUNDANT_PATTERN),
LintId::of(&mut_mutex_lock::MUT_MUTEX_LOCK),
LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED),
LintId::of(&needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(&neg_multiply::NEG_MULTIPLY),
LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT),
LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
Expand Down
194 changes: 194 additions & 0 deletions clippy_lints/src/needless_question_mark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports, unused_variables))]
use rustc_errors::Applicability;
use rustc_hir::def::*;
use rustc_hir::intravisit::FnKind;
use rustc_hir::*;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;

use rustc_span::sym;

use crate::utils;
use if_chain::if_chain;

use std::iter;

declare_clippy_lint! {
/// **What it does:**
/// Suggest
/// ```rust,ignore
/// option.and_then(|value| value.inner_option)
/// ```
/// instead of
/// ```rust,ignore
/// option.and_then(|value| Some(value.inner_option?))
/// ```
///
/// **Why is this bad?** There's no reason to take an option, wrap it in another option, and use ? to short-circuit. and_then will work fine without it, and it's needlessly difficult to read.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// // struct Outer {
/// // inner: Option<String>
/// // }
/// //
/// // let a = Some(Outer { inner: Some("hello".into()) });
/// // a.and_then(|a| Some(a.inner?))
/// ```
/// Use instead:
/// ```rust
/// // struct Outer {
/// // inner: Option<String>
/// // }
/// //
/// // let a = Some(Outer { inner: Some("hello".into()) });
/// // a.and_then(|a| a.inner)
/// ```
pub NEEDLESS_QUESTION_MARK,
complexity,
"Suggest option.and_then(|value| value.inner_option) instead of option.and_then(|value| Some(value.inner_option?)). The same goes for Result<T, E>"
}

declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);

impl LateLintPass<'_> for NeedlessQuestionMark {
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
check_body_free(cx, body);
}
}

#[derive(Debug)]
enum SomeOkCall<'a> {
SomeCall(&'a Expr<'a>, &'a Expr<'a>),
OkCall(&'a Expr<'a>, &'a Expr<'a>),
}

fn check_body_free<'a>(cx: &'a LateContext<'_>, body: &'a Body<'_>) /* -> Vec<SomeOkCall<'a>> */
{
/*
* The question mark operator is compatible with both Result<T, E> and Option<T>,
* from Rust 1.13 and 1.22 respectively.
*
* This can be constrained like this:
* https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
*/

/*
* What do we match:
* Expressions that look like this:
* Some(option?), Ok(result?)
*
* Where do we match:
* Last expression of a body or a return statement
*/

// Get last expression and expression in return statements in bodies
// TODO: what about one liners, like in closures?
let candidate_exprs: Vec<&Expr<'_>> = match body.value.kind {
ExprKind::Block(Block { stmts, expr, .. }, _) => {
let exprs_from_stmts = stmts.into_iter().filter_map(|stmt| stmt.extract_expr());
let mut return_expr = expr.and_then(|expr| expr.extract_expr()).into_iter();

exprs_from_stmts.chain(&mut return_expr).collect()
},

_ => iter::empty().collect(),
};

// We need to match Some(some_instance?) or Ok(result_instance?)
let candidate_exprs: Vec<SomeOkCall<'_>> = candidate_exprs
.into_iter()
.filter_map(|expr| is_some_or_ok_call(cx, expr))
.collect();

// candidate_exprs.iter().map(|expr|)

for expr in candidate_exprs.iter() {
let (entire_expr, inner_expr) = match expr {
SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner),
};

let help_msg = match expr {
SomeOkCall::OkCall(_, _) => "Remove the Ok call and the question mark",
SomeOkCall::SomeCall(_, _) => "Remove the Some call and the question mark",
};

// utils::span_lint_and_sugg(
// cx, NEEDLESS_QUESTION_MARK, entire_expr.span, "Question mark operator is useless
// here", help_msg, format!("{}", inner_expr.), Applicability::MachineApplicable
// );
}
}

fn is_some_or_ok_call<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<SomeOkCall<'a>> {
if_chain! {
if let ExprKind::Call(path, args) = &expr.kind;
if let ExprKind::Path(qpath) = &path.kind;
if let QPath::Resolved(_, Path { span, segments, .. }) = qpath;
if segments.len() == 1 && args.len() == 1;

if utils::is_try(&args[0]).is_some();
if let ExprKind::Match(inner_expr, _, _) = &args[0].kind;
then {
// Extract inner expr type from match argument generated by
// question mark operator
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);

if outer_is_some && inner_is_some {
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);

// TODO: Is there a better way of matching the contructor's name?
// sym! delivers "Ok", not Ok
if outer_is_result && inner_is_result && segments[0].ident.name.to_ident_string() == "Ok" {
return Some(SomeOkCall::OkCall(expr, inner_expr));
}
}
}

return Option::None;
}

trait ExtractExpr {
fn extract_expr(self: &Self) -> Option<&Expr<'_>>;
}

impl ExtractExpr for Expr<'_> {
fn extract_expr(self: &Self) -> Option<&Expr<'_>> {
// NOTE:
return match self.kind {
// We only concern ourself with return statements
ExprKind::Ret(e) if e.map(|e| utils::contains_return(e)).unwrap_or(false) => e,

// Lookup body from BodyId.hir_id
// ExprKind::Closure(_, _, BodyId { hir_id }, ..) => {
// let {} utils::get_enclosing_block(cx, hir_id).and_then(|block| {
// block.
// })
// },
_ => None,
};
}
}

impl ExtractExpr for Stmt<'_> {
fn extract_expr(self: &Self) -> Option<&Expr<'_>> {
return match self.kind {
StmtKind::Semi(e) => e.extract_expr(),
_ => None,
};
}
}
27 changes: 27 additions & 0 deletions tests/ui/needless_question_mark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#![warn(clippy::needless_question_mark)]
#![allow(clippy::needless_return)]
struct TO {
magic: Option<usize>,
}

struct TR {
magic: Result<usize, bool>
}

// fn bad1(to: TO) -> Option<usize> {
// Some(to.magic?)
// }

// fn bad2(tr: Result<TR, bool>) -> Result<usize, bool> {
// return tr.and_then(|value| {
// Ok(value.magic?)
// });
// }

fn bad3(tr: TR) -> Result<usize, bool> {
return Ok(tr.magic?);
}

fn main() {

}

0 comments on commit 2f4c16a

Please sign in to comment.