From 111b9023dad65721300a39c3cf337f6bfb96d5d3 Mon Sep 17 00:00:00 2001 From: Tim Nielens Date: Tue, 27 Oct 2020 01:47:40 +0100 Subject: [PATCH 1/2] add manual_ok_or lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 4 ++ clippy_lints/src/manual_ok_or.rs | 97 ++++++++++++++++++++++++++++++++ src/lintlist/mod.rs | 7 +++ tests/ui/manual_ok_or.fixed | 40 +++++++++++++ tests/ui/manual_ok_or.rs | 44 +++++++++++++++ tests/ui/manual_ok_or.stderr | 41 ++++++++++++++ 7 files changed, 234 insertions(+) create mode 100644 clippy_lints/src/manual_ok_or.rs create mode 100644 tests/ui/manual_ok_or.fixed create mode 100644 tests/ui/manual_ok_or.rs create mode 100644 tests/ui/manual_ok_or.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 25f3b5da198..cd884e0665d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1795,6 +1795,7 @@ Released 2018-09-13 [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive +[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3be8bc0e36d..9b6f8e73454 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -233,6 +233,7 @@ macro_rules! declare_clippy_lint { mod main_recursion; mod manual_async_fn; mod manual_non_exhaustive; +mod manual_ok_or; mod manual_strip; mod manual_unwrap_or; mod map_clone; @@ -645,6 +646,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &main_recursion::MAIN_RECURSION, &manual_async_fn::MANUAL_ASYNC_FN, &manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, + &manual_ok_or::MANUAL_OK_OR, &manual_strip::MANUAL_STRIP, &manual_unwrap_or::MANUAL_UNWRAP_OR, &map_clone::MAP_CLONE, @@ -1140,6 +1142,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box unwrap_in_result::UnwrapInResult); store.register_late_pass(|| box self_assignment::SelfAssignment); store.register_late_pass(|| box manual_unwrap_or::ManualUnwrapOr); + store.register_late_pass(|| box manual_ok_or::ManualOkOr); store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs); store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync); store.register_late_pass(|| box manual_strip::ManualStrip); @@ -1229,6 +1232,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP), LintId::of(&loops::EXPLICIT_ITER_LOOP), LintId::of(¯o_use::MACRO_USE_IMPORTS), + LintId::of(&manual_ok_or::MANUAL_OK_OR), LintId::of(&map_err_ignore::MAP_ERR_IGNORE), LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs new file mode 100644 index 00000000000..38298eb813a --- /dev/null +++ b/clippy_lints/src/manual_ok_or.rs @@ -0,0 +1,97 @@ +use crate::utils; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def, Expr, ExprKind, PatKind, QPath}; +use rustc_lint::LintContext; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** + /// Finds patterns that reimplement `Option::ok_or`. + /// + /// **Why is this bad?** + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// let foo: Option = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// + /// let foo: Option = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// ``` + /// + /// Use instead: + /// ```rust + /// let foo: Option = None; + /// foo.ok_or("error"); + /// ``` + pub MANUAL_OK_OR, + pedantic, + "finds patterns that can be encoded more concisely with `Option::ok_or`" +} + +declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]); + +impl LateLintPass<'_> for ManualOkOr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) { + if in_external_macro(cx.sess(), scrutinee.span) { + return; + } + + if_chain! { + if let ExprKind::MethodCall(method_segment, _, args, _) = scrutinee.kind; + if method_segment.ident.name == sym!(map_or); + if args.len() == 3; + let method_receiver = &args[0]; + let ty = cx.typeck_results().expr_ty(method_receiver); + if utils::is_type_diagnostic_item(cx, ty, sym!(option_type)); + let or_expr = &args[1]; + if is_ok_wrapping(cx, &args[2]); + if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind; + if utils::match_qpath(err_path, &utils::paths::RESULT_ERR); + if let Some(method_receiver_snippet) = utils::snippet_opt(cx, method_receiver.span); + if let Some(err_arg_snippet) = utils::snippet_opt(cx, err_arg.span); + if let Some(indent) = utils::indent_of(cx, scrutinee.span); + then { + let reindented_err_arg_snippet = + utils::reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); + utils::span_lint_and_sugg( + cx, + MANUAL_OK_OR, + scrutinee.span, + "this pattern reimplements `Option::ok_or`", + "replace with", + format!( + "{}.ok_or({})", + method_receiver_snippet, + reindented_err_arg_snippet + ), + Applicability::MachineApplicable, + ); + } + } + } +} + +fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { + if let ExprKind::Path(ref qpath) = map_expr.kind { + if utils::match_qpath(qpath, &utils::paths::RESULT_OK) { + return true; + } + } + if_chain! { + if let ExprKind::Closure(_, _, body_id, ..) = map_expr.kind; + let body = cx.tcx.hir().body(body_id); + if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; + if utils::match_qpath(ok_path, &utils::paths::RESULT_OK); + if let ExprKind::Path(QPath::Resolved(_, ok_arg_path)) = ok_arg.kind; + if let def::Res::Local(ok_arg_path_id) = ok_arg_path.res; + then { param_id == ok_arg_path_id } else { false } + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 6272ce45efb..f438da00720 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1173,6 +1173,13 @@ deprecation: None, module: "manual_non_exhaustive", }, + Lint { + name: "manual_ok_or", + group: "pedantic", + desc: "finds patterns that can be encoded more concisely with `Option::ok_or`", + deprecation: None, + module: "manual_ok_or", + }, Lint { name: "manual_range_contains", group: "style", diff --git a/tests/ui/manual_ok_or.fixed b/tests/ui/manual_ok_or.fixed new file mode 100644 index 00000000000..b42e94bd727 --- /dev/null +++ b/tests/ui/manual_ok_or.fixed @@ -0,0 +1,40 @@ +// run-rustfix +#![warn(clippy::manual_ok_or)] +#![allow(clippy::blacklisted_name)] +#![allow(clippy::redundant_closure)] +#![allow(dead_code)] +#![allow(unused_must_use)] + +fn main() { + // basic case + let foo: Option = None; + foo.ok_or("error"); + + // eta expansion case + foo.ok_or("error"); + + // turbo fish syntax + None::.ok_or("error"); + + // multiline case + #[rustfmt::skip] + foo.ok_or(&format!( + "{}{}{}{}{}{}{}", + "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")); + + // not applicable, closure isn't direct `Ok` wrapping + foo.map_or(Err("error"), |v| Ok(v + 1)); + + // not applicable, or side isn't `Result::Err` + foo.map_or(Ok::(1), |v| Ok(v)); + + // not applicatble, expr is not a `Result` value + foo.map_or(42, |v| v); + + // TODO patterns not covered yet + match foo { + Some(v) => Ok(v), + None => Err("error"), + }; + foo.map_or_else(|| Err("error"), |v| Ok(v)); +} diff --git a/tests/ui/manual_ok_or.rs b/tests/ui/manual_ok_or.rs new file mode 100644 index 00000000000..e5a6056fbf5 --- /dev/null +++ b/tests/ui/manual_ok_or.rs @@ -0,0 +1,44 @@ +// run-rustfix +#![warn(clippy::manual_ok_or)] +#![allow(clippy::blacklisted_name)] +#![allow(clippy::redundant_closure)] +#![allow(dead_code)] +#![allow(unused_must_use)] + +fn main() { + // basic case + let foo: Option = None; + foo.map_or(Err("error"), |v| Ok(v)); + + // eta expansion case + foo.map_or(Err("error"), Ok); + + // turbo fish syntax + None::.map_or(Err("error"), |v| Ok(v)); + + // multiline case + #[rustfmt::skip] + foo.map_or(Err::( + &format!( + "{}{}{}{}{}{}{}", + "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer") + ), + |v| Ok(v), + ); + + // not applicable, closure isn't direct `Ok` wrapping + foo.map_or(Err("error"), |v| Ok(v + 1)); + + // not applicable, or side isn't `Result::Err` + foo.map_or(Ok::(1), |v| Ok(v)); + + // not applicatble, expr is not a `Result` value + foo.map_or(42, |v| v); + + // TODO patterns not covered yet + match foo { + Some(v) => Ok(v), + None => Err("error"), + }; + foo.map_or_else(|| Err("error"), |v| Ok(v)); +} diff --git a/tests/ui/manual_ok_or.stderr b/tests/ui/manual_ok_or.stderr new file mode 100644 index 00000000000..8ea10ac5436 --- /dev/null +++ b/tests/ui/manual_ok_or.stderr @@ -0,0 +1,41 @@ +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:11:5 + | +LL | foo.map_or(Err("error"), |v| Ok(v)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")` + | + = note: `-D clippy::manual-ok-or` implied by `-D warnings` + +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:14:5 + | +LL | foo.map_or(Err("error"), Ok); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")` + +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:17:5 + | +LL | None::.map_or(Err("error"), |v| Ok(v)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `None::.ok_or("error")` + +error: this pattern reimplements `Option::ok_or` + --> $DIR/manual_ok_or.rs:21:5 + | +LL | / foo.map_or(Err::( +LL | | &format!( +LL | | "{}{}{}{}{}{}{}", +LL | | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer") +LL | | ), +LL | | |v| Ok(v), +LL | | ); + | |_____^ + | +help: replace with + | +LL | foo.ok_or(&format!( +LL | "{}{}{}{}{}{}{}", +LL | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")); + | + +error: aborting due to 4 previous errors + From d780f61d7b3febdf0808dfef9abe80e95f2d46f8 Mon Sep 17 00:00:00 2001 From: Tim Nielens Date: Thu, 29 Oct 2020 19:08:54 +0100 Subject: [PATCH 2/2] add manual_ok_or / pr remarks --- clippy_lints/src/manual_ok_or.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs index 38298eb813a..c99d2e35b94 100644 --- a/clippy_lints/src/manual_ok_or.rs +++ b/clippy_lints/src/manual_ok_or.rs @@ -1,4 +1,6 @@ -use crate::utils; +use crate::utils::{ + indent_of, is_type_diagnostic_item, match_qpath, paths, reindent_multiline, snippet_opt, span_lint_and_sugg, +}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{def, Expr, ExprKind, PatKind, QPath}; @@ -49,18 +51,18 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) { if args.len() == 3; let method_receiver = &args[0]; let ty = cx.typeck_results().expr_ty(method_receiver); - if utils::is_type_diagnostic_item(cx, ty, sym!(option_type)); + if is_type_diagnostic_item(cx, ty, sym!(option_type)); let or_expr = &args[1]; if is_ok_wrapping(cx, &args[2]); if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind; - if utils::match_qpath(err_path, &utils::paths::RESULT_ERR); - if let Some(method_receiver_snippet) = utils::snippet_opt(cx, method_receiver.span); - if let Some(err_arg_snippet) = utils::snippet_opt(cx, err_arg.span); - if let Some(indent) = utils::indent_of(cx, scrutinee.span); + if match_qpath(err_path, &paths::RESULT_ERR); + if let Some(method_receiver_snippet) = snippet_opt(cx, method_receiver.span); + if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); + if let Some(indent) = indent_of(cx, scrutinee.span); then { let reindented_err_arg_snippet = - utils::reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); - utils::span_lint_and_sugg( + reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); + span_lint_and_sugg( cx, MANUAL_OK_OR, scrutinee.span, @@ -80,7 +82,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) { fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { if let ExprKind::Path(ref qpath) = map_expr.kind { - if utils::match_qpath(qpath, &utils::paths::RESULT_OK) { + if match_qpath(qpath, &paths::RESULT_OK) { return true; } } @@ -89,7 +91,7 @@ fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { let body = cx.tcx.hir().body(body_id); if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; - if utils::match_qpath(ok_path, &utils::paths::RESULT_OK); + if match_qpath(ok_path, &paths::RESULT_OK); if let ExprKind::Path(QPath::Resolved(_, ok_arg_path)) = ok_arg.kind; if let def::Res::Local(ok_arg_path_id) = ok_arg_path.res; then { param_id == ok_arg_path_id } else { false }