From f072ded3bf6286668ff8eade5b58e471dbe66f2a Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Fri, 1 May 2020 01:44:17 +0200 Subject: Implement the manual_non_exhaustive lint --- src/lintlist/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 72675c25175..c5360002fa0 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1088,6 +1088,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "loops", }, + Lint { + name: "manual_non_exhaustive", + group: "style", + desc: "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]", + deprecation: None, + module: "manual_non_exhaustive", + }, Lint { name: "manual_saturating_arithmetic", group: "style", -- cgit 1.4.1-3-g733a5 From 10e3f9bdb854e3cabbc4fda69ed713388344d524 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Fri, 1 May 2020 23:02:31 +0200 Subject: Move match_on_vec_items to pedantic --- clippy_lints/src/lib.rs | 3 +-- clippy_lints/src/match_on_vec_items.rs | 2 +- src/lintlist/mod.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c995be5edc2..06e21a5272e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1138,6 +1138,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(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), @@ -1283,7 +1284,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&map_clone::MAP_CLONE), LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN), - LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), LintId::of(&matches::MATCH_AS_REF), LintId::of(&matches::MATCH_OVERLAPPING_ARM), @@ -1647,7 +1647,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::NEVER_LOOP), LintId::of(&loops::REVERSE_RANGE_LOOP), LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), - LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), LintId::of(&methods::CLONE_DOUBLE_REF), diff --git a/clippy_lints/src/match_on_vec_items.rs b/clippy_lints/src/match_on_vec_items.rs index 4071406cc84..421571d2f2f 100644 --- a/clippy_lints/src/match_on_vec_items.rs +++ b/clippy_lints/src/match_on_vec_items.rs @@ -38,7 +38,7 @@ declare_clippy_lint! { /// } /// ``` pub MATCH_ON_VEC_ITEMS, - correctness, + pedantic, "matching on vector elements can panic" } diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 72675c25175..f337db72ba0 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1146,7 +1146,7 @@ pub static ref ALL_LINTS: Vec = vec![ }, Lint { name: "match_on_vec_items", - group: "correctness", + group: "pedantic", desc: "matching on vector elements can panic", deprecation: None, module: "match_on_vec_items", -- cgit 1.4.1-3-g733a5 From 3b58d66b22fe9107a78b99c267c71322276aa15a Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Thu, 7 May 2020 21:41:23 +0200 Subject: Add the manual_async_fn lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 5 ++ clippy_lints/src/manual_async_fn.rs | 160 ++++++++++++++++++++++++++++++++++++ clippy_lints/src/utils/paths.rs | 3 + src/lintlist/mod.rs | 7 ++ tests/ui/future_not_send.rs | 1 + tests/ui/future_not_send.stderr | 6 +- tests/ui/manual_async_fn.fixed | 55 +++++++++++++ tests/ui/manual_async_fn.rs | 67 +++++++++++++++ tests/ui/manual_async_fn.stderr | 93 +++++++++++++++++++++ 10 files changed, 395 insertions(+), 3 deletions(-) create mode 100644 clippy_lints/src/manual_async_fn.rs create mode 100644 tests/ui/manual_async_fn.fixed create mode 100644 tests/ui/manual_async_fn.rs create mode 100644 tests/ui/manual_async_fn.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index facf363e371..8457d6ad05c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1422,6 +1422,7 @@ Released 2018-09-13 [`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal [`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports [`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion +[`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_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 97785cba883..fb2e9932b8e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -247,6 +247,7 @@ mod literal_representation; mod loops; mod macro_use; mod main_recursion; +mod manual_async_fn; mod manual_non_exhaustive; mod map_clone; mod map_unit_fn; @@ -629,6 +630,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &loops::WHILE_LET_ON_ITERATOR, ¯o_use::MACRO_USE_IMPORTS, &main_recursion::MAIN_RECURSION, + &manual_async_fn::MANUAL_ASYNC_FN, &manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, &map_clone::MAP_CLONE, &map_unit_fn::OPTION_MAP_UNIT_FN, @@ -1067,6 +1069,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box if_let_mutex::IfLetMutex); store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive); + store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1284,6 +1287,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::WHILE_LET_LOOP), LintId::of(&loops::WHILE_LET_ON_ITERATOR), LintId::of(&main_recursion::MAIN_RECURSION), + LintId::of(&manual_async_fn::MANUAL_ASYNC_FN), LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), LintId::of(&map_clone::MAP_CLONE), LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), @@ -1478,6 +1482,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::NEEDLESS_RANGE_LOOP), LintId::of(&loops::WHILE_LET_ON_ITERATOR), LintId::of(&main_recursion::MAIN_RECURSION), + LintId::of(&manual_async_fn::MANUAL_ASYNC_FN), LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), LintId::of(&map_clone::MAP_CLONE), LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs new file mode 100644 index 00000000000..ab8829abbf2 --- /dev/null +++ b/clippy_lints/src/manual_async_fn.rs @@ -0,0 +1,160 @@ +use crate::utils::paths::{FUTURE_CORE, FUTURE_FROM_GENERATOR, FUTURE_STD}; +use crate::utils::{match_function_call, match_path, snippet_block, snippet_opt, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericBound, HirId, IsAsync, + ItemKind, TraitRef, Ty, TyKind, TypeBindingKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** It checks for manual implementations of `async` functions. + /// + /// **Why is this bad?** It's more idiomatic to use the dedicated syntax. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::future::Future; + /// + /// fn foo() -> Future { async { 42 } } + /// ``` + /// Use instead: + /// ```rust + /// use std::future::Future; + /// + /// async fn foo() -> i32 { 42 } + /// ``` + pub MANUAL_ASYNC_FN, + style, + "manual implementations of `async` functions can be simplified using the dedicated syntax" +} + +declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ManualAsyncFn { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + _: HirId, + ) { + if_chain! { + if let Some(header) = kind.header(); + if let IsAsync::NotAsync = header.asyncness; + // Check that this function returns `impl Future` + if let FnRetTy::Return(ret_ty) = decl.output; + if let Some(trait_ref) = future_trait_ref(cx, ret_ty); + if let Some(output) = future_output_ty(trait_ref); + // Check that the body of the function consists of one async block + if let ExprKind::Block(block, _) = body.value.kind; + if block.stmts.is_empty(); + if let Some(closure_body) = desugared_async_block(cx, block); + then { + let header_span = span.with_hi(ret_ty.span.hi()); + + span_lint_and_then( + cx, + MANUAL_ASYNC_FN, + header_span, + "this function can be simplified using async syntax", + |diag| { + if_chain! { + if let Some(header_snip) = snippet_opt(cx, header_span); + if let Some(ret_pos) = header_snip.rfind("->"); + if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output); + then { + let help = format!("make the function `async` and {}", ret_sugg); + diag.span_suggestion( + header_span, + &help, + format!("async {}{}", &header_snip[..ret_pos], ret_snip), + Applicability::MachineApplicable + ); + + let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)); + diag.span_suggestion( + block.span, + "move the body of the async block to the enclosing function", + body_snip.to_string(), + Applicability::MachineApplicable + ); + } + } + }, + ); + } + } + } +} + +fn future_trait_ref<'tcx>(cx: &LateContext<'_, 'tcx>, ty: &'tcx Ty<'tcx>) -> Option<&'tcx TraitRef<'tcx>> { + if_chain! { + if let TyKind::Def(item_id, _) = ty.kind; + let item = cx.tcx.hir().item(item_id.id); + if let ItemKind::OpaqueTy(opaque) = &item.kind; + if opaque.bounds.len() == 1; + if let GenericBound::Trait(poly, _) = &opaque.bounds[0]; + let path = poly.trait_ref.path; + if match_path(&path, &FUTURE_CORE) || match_path(&path, &FUTURE_STD); + then { + return Some(&poly.trait_ref); + } + } + + None +} + +fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> { + if_chain! { + if let Some(segment) = trait_ref.path.segments.last(); + if let Some(args) = segment.args; + if args.bindings.len() == 1; + let binding = &args.bindings[0]; + if binding.ident.as_str() == "Output"; + if let TypeBindingKind::Equality{ty: output} = binding.kind; + then { + return Some(output) + } + } + + None +} + +fn desugared_async_block<'tcx>(cx: &LateContext<'_, 'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { + if_chain! { + if let Some(block_expr) = block.expr; + if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR); + if args.len() == 1; + if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..} = args[0]; + let closure_body = cx.tcx.hir().body(body_id); + if let Some(GeneratorKind::Async(AsyncGeneratorKind::Block)) = closure_body.generator_kind; + then { + return Some(closure_body); + } + } + + None +} + +fn suggested_ret(cx: &LateContext<'_, '_>, output: &Ty<'_>) -> Option<(&'static str, String)> { + match output.kind { + TyKind::Tup(tys) if tys.is_empty() => { + let sugg = "remove the return type"; + Some((sugg, "".into())) + }, + _ => { + let sugg = "return the output of the future directly"; + snippet_opt(cx, output.span).map(|snip| (sugg, format!("-> {}", snip))) + }, + } +} diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index 79ca6845c6f..74040a96c78 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -42,6 +42,9 @@ pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"]; pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"]; pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"]; +pub const FUTURE_CORE: [&str; 3] = ["core", "future", "Future"]; +pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"]; +pub const FUTURE_STD: [&str; 3] = ["std", "future", "Future"]; pub const HASH: [&str; 2] = ["hash", "Hash"]; pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"]; pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index a7685f67211..51d1cb2216a 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1081,6 +1081,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "main_recursion", }, + Lint { + name: "manual_async_fn", + group: "style", + desc: "manual implementations of `async` functions can be simplified using the dedicated syntax", + deprecation: None, + module: "manual_async_fn", + }, Lint { name: "manual_memcpy", group: "perf", diff --git a/tests/ui/future_not_send.rs b/tests/ui/future_not_send.rs index 6d09d71a630..d3a920de4b6 100644 --- a/tests/ui/future_not_send.rs +++ b/tests/ui/future_not_send.rs @@ -41,6 +41,7 @@ impl Dummy { self.private_future().await; } + #[allow(clippy::manual_async_fn)] pub fn public_send(&self) -> impl std::future::Future { async { false } } diff --git a/tests/ui/future_not_send.stderr b/tests/ui/future_not_send.stderr index 3b4968ef0a6..d1863701bfe 100644 --- a/tests/ui/future_not_send.stderr +++ b/tests/ui/future_not_send.stderr @@ -96,13 +96,13 @@ LL | } = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> $DIR/future_not_send.rs:49:37 + --> $DIR/future_not_send.rs:50:37 | LL | async fn generic_future(t: T) -> T | ^ future returned by `generic_future` is not `Send` | note: future is not `Send` as this value is used across an await - --> $DIR/future_not_send.rs:54:5 + --> $DIR/future_not_send.rs:55:5 | LL | let rt = &t; | -- has type `&T` which is not `Send` @@ -114,7 +114,7 @@ LL | } = note: `T` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> $DIR/future_not_send.rs:65:34 + --> $DIR/future_not_send.rs:66:34 | LL | async fn unclear_future(t: T) {} | ^ diff --git a/tests/ui/manual_async_fn.fixed b/tests/ui/manual_async_fn.fixed new file mode 100644 index 00000000000..84c02bba4dc --- /dev/null +++ b/tests/ui/manual_async_fn.fixed @@ -0,0 +1,55 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::manual_async_fn)] +#![allow(unused)] + +use std::future::Future; + +async fn fut() -> i32 { 42 } + +async fn empty_fut() {} + +async fn core_fut() -> i32 { 42 } + +// should be ignored +fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } +} + +// should be ignored +fn not_fut() -> i32 { + 42 +} + +// should be ignored +async fn already_async() -> impl Future { + async { 42 } +} + +struct S {} +impl S { + async fn inh_fut() -> i32 { 42 } + + async fn meth_fut(&self) -> i32 { 42 } + + async fn empty_fut(&self) {} + + // should be ignored + fn not_fut(&self) -> i32 { + 42 + } + + // should be ignored + fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } + } + + // should be ignored + async fn already_async(&self) -> impl Future { + async { 42 } + } +} + +fn main() {} diff --git a/tests/ui/manual_async_fn.rs b/tests/ui/manual_async_fn.rs new file mode 100644 index 00000000000..bd5f9d1cf5f --- /dev/null +++ b/tests/ui/manual_async_fn.rs @@ -0,0 +1,67 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::manual_async_fn)] +#![allow(unused)] + +use std::future::Future; + +fn fut() -> impl Future { + async { 42 } +} + +fn empty_fut() -> impl Future { + async {} +} + +fn core_fut() -> impl core::future::Future { + async move { 42 } +} + +// should be ignored +fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } +} + +// should be ignored +fn not_fut() -> i32 { + 42 +} + +// should be ignored +async fn already_async() -> impl Future { + async { 42 } +} + +struct S {} +impl S { + fn inh_fut() -> impl Future { + async { 42 } + } + + fn meth_fut(&self) -> impl Future { + async { 42 } + } + + fn empty_fut(&self) -> impl Future { + async {} + } + + // should be ignored + fn not_fut(&self) -> i32 { + 42 + } + + // should be ignored + fn has_other_stmts() -> impl core::future::Future { + let _ = 42; + async move { 42 } + } + + // should be ignored + async fn already_async(&self) -> impl Future { + async { 42 } + } +} + +fn main() {} diff --git a/tests/ui/manual_async_fn.stderr b/tests/ui/manual_async_fn.stderr new file mode 100644 index 00000000000..016fdf95977 --- /dev/null +++ b/tests/ui/manual_async_fn.stderr @@ -0,0 +1,93 @@ +error: this function can be simplified using async syntax + --> $DIR/manual_async_fn.rs:8:1 + | +LL | fn fut() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-async-fn` implied by `-D warnings` +help: make the function `async` and return the output of the future directly + | +LL | async fn fut() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn fut() -> impl Future { 42 } + | ^^^^^^ + +error: this function can be simplified using async syntax + --> $DIR/manual_async_fn.rs:12:1 + | +LL | fn empty_fut() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and remove the return type + | +LL | async fn empty_fut() { + | ^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn empty_fut() -> impl Future {} + | ^^ + +error: this function can be simplified using async syntax + --> $DIR/manual_async_fn.rs:16:1 + | +LL | fn core_fut() -> impl core::future::Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn core_fut() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn core_fut() -> impl core::future::Future { 42 } + | ^^^^^^ + +error: this function can be simplified using async syntax + --> $DIR/manual_async_fn.rs:38:5 + | +LL | fn inh_fut() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn inh_fut() -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn inh_fut() -> impl Future { 42 } + | ^^^^^^ + +error: this function can be simplified using async syntax + --> $DIR/manual_async_fn.rs:42:5 + | +LL | fn meth_fut(&self) -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | async fn meth_fut(&self) -> i32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn meth_fut(&self) -> impl Future { 42 } + | ^^^^^^ + +error: this function can be simplified using async syntax + --> $DIR/manual_async_fn.rs:46:5 + | +LL | fn empty_fut(&self) -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and remove the return type + | +LL | async fn empty_fut(&self) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: move the body of the async block to the enclosing function + | +LL | fn empty_fut(&self) -> impl Future {} + | ^^ + +error: aborting due to 6 previous errors + -- cgit 1.4.1-3-g733a5 From 0f2b1193f9501ffd06f9bf2ea8ab85a4db92f47b Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Mon, 11 May 2020 00:53:31 +0200 Subject: Remove reverse_range_loop lint --- CHANGELOG.md | 1 - clippy_lints/src/lib.rs | 7 +-- clippy_lints/src/loops.rs | 102 +------------------------------------ src/lintlist/mod.rs | 6 +-- tests/ui/for_loop_fixable.fixed | 54 -------------------- tests/ui/for_loop_fixable.rs | 54 -------------------- tests/ui/for_loop_fixable.stderr | 88 ++++++-------------------------- tests/ui/for_loop_unfixable.rs | 18 ------- tests/ui/for_loop_unfixable.stderr | 11 ++-- tests/ui/manual_memcpy.rs | 2 +- 10 files changed, 32 insertions(+), 311 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b277fbd31..b25ef049356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1545,7 +1545,6 @@ Released 2018-09-13 [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn [`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used -[`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e1cb10a4651..0c4daeb731f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -624,7 +624,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &loops::NEEDLESS_COLLECT, &loops::NEEDLESS_RANGE_LOOP, &loops::NEVER_LOOP, - &loops::REVERSE_RANGE_LOOP, &loops::WHILE_IMMUTABLE_CONDITION, &loops::WHILE_LET_LOOP, &loops::WHILE_LET_ON_ITERATOR, @@ -1284,7 +1283,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::NEEDLESS_COLLECT), LintId::of(&loops::NEEDLESS_RANGE_LOOP), LintId::of(&loops::NEVER_LOOP), - LintId::of(&loops::REVERSE_RANGE_LOOP), LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), LintId::of(&loops::WHILE_LET_LOOP), LintId::of(&loops::WHILE_LET_ON_ITERATOR), @@ -1658,7 +1656,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::FOR_LOOP_OVER_RESULT), LintId::of(&loops::ITER_NEXT_LOOP), LintId::of(&loops::NEVER_LOOP), - LintId::of(&loops::REVERSE_RANGE_LOOP), LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), @@ -1788,6 +1785,10 @@ fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { "unsafe_vector_initialization", "the replacement suggested by this lint had substantially different behavior", ); + store.register_removed( + "reverse_range_loop", + "this lint is now included in reversed_empty_ranges", + ); } /// Register renamed lints. diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index 2bbf4dba614..0bc6b70855b 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -1,4 +1,4 @@ -use crate::consts::{constant, Constant}; +use crate::consts::constant; use crate::reexport::Name; use crate::utils::paths; use crate::utils::usage::{is_unused, mutated_variables}; @@ -8,7 +8,7 @@ use crate::utils::{ multispan_sugg, snippet, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, SpanlessEq, }; -use crate::utils::{is_type_diagnostic_item, qpath_res, same_tys, sext, sugg}; +use crate::utils::{is_type_diagnostic_item, qpath_res, same_tys, sugg}; use if_chain::if_chain; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -270,30 +270,6 @@ declare_clippy_lint! { "collecting an iterator when collect is not needed" } -declare_clippy_lint! { - /// **What it does:** Checks for loops over ranges `x..y` where both `x` and `y` - /// are constant and `x` is greater or equal to `y`, unless the range is - /// reversed or has a negative `.step_by(_)`. - /// - /// **Why is it bad?** Such loops will either be skipped or loop until - /// wrap-around (in debug code, this may `panic!()`). Both options are probably - /// not intended. - /// - /// **Known problems:** The lint cannot catch loops over dynamically defined - /// ranges. Doing this would require simulating all possible inputs and code - /// paths through the program, which would be complex and error-prone. - /// - /// **Example:** - /// ```ignore - /// for x in 5..10 - 5 { - /// .. - /// } // oops, stray `-` - /// ``` - pub REVERSE_RANGE_LOOP, - correctness, - "iteration over an empty range, such as `10..0` or `5..5`" -} - declare_clippy_lint! { /// **What it does:** Checks `for` loops over slices with an explicit counter /// and suggests the use of `.enumerate()`. @@ -463,7 +439,6 @@ declare_lint_pass!(Loops => [ FOR_LOOP_OVER_OPTION, WHILE_LET_LOOP, NEEDLESS_COLLECT, - REVERSE_RANGE_LOOP, EXPLICIT_COUNTER_LOOP, EMPTY_LOOP, WHILE_LET_ON_ITERATOR, @@ -761,7 +736,6 @@ fn check_for_loop<'a, 'tcx>( expr: &'tcx Expr<'_>, ) { check_for_loop_range(cx, pat, arg, body, expr); - check_for_loop_reverse_range(cx, arg, expr); check_for_loop_arg(cx, pat, arg, expr); check_for_loop_explicit_counter(cx, pat, arg, body, expr); check_for_loop_over_map_kv(cx, pat, arg, body, expr); @@ -1248,78 +1222,6 @@ fn is_end_eq_array_len<'tcx>( false } -fn check_for_loop_reverse_range<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arg: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { - // if this for loop is iterating over a two-sided range... - if let Some(higher::Range { - start: Some(start), - end: Some(end), - limits, - }) = higher::range(cx, arg) - { - // ...and both sides are compile-time constant integers... - if let Some((start_idx, _)) = constant(cx, cx.tables, start) { - if let Some((end_idx, _)) = constant(cx, cx.tables, end) { - // ...and the start index is greater than the end index, - // this loop will never run. This is often confusing for developers - // who think that this will iterate from the larger value to the - // smaller value. - let ty = cx.tables.expr_ty(start); - let (sup, eq) = match (start_idx, end_idx) { - (Constant::Int(start_idx), Constant::Int(end_idx)) => ( - match ty.kind { - ty::Int(ity) => sext(cx.tcx, start_idx, ity) > sext(cx.tcx, end_idx, ity), - ty::Uint(_) => start_idx > end_idx, - _ => false, - }, - start_idx == end_idx, - ), - _ => (false, false), - }; - - if sup { - let start_snippet = snippet(cx, start.span, "_"); - let end_snippet = snippet(cx, end.span, "_"); - let dots = if limits == ast::RangeLimits::Closed { - "..=" - } else { - ".." - }; - - span_lint_and_then( - cx, - REVERSE_RANGE_LOOP, - expr.span, - "this range is empty so this for loop will never run", - |diag| { - diag.span_suggestion( - arg.span, - "consider using the following if you are attempting to iterate over this \ - range in reverse", - format!( - "({end}{dots}{start}).rev()", - end = end_snippet, - dots = dots, - start = start_snippet - ), - Applicability::MaybeIncorrect, - ); - }, - ); - } else if eq && limits != ast::RangeLimits::Closed { - // if they are equal, it's also problematic - this loop - // will never run. - span_lint( - cx, - REVERSE_RANGE_LOOP, - expr.span, - "this range is empty so this for loop will never run", - ); - } - } - } - } -} - fn lint_iter_method(cx: &LateContext<'_, '_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { let mut applicability = Applicability::MachineApplicable; let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 51d1cb2216a..e1a6d4bdd31 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1922,11 +1922,11 @@ pub static ref ALL_LINTS: Vec = vec![ module: "methods", }, Lint { - name: "reverse_range_loop", + name: "reversed_empty_ranges", group: "correctness", - desc: "iteration over an empty range, such as `10..0` or `5..5`", + desc: "reversing the limits of range expressions, resulting in empty ranges", deprecation: None, - module: "loops", + module: "ranges", }, Lint { name: "same_functions_in_if_condition", diff --git a/tests/ui/for_loop_fixable.fixed b/tests/ui/for_loop_fixable.fixed index 5fc84ada9ef..249a88a0b39 100644 --- a/tests/ui/for_loop_fixable.fixed +++ b/tests/ui/for_loop_fixable.fixed @@ -21,7 +21,6 @@ impl Unrelated { clippy::explicit_iter_loop, clippy::explicit_into_iter_loop, clippy::iter_next_loop, - clippy::reverse_range_loop, clippy::for_kv_map )] #[allow( @@ -32,61 +31,8 @@ impl Unrelated { )] #[allow(clippy::many_single_char_names, unused_variables)] fn main() { - const MAX_LEN: usize = 42; let mut vec = vec![1, 2, 3, 4]; - for i in (0..10).rev() { - println!("{}", i); - } - - for i in (0..=10).rev() { - println!("{}", i); - } - - for i in (0..MAX_LEN).rev() { - println!("{}", i); - } - - for i in 5..=5 { - // not an error, this is the range with only one element “5” - println!("{}", i); - } - - for i in 0..10 { - // not an error, the start index is less than the end index - println!("{}", i); - } - - for i in -10..0 { - // not an error - println!("{}", i); - } - - for i in (10..0).map(|x| x * 2) { - // not an error, it can't be known what arbitrary methods do to a range - println!("{}", i); - } - - // testing that the empty range lint folds constants - for i in (5 + 4..10).rev() { - println!("{}", i); - } - - for i in ((3 - 1)..(5 + 2)).rev() { - println!("{}", i); - } - - for i in (2 * 2)..(2 * 3) { - // no error, 4..6 is fine - println!("{}", i); - } - - let x = 42; - for i in x..10 { - // no error, not constant-foldable - println!("{}", i); - } - // See #601 for i in 0..10 { // no error, id_col does not exist outside the loop diff --git a/tests/ui/for_loop_fixable.rs b/tests/ui/for_loop_fixable.rs index 4165b0dc004..306d85a6351 100644 --- a/tests/ui/for_loop_fixable.rs +++ b/tests/ui/for_loop_fixable.rs @@ -21,7 +21,6 @@ impl Unrelated { clippy::explicit_iter_loop, clippy::explicit_into_iter_loop, clippy::iter_next_loop, - clippy::reverse_range_loop, clippy::for_kv_map )] #[allow( @@ -32,61 +31,8 @@ impl Unrelated { )] #[allow(clippy::many_single_char_names, unused_variables)] fn main() { - const MAX_LEN: usize = 42; let mut vec = vec![1, 2, 3, 4]; - for i in 10..0 { - println!("{}", i); - } - - for i in 10..=0 { - println!("{}", i); - } - - for i in MAX_LEN..0 { - println!("{}", i); - } - - for i in 5..=5 { - // not an error, this is the range with only one element “5” - println!("{}", i); - } - - for i in 0..10 { - // not an error, the start index is less than the end index - println!("{}", i); - } - - for i in -10..0 { - // not an error - println!("{}", i); - } - - for i in (10..0).map(|x| x * 2) { - // not an error, it can't be known what arbitrary methods do to a range - println!("{}", i); - } - - // testing that the empty range lint folds constants - for i in 10..5 + 4 { - println!("{}", i); - } - - for i in (5 + 2)..(3 - 1) { - println!("{}", i); - } - - for i in (2 * 2)..(2 * 3) { - // no error, 4..6 is fine - println!("{}", i); - } - - let x = 42; - for i in x..10 { - // no error, not constant-foldable - println!("{}", i); - } - // See #601 for i in 0..10 { // no error, id_col does not exist outside the loop diff --git a/tests/ui/for_loop_fixable.stderr b/tests/ui/for_loop_fixable.stderr index cffb4b9f0a9..ddfe66d675f 100644 --- a/tests/ui/for_loop_fixable.stderr +++ b/tests/ui/for_loop_fixable.stderr @@ -1,61 +1,5 @@ -error: this range is empty so this for loop will never run - --> $DIR/for_loop_fixable.rs:38:14 - | -LL | for i in 10..0 { - | ^^^^^ - | - = note: `-D clippy::reverse-range-loop` implied by `-D warnings` -help: consider using the following if you are attempting to iterate over this range in reverse - | -LL | for i in (0..10).rev() { - | ^^^^^^^^^^^^^ - -error: this range is empty so this for loop will never run - --> $DIR/for_loop_fixable.rs:42:14 - | -LL | for i in 10..=0 { - | ^^^^^^ - | -help: consider using the following if you are attempting to iterate over this range in reverse - | -LL | for i in (0..=10).rev() { - | ^^^^^^^^^^^^^^ - -error: this range is empty so this for loop will never run - --> $DIR/for_loop_fixable.rs:46:14 - | -LL | for i in MAX_LEN..0 { - | ^^^^^^^^^^ - | -help: consider using the following if you are attempting to iterate over this range in reverse - | -LL | for i in (0..MAX_LEN).rev() { - | ^^^^^^^^^^^^^^^^^^ - -error: this range is empty so this for loop will never run - --> $DIR/for_loop_fixable.rs:71:14 - | -LL | for i in 10..5 + 4 { - | ^^^^^^^^^ - | -help: consider using the following if you are attempting to iterate over this range in reverse - | -LL | for i in (5 + 4..10).rev() { - | ^^^^^^^^^^^^^^^^^ - -error: this range is empty so this for loop will never run - --> $DIR/for_loop_fixable.rs:75:14 - | -LL | for i in (5 + 2)..(3 - 1) { - | ^^^^^^^^^^^^^^^^ - | -help: consider using the following if you are attempting to iterate over this range in reverse - | -LL | for i in ((3 - 1)..(5 + 2)).rev() { - | ^^^^^^^^^^^^^^^^^^^^^^^^ - error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:97:15 + --> $DIR/for_loop_fixable.rs:43:15 | LL | for _v in vec.iter() {} | ^^^^^^^^^^ help: to write this more concisely, try: `&vec` @@ -63,13 +7,13 @@ LL | for _v in vec.iter() {} = note: `-D clippy::explicit-iter-loop` implied by `-D warnings` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:99:15 + --> $DIR/for_loop_fixable.rs:45:15 | LL | for _v in vec.iter_mut() {} | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut vec` error: it is more concise to loop over containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:102:15 + --> $DIR/for_loop_fixable.rs:48:15 | LL | for _v in out_vec.into_iter() {} | ^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `out_vec` @@ -77,76 +21,76 @@ LL | for _v in out_vec.into_iter() {} = note: `-D clippy::explicit-into-iter-loop` implied by `-D warnings` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:107:15 + --> $DIR/for_loop_fixable.rs:53:15 | LL | for _v in [1, 2, 3].iter() {} | ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[1, 2, 3]` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:111:15 + --> $DIR/for_loop_fixable.rs:57:15 | LL | for _v in [0; 32].iter() {} | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 32]` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:116:15 + --> $DIR/for_loop_fixable.rs:62:15 | LL | for _v in ll.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&ll` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:119:15 + --> $DIR/for_loop_fixable.rs:65:15 | LL | for _v in vd.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&vd` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:122:15 + --> $DIR/for_loop_fixable.rs:68:15 | LL | for _v in bh.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&bh` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:125:15 + --> $DIR/for_loop_fixable.rs:71:15 | LL | for _v in hm.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&hm` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:128:15 + --> $DIR/for_loop_fixable.rs:74:15 | LL | for _v in bt.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&bt` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:131:15 + --> $DIR/for_loop_fixable.rs:77:15 | LL | for _v in hs.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&hs` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:134:15 + --> $DIR/for_loop_fixable.rs:80:15 | LL | for _v in bs.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&bs` error: it is more concise to loop over containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:309:18 + --> $DIR/for_loop_fixable.rs:255:18 | LL | for i in iterator.into_iter() { | ^^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `iterator` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:329:18 + --> $DIR/for_loop_fixable.rs:275:18 | LL | for _ in t.into_iter() {} | ^^^^^^^^^^^^^ help: to write this more concisely, try: `&t` error: it is more concise to loop over containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:331:18 + --> $DIR/for_loop_fixable.rs:277:18 | LL | for _ in r.into_iter() {} | ^^^^^^^^^^^^^ help: to write this more concisely, try: `r` -error: aborting due to 20 previous errors +error: aborting due to 15 previous errors diff --git a/tests/ui/for_loop_unfixable.rs b/tests/ui/for_loop_unfixable.rs index 179b255e08c..e73536052f0 100644 --- a/tests/ui/for_loop_unfixable.rs +++ b/tests/ui/for_loop_unfixable.rs @@ -5,7 +5,6 @@ clippy::explicit_iter_loop, clippy::explicit_into_iter_loop, clippy::iter_next_loop, - clippy::reverse_range_loop, clippy::for_kv_map )] #[allow( @@ -16,25 +15,8 @@ unused, dead_code )] -#[allow(clippy::many_single_char_names, unused_variables)] fn main() { - for i in 5..5 { - println!("{}", i); - } - let vec = vec![1, 2, 3, 4]; for _v in vec.iter().next() {} - - for i in (5 + 2)..(8 - 1) { - println!("{}", i); - } - - const ZERO: usize = 0; - - for i in ZERO..vec.len() { - if f(&vec[i], &vec[i]) { - panic!("at the disco"); - } - } } diff --git a/tests/ui/for_loop_unfixable.stderr b/tests/ui/for_loop_unfixable.stderr index 1da8e0f3588..1c9287b6acb 100644 --- a/tests/ui/for_loop_unfixable.stderr +++ b/tests/ui/for_loop_unfixable.stderr @@ -1,9 +1,10 @@ -error[E0425]: cannot find function `f` in this scope - --> $DIR/for_loop_unfixable.rs:36:12 +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want + --> $DIR/for_loop_unfixable.rs:21:15 | -LL | if f(&vec[i], &vec[i]) { - | ^ help: a local variable with a similar name exists: `i` +LL | for _v in vec.iter().next() {} + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iter-next-loop` implied by `-D warnings` error: aborting due to previous error -For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/manual_memcpy.rs b/tests/ui/manual_memcpy.rs index 9c24d6d4db1..0083f94798f 100644 --- a/tests/ui/manual_memcpy.rs +++ b/tests/ui/manual_memcpy.rs @@ -104,7 +104,7 @@ pub fn manual_copy(src: &[i32], dst: &mut [i32], dst2: &mut [i32]) { dst[i - 0] = src[i]; } - #[allow(clippy::reverse_range_loop)] + #[allow(clippy::reversed_empty_ranges)] for i in 0..0 { dst[i] = src[i]; } -- cgit 1.4.1-3-g733a5 From 945c9447093a2ca944e70bae125f2af69f8eac16 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 3 May 2020 11:20:51 +0200 Subject: Merge `block_in_if_condition_expr` and `block_in_if_condition_stmt` lints into `block_in_if_condition` lint --- CHANGELOG.md | 3 +- clippy_lints/src/block_in_if_condition.rs | 51 +++++++++++++-------------- clippy_lints/src/lib.rs | 9 ++--- src/lintlist/mod.rs | 11 ++---- tests/ui/block_in_if_condition.fixed | 3 +- tests/ui/block_in_if_condition.rs | 3 +- tests/ui/block_in_if_condition.stderr | 10 +++--- tests/ui/block_in_if_condition_closure.rs | 5 ++- tests/ui/block_in_if_condition_closure.stderr | 6 ++-- 9 files changed, 41 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index b25ef049356..0b270e6acd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1274,8 +1274,7 @@ Released 2018-09-13 [`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock [`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask [`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name -[`block_in_if_condition_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_expr -[`block_in_if_condition_stmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_stmt +[`block_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition [`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison [`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const [`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box diff --git a/clippy_lints/src/block_in_if_condition.rs b/clippy_lints/src/block_in_if_condition.rs index 9e533eaa32c..8a5e595749f 100644 --- a/clippy_lints/src/block_in_if_condition.rs +++ b/clippy_lints/src/block_in_if_condition.rs @@ -8,43 +8,40 @@ use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { - /// **What it does:** Checks for `if` conditions that use blocks to contain an - /// expression. + /// **What it does:** Checks for `if` conditions that use blocks containing an + /// expression, statements or conditions that use closures with blocks. /// - /// **Why is this bad?** It isn't really Rust style, same as using parentheses - /// to contain expressions. + /// **Why is this bad?** Style, using blocks in the condition makes it hard to read. /// /// **Known problems:** None. /// - /// **Example:** + /// **Examples:** /// ```rust + /// // Bad /// if { true } { /* ... */ } + /// + /// // Good + /// if true { /* ... */ } /// ``` - pub BLOCK_IN_IF_CONDITION_EXPR, - style, - "braces that can be eliminated in conditions, e.g., `if { true } ...`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for `if` conditions that use blocks containing - /// statements, or conditions that use closures with blocks. /// - /// **Why is this bad?** Using blocks in the condition makes it hard to read. + /// // or /// - /// **Known problems:** None. + /// ```rust + /// # fn somefunc() -> bool { true }; /// - /// **Example:** - /// ```rust,ignore - /// if { let x = somefunc(); x } {} - /// // or - /// if somefunc(|x| { x == 47 }) {} + /// // Bad + /// if { let x = somefunc(); x } { /* ... */ } + /// + /// // Good + /// let res = { let x = somefunc(); x }; + /// if res { /* ... */ } /// ``` - pub BLOCK_IN_IF_CONDITION_STMT, + pub BLOCK_IN_IF_CONDITION, style, - "complex blocks in conditions, e.g., `if { let x = true; x } ...`" + "useless or complex blocks that can be eliminated in conditions" } -declare_lint_pass!(BlockInIfCondition => [BLOCK_IN_IF_CONDITION_EXPR, BLOCK_IN_IF_CONDITION_STMT]); +declare_lint_pass!(BlockInIfCondition => [BLOCK_IN_IF_CONDITION]); struct ExVisitor<'a, 'tcx> { found_block: Option<&'tcx Expr<'tcx>>, @@ -72,7 +69,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition"; const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \ - instead, move the block or closure higher and bind it with a `let`"; + instead, move the block or closure higher and bind it with a `let`"; impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition { fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { @@ -92,7 +89,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition { let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( cx, - BLOCK_IN_IF_CONDITION_EXPR, + BLOCK_IN_IF_CONDITION, cond.span, BRACED_EXPR_MESSAGE, "try", @@ -118,7 +115,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition { let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( cx, - BLOCK_IN_IF_CONDITION_STMT, + BLOCK_IN_IF_CONDITION, expr.span.with_hi(cond.span.hi()), COMPLEX_BLOCK_MESSAGE, "try", @@ -140,7 +137,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition { let mut visitor = ExVisitor { found_block: None, cx }; walk_expr(&mut visitor, cond); if let Some(block) = visitor.found_block { - span_lint(cx, BLOCK_IN_IF_CONDITION_STMT, block.span, COMPLEX_BLOCK_MESSAGE); + span_lint(cx, BLOCK_IN_IF_CONDITION, block.span, COMPLEX_BLOCK_MESSAGE); } } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0c4daeb731f..98b696533d8 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -507,8 +507,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &bit_mask::INEFFECTIVE_BIT_MASK, &bit_mask::VERBOSE_BIT_MASK, &blacklisted_name::BLACKLISTED_NAME, - &block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR, - &block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT, + &block_in_if_condition::BLOCK_IN_IF_CONDITION, &booleans::LOGIC_BUG, &booleans::NONMINIMAL_BOOL, &bytecount::NAIVE_BYTECOUNT, @@ -1209,8 +1208,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(&bit_mask::VERBOSE_BIT_MASK), LintId::of(&blacklisted_name::BLACKLISTED_NAME), - LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR), - LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT), + LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION), LintId::of(&booleans::LOGIC_BUG), LintId::of(&booleans::NONMINIMAL_BOOL), LintId::of(&bytecount::NAIVE_BYTECOUNT), @@ -1456,8 +1454,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&attrs::UNKNOWN_CLIPPY_LINTS), LintId::of(&bit_mask::VERBOSE_BIT_MASK), LintId::of(&blacklisted_name::BLACKLISTED_NAME), - LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR), - LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT), + LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION), LintId::of(&collapsible_if::COLLAPSIBLE_IF), LintId::of(&comparison_chain::COMPARISON_CHAIN), LintId::of(&doc::MISSING_SAFETY_DOC), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index e1a6d4bdd31..4ae60f7d808 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -74,16 +74,9 @@ pub static ref ALL_LINTS: Vec = vec![ module: "blacklisted_name", }, Lint { - name: "block_in_if_condition_expr", + name: "block_in_if_condition", group: "style", - desc: "braces that can be eliminated in conditions, e.g., `if { true } ...`", - deprecation: None, - module: "block_in_if_condition", - }, - Lint { - name: "block_in_if_condition_stmt", - group: "style", - desc: "complex blocks in conditions, e.g., `if { let x = true; x } ...`", + desc: "useless or complex blocks that can be eliminated in conditions", deprecation: None, module: "block_in_if_condition", }, diff --git a/tests/ui/block_in_if_condition.fixed b/tests/ui/block_in_if_condition.fixed index 955801e40f9..ae01c6d3042 100644 --- a/tests/ui/block_in_if_condition.fixed +++ b/tests/ui/block_in_if_condition.fixed @@ -1,6 +1,5 @@ // run-rustfix -#![warn(clippy::block_in_if_condition_expr)] -#![warn(clippy::block_in_if_condition_stmt)] +#![warn(clippy::block_in_if_condition)] #![allow(unused, clippy::let_and_return)] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/block_in_if_condition.rs b/tests/ui/block_in_if_condition.rs index a6ea01d5fc5..88555dc47c2 100644 --- a/tests/ui/block_in_if_condition.rs +++ b/tests/ui/block_in_if_condition.rs @@ -1,6 +1,5 @@ // run-rustfix -#![warn(clippy::block_in_if_condition_expr)] -#![warn(clippy::block_in_if_condition_stmt)] +#![warn(clippy::block_in_if_condition)] #![allow(unused, clippy::let_and_return)] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/block_in_if_condition.stderr b/tests/ui/block_in_if_condition.stderr index b0a0a276c89..89e9ad26f49 100644 --- a/tests/ui/block_in_if_condition.stderr +++ b/tests/ui/block_in_if_condition.stderr @@ -1,5 +1,5 @@ error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> $DIR/block_in_if_condition.rs:27:5 + --> $DIR/block_in_if_condition.rs:26:5 | LL | / if { LL | | let x = 3; @@ -7,7 +7,7 @@ LL | | x == 3 LL | | } { | |_____^ | - = note: `-D clippy::block-in-if-condition-stmt` implied by `-D warnings` + = note: `-D clippy::block-in-if-condition` implied by `-D warnings` help: try | LL | let res = { @@ -17,15 +17,13 @@ LL | }; if res { | error: omit braces around single expression condition - --> $DIR/block_in_if_condition.rs:38:8 + --> $DIR/block_in_if_condition.rs:37:8 | LL | if { true } { | ^^^^^^^^ help: try: `true` - | - = note: `-D clippy::block-in-if-condition-expr` implied by `-D warnings` error: this boolean expression can be simplified - --> $DIR/block_in_if_condition.rs:47:8 + --> $DIR/block_in_if_condition.rs:46:8 | LL | if true && x == 3 { | ^^^^^^^^^^^^^^ help: try: `x == 3` diff --git a/tests/ui/block_in_if_condition_closure.rs b/tests/ui/block_in_if_condition_closure.rs index bac3eda5e7f..87b3fb94daf 100644 --- a/tests/ui/block_in_if_condition_closure.rs +++ b/tests/ui/block_in_if_condition_closure.rs @@ -1,5 +1,4 @@ -#![warn(clippy::block_in_if_condition_expr)] -#![warn(clippy::block_in_if_condition_stmt)] +#![warn(clippy::block_in_if_condition)] #![allow(unused, clippy::let_and_return)] fn predicate bool, T>(pfn: F, val: T) -> bool { @@ -10,7 +9,7 @@ fn pred_test() { let v = 3; let sky = "blue"; // This is a sneaky case, where the block isn't directly in the condition, - // but is actually nside a closure that the condition is using. + // but is actually inside a closure that the condition is using. // The same principle applies -- add some extra expressions to make sure // linter isn't confused by them. if v == 3 diff --git a/tests/ui/block_in_if_condition_closure.stderr b/tests/ui/block_in_if_condition_closure.stderr index 86cd24fe763..3df25691c3c 100644 --- a/tests/ui/block_in_if_condition_closure.stderr +++ b/tests/ui/block_in_if_condition_closure.stderr @@ -1,5 +1,5 @@ error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> $DIR/block_in_if_condition_closure.rs:19:17 + --> $DIR/block_in_if_condition_closure.rs:18:17 | LL | |x| { | _________________^ @@ -8,10 +8,10 @@ LL | | x == target LL | | }, | |_____________^ | - = note: `-D clippy::block-in-if-condition-stmt` implied by `-D warnings` + = note: `-D clippy::block-in-if-condition` implied by `-D warnings` error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> $DIR/block_in_if_condition_closure.rs:28:13 + --> $DIR/block_in_if_condition_closure.rs:27:13 | LL | |x| { | _____________^ -- cgit 1.4.1-3-g733a5 From 6cbdd1e49dbb2355ac1036946a5a635e22023c6f Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 3 May 2020 12:28:40 +0200 Subject: Merge `option_map_unwrap_or`, `option_map_unwrap_or_else` and `result_map_unwrap_or_else` lints into `map_unwrap` lint --- CHANGELOG.md | 4 +- clippy_lints/src/lib.rs | 8 +- clippy_lints/src/methods/mod.rs | 92 ++++--------- clippy_lints/src/methods/option_map_unwrap_or.rs | 6 +- src/lintlist/mod.rs | 28 +--- tests/ui/map_unwrap.rs | 99 ++++++++++++++ tests/ui/map_unwrap.stderr | 161 +++++++++++++++++++++++ tests/ui/option_map_unwrap_or.rs | 88 ------------- tests/ui/option_map_unwrap_or.stderr | 138 ------------------- tests/ui/result_map_unwrap_or_else.rs | 23 ---- tests/ui/result_map_unwrap_or_else.stderr | 27 ---- 11 files changed, 301 insertions(+), 373 deletions(-) create mode 100644 tests/ui/map_unwrap.rs create mode 100644 tests/ui/map_unwrap.stderr delete mode 100644 tests/ui/option_map_unwrap_or.rs delete mode 100644 tests/ui/option_map_unwrap_or.stderr delete mode 100644 tests/ui/result_map_unwrap_or_else.rs delete mode 100644 tests/ui/result_map_unwrap_or_else.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b270e6acd2..28b05044db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1430,6 +1430,7 @@ Released 2018-09-13 [`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone [`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry [`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten +[`map_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap [`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref [`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool [`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items @@ -1499,8 +1500,6 @@ Released 2018-09-13 [`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used [`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn -[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or -[`option_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or_else [`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option [`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used [`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call @@ -1542,7 +1541,6 @@ Released 2018-09-13 [`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used [`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn -[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 98b696533d8..c9a2ef49907 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -673,19 +673,17 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::ITER_SKIP_NEXT, &methods::MANUAL_SATURATING_ARITHMETIC, &methods::MAP_FLATTEN, + &methods::MAP_UNWRAP, &methods::NEW_RET_NO_SELF, &methods::OK_EXPECT, &methods::OPTION_AND_THEN_SOME, &methods::OPTION_AS_REF_DEREF, &methods::OPTION_EXPECT_USED, &methods::OPTION_MAP_OR_NONE, - &methods::OPTION_MAP_UNWRAP_OR, - &methods::OPTION_MAP_UNWRAP_OR_ELSE, &methods::OPTION_UNWRAP_USED, &methods::OR_FUN_CALL, &methods::RESULT_EXPECT_USED, &methods::RESULT_MAP_OR_INTO_OPTION, - &methods::RESULT_MAP_UNWRAP_OR_ELSE, &methods::RESULT_UNWRAP_USED, &methods::SEARCH_IS_SOME, &methods::SHOULD_IMPLEMENT_TRAIT, @@ -1152,9 +1150,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::FIND_MAP), LintId::of(&methods::INEFFICIENT_TO_STRING), LintId::of(&methods::MAP_FLATTEN), - LintId::of(&methods::OPTION_MAP_UNWRAP_OR), - LintId::of(&methods::OPTION_MAP_UNWRAP_OR_ELSE), - LintId::of(&methods::RESULT_MAP_UNWRAP_OR_ELSE), + LintId::of(&methods::MAP_UNWRAP), LintId::of(&misc::USED_UNDERSCORE_BINDING), LintId::of(&misc_early::UNSEPARATED_LITERAL_SUFFIX), LintId::of(&mut_mut::MUT_MUT), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 3676dc5b09d..401298b2d51 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -257,59 +257,40 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for usage of `_.map(_).unwrap_or(_)`. + /// **What it does:** Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or + /// `result.map(_).unwrap_or_else(_)`. /// - /// **Why is this bad?** Readability, this can be written more concisely as - /// `_.map_or(_, _)`. + /// **Why is this bad?** Readability, these can be written more concisely (resp.) as + /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`. /// /// **Known problems:** The order of the arguments is not in execution order /// - /// **Example:** + /// **Examples:** /// ```rust /// # let x = Some(1); - /// x.map(|a| a + 1).unwrap_or(0); - /// ``` - pub OPTION_MAP_UNWRAP_OR, - pedantic, - "using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for usage of `_.map(_).unwrap_or_else(_)`. - /// - /// **Why is this bad?** Readability, this can be written more concisely as - /// `_.map_or_else(_, _)`. /// - /// **Known problems:** The order of the arguments is not in execution order. + /// // Bad + /// x.map(|a| a + 1).unwrap_or(0); /// - /// **Example:** - /// ```rust - /// # let x = Some(1); - /// # fn some_function() -> usize { 1 } - /// x.map(|a| a + 1).unwrap_or_else(some_function); + /// // Good + /// x.map_or(0, |a| a + 1); /// ``` - pub OPTION_MAP_UNWRAP_OR_ELSE, - pedantic, - "using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for usage of `result.map(_).unwrap_or_else(_)`. /// - /// **Why is this bad?** Readability, this can be written more concisely as - /// `result.map_or_else(_, _)`. - /// - /// **Known problems:** None. + /// // or /// - /// **Example:** /// ```rust /// # let x: Result = Ok(1); /// # fn some_function(foo: ()) -> usize { 1 } + /// + /// // Bad /// x.map(|a| a + 1).unwrap_or_else(some_function); + /// + /// // Good + /// x.map_or_else(some_function, |a| a + 1); /// ``` - pub RESULT_MAP_UNWRAP_OR_ELSE, + pub MAP_UNWRAP, pedantic, - "using `Result.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `.map_or_else(g, f)`" + "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`" } declare_clippy_lint! { @@ -1294,9 +1275,7 @@ declare_lint_pass!(Methods => [ WRONG_SELF_CONVENTION, WRONG_PUB_SELF_CONVENTION, OK_EXPECT, - OPTION_MAP_UNWRAP_OR, - OPTION_MAP_UNWRAP_OR_ELSE, - RESULT_MAP_UNWRAP_OR_ELSE, + MAP_UNWRAP, RESULT_MAP_OR_INTO_OPTION, OPTION_MAP_OR_NONE, OPTION_AND_THEN_SOME, @@ -1503,9 +1482,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { cx, lint, first_arg.pat.span, - &format!( - "methods called `{}` usually take {}; consider choosing a less \ - ambiguous name", + &format!("methods called `{}` usually take {}; consider choosing a less ambiguous name", conv, &self_kinds .iter() @@ -1678,7 +1655,7 @@ fn lint_or_fun_call<'a, 'tcx>( let self_ty = cx.tables.expr_ty(self_expr); if let Some(&(_, fn_has_arguments, poss, suffix)) = - know_types.iter().find(|&&i| match_type(cx, self_ty, i.0)); + know_types.iter().find(|&&i| match_type(cx, self_ty, i.0)); if poss.contains(&name); @@ -1931,7 +1908,7 @@ fn lint_clone_on_copy(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, arg: &hir: CLONE_DOUBLE_REF, expr.span, "using `clone` on a double-reference; \ - this will copy the reference instead of cloning the inner type", + this will copy the reference instead of cloning the inner type", |diag| { if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) { let mut ty = innermost; @@ -2121,7 +2098,7 @@ fn lint_iter_cloned_collect<'a, 'tcx>( ITER_CLONED_COLLECT, to_replace, "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \ - more readable", + more readable", "try", ".to_vec()".to_string(), Applicability::MachineApplicable, @@ -2436,7 +2413,7 @@ fn lint_unwrap(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, unwrap_args: &[hi None, &format!( "if you don't want to handle the `{}` case gracefully, consider \ - using `expect()` to provide a better panic message", + using `expect()` to provide a better panic message", none_value, ), ); @@ -2494,7 +2471,7 @@ fn lint_map_flatten<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr< // lint if caller of `.map().flatten()` is an Iterator if match_trait_method(cx, expr, &paths::ITERATOR) { let msg = "called `map(..).flatten()` on an `Iterator`. \ - This is more succinctly expressed by calling `.flat_map(..)`"; + This is more succinctly expressed by calling `.flat_map(..)`"; let self_snippet = snippet(cx, map_args[0].span, ".."); let func_snippet = snippet(cx, map_args[1].span, ".."); let hint = format!("{0}.flat_map({1})", self_snippet, func_snippet); @@ -2555,10 +2532,10 @@ fn lint_map_unwrap_or_else<'a, 'tcx>( // lint message let msg = if is_option { "called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling \ - `map_or_else(g, f)` instead" + `map_or_else(g, f)` instead" } else { "called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling \ - `.map_or_else(g, f)` instead" + `.map_or_else(g, f)` instead" }; // get snippets for args to map() and unwrap_or_else() let map_snippet = snippet(cx, map_args[1].span, ".."); @@ -2570,11 +2547,7 @@ fn lint_map_unwrap_or_else<'a, 'tcx>( if same_span && !multiline { span_lint_and_note( cx, - if is_option { - OPTION_MAP_UNWRAP_OR_ELSE - } else { - RESULT_MAP_UNWRAP_OR_ELSE - }, + MAP_UNWRAP, expr.span, msg, None, @@ -2584,16 +2557,7 @@ fn lint_map_unwrap_or_else<'a, 'tcx>( ), ); } else if same_span && multiline { - span_lint( - cx, - if is_option { - OPTION_MAP_UNWRAP_OR_ELSE - } else { - RESULT_MAP_UNWRAP_OR_ELSE - }, - expr.span, - msg, - ); + span_lint(cx, MAP_UNWRAP, expr.span, msg); }; } } diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index bf9dd3c9369..fcaa9b47e64 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -9,7 +9,7 @@ use rustc_middle::hir::map::Map; use rustc_span::source_map::Span; use rustc_span::symbol::Symbol; -use super::OPTION_MAP_UNWRAP_OR; +use super::MAP_UNWRAP; /// lint use of `map().unwrap_or()` for `Option`s pub(super) fn lint<'a, 'tcx>( @@ -62,11 +62,11 @@ pub(super) fn lint<'a, 'tcx>( }; let msg = &format!( "called `map(f).unwrap_or({})` on an `Option` value. \ - This can be done more directly by calling `{}` instead", + This can be done more directly by calling `{}` instead", arg, suggest ); - span_lint_and_then(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg, |diag| { + span_lint_and_then(cx, MAP_UNWRAP, expr.span, msg, |diag| { let map_arg_span = map_args[1].span; let mut suggestion = vec![ diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 4ae60f7d808..d3bd9f66e38 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1137,6 +1137,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "methods", }, + Lint { + name: "map_unwrap", + group: "pedantic", + desc: "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`", + deprecation: None, + module: "methods", + }, Lint { name: "match_as_ref", group: "complexity", @@ -1613,20 +1620,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "map_unit_fn", }, - Lint { - name: "option_map_unwrap_or", - group: "pedantic", - desc: "using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`", - deprecation: None, - module: "methods", - }, - Lint { - name: "option_map_unwrap_or_else", - group: "pedantic", - desc: "using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`", - deprecation: None, - module: "methods", - }, Lint { name: "option_option", group: "pedantic", @@ -1900,13 +1893,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "map_unit_fn", }, - Lint { - name: "result_map_unwrap_or_else", - group: "pedantic", - desc: "using `Result.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `.map_or_else(g, f)`", - deprecation: None, - module: "methods", - }, Lint { name: "result_unwrap_used", group: "restriction", diff --git a/tests/ui/map_unwrap.rs b/tests/ui/map_unwrap.rs new file mode 100644 index 00000000000..53e50368231 --- /dev/null +++ b/tests/ui/map_unwrap.rs @@ -0,0 +1,99 @@ +// FIXME: Add "run-rustfix" once it's supported for multipart suggestions +// aux-build:option_helpers.rs + +#![warn(clippy::map_unwrap)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::HashMap; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or(_)` use. + // Single line case. + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or(0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or(0); + let _ = opt.map(|x| x + 1) + .unwrap_or({ + 0 + }); + // Single line `map(f).unwrap_or(None)` case. + let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + // Multi-line `map(f).unwrap_or(None)` cases. + let _ = opt.map(|x| { + Some(x + 1) + } + ).unwrap_or(None); + let _ = opt + .map(|x| Some(x + 1)) + .unwrap_or(None); + // macro case + let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint + + // Should not lint if not copyable + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id); + // ...but DO lint if the `unwrap_or` argument is not used in the `map` + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + + // Check for `option.map(_).unwrap_or_else(_)` use. + // single line case + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or_else(|| 0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or_else(|| 0); + let _ = opt.map(|x| x + 1) + .unwrap_or_else(|| + 0 + ); + // Macro case. + // Should not lint. + let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); + + // Issue #4144 + { + let mut frequencies = HashMap::new(); + let word = "foo"; + + frequencies + .get_mut(word) + .map(|count| { + *count += 1; + }) + .unwrap_or_else(|| { + frequencies.insert(word.to_owned(), 1); + }); + } +} + +fn result_methods() { + let res: Result = Ok(1); + + // Check for `result.map(_).unwrap_or_else(_)` use. + // single line case + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line + // multi line cases + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + // macro case + let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|e| 0); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/tests/ui/map_unwrap.stderr b/tests/ui/map_unwrap.stderr new file mode 100644 index 00000000000..2610923275d --- /dev/null +++ b/tests/ui/map_unwrap.stderr @@ -0,0 +1,161 @@ +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap.rs:17:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or(0); + | |_____________________^ + | + = note: `-D clippy::map-unwrap` implied by `-D warnings` +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or(0, |x| x + 1); + | ^^^^^^ ^^ -- + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap.rs:21:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or(0); + | |__________________^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or(0, |x| { +LL | x + 1 +LL | } +LL | ); + | + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap.rs:25:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or({ +LL | | 0 +LL | | }); + | |__________^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or({ +LL | 0 +LL | }, |x| x + 1); + | + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/map_unwrap.rs:30:13 + | +LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `and_then(f)` instead + | +LL | let _ = opt.and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/map_unwrap.rs:32:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | Some(x + 1) +LL | | } +LL | | ).unwrap_or(None); + | |_____________________^ + | +help: use `and_then(f)` instead + | +LL | let _ = opt.and_then(|x| { +LL | Some(x + 1) +LL | } +LL | ); + | + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/map_unwrap.rs:36:13 + | +LL | let _ = opt + | _____________^ +LL | | .map(|x| Some(x + 1)) +LL | | .unwrap_or(None); + | |________________________^ + | +help: use `and_then(f)` instead + | +LL | .and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap.rs:47:13 + | +LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); + | ^^^^^^ ^^^ -- + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/map_unwrap.rs:51:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or_else(|| 0); + | |_____________________________^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|| 0)` with `map_or_else(|| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/map_unwrap.rs:55:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or_else(|| 0); + | |__________________________^ + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/map_unwrap.rs:59:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or_else(|| +LL | | 0 +LL | | ); + | |_________^ + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/map_unwrap.rs:88:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/map_unwrap.rs:90:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/map_unwrap.rs:91:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: aborting due to 13 previous errors + diff --git a/tests/ui/option_map_unwrap_or.rs b/tests/ui/option_map_unwrap_or.rs deleted file mode 100644 index 0364d83663a..00000000000 --- a/tests/ui/option_map_unwrap_or.rs +++ /dev/null @@ -1,88 +0,0 @@ -// FIXME: Add "run-rustfix" once it's supported for multipart suggestions -// aux-build:option_helpers.rs - -#![warn(clippy::option_map_unwrap_or, clippy::option_map_unwrap_or_else)] - -#[macro_use] -extern crate option_helpers; - -use std::collections::HashMap; - -/// Checks implementation of the following lints: -/// * `OPTION_MAP_UNWRAP_OR` -/// * `OPTION_MAP_UNWRAP_OR_ELSE` -#[rustfmt::skip] -fn option_methods() { - let opt = Some(1); - - // Check `OPTION_MAP_UNWRAP_OR`. - // Single line case. - let _ = opt.map(|x| x + 1) - // Should lint even though this call is on a separate line. - .unwrap_or(0); - // Multi-line cases. - let _ = opt.map(|x| { - x + 1 - } - ).unwrap_or(0); - let _ = opt.map(|x| x + 1) - .unwrap_or({ - 0 - }); - // Single line `map(f).unwrap_or(None)` case. - let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); - // Multi-line `map(f).unwrap_or(None)` cases. - let _ = opt.map(|x| { - Some(x + 1) - } - ).unwrap_or(None); - let _ = opt - .map(|x| Some(x + 1)) - .unwrap_or(None); - // macro case - let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint - - // Should not lint if not copyable - let id: String = "identifier".to_string(); - let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id); - // ...but DO lint if the `unwrap_or` argument is not used in the `map` - let id: String = "identifier".to_string(); - let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); - - // Check OPTION_MAP_UNWRAP_OR_ELSE - // single line case - let _ = opt.map(|x| x + 1) - // Should lint even though this call is on a separate line. - .unwrap_or_else(|| 0); - // Multi-line cases. - let _ = opt.map(|x| { - x + 1 - } - ).unwrap_or_else(|| 0); - let _ = opt.map(|x| x + 1) - .unwrap_or_else(|| - 0 - ); - // Macro case. - // Should not lint. - let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); - - // Issue #4144 - { - let mut frequencies = HashMap::new(); - let word = "foo"; - - frequencies - .get_mut(word) - .map(|count| { - *count += 1; - }) - .unwrap_or_else(|| { - frequencies.insert(word.to_owned(), 1); - }); - } -} - -fn main() { - option_methods(); -} diff --git a/tests/ui/option_map_unwrap_or.stderr b/tests/ui/option_map_unwrap_or.stderr deleted file mode 100644 index f05f2893de2..00000000000 --- a/tests/ui/option_map_unwrap_or.stderr +++ /dev/null @@ -1,138 +0,0 @@ -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/option_map_unwrap_or.rs:20:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | // Should lint even though this call is on a separate line. -LL | | .unwrap_or(0); - | |_____________________^ - | - = note: `-D clippy::option-map-unwrap-or` implied by `-D warnings` -help: use `map_or(a, f)` instead - | -LL | let _ = opt.map_or(0, |x| x + 1); - | ^^^^^^ ^^ -- - -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/option_map_unwrap_or.rs:24:13 - | -LL | let _ = opt.map(|x| { - | _____________^ -LL | | x + 1 -LL | | } -LL | | ).unwrap_or(0); - | |__________________^ - | -help: use `map_or(a, f)` instead - | -LL | let _ = opt.map_or(0, |x| { -LL | x + 1 -LL | } -LL | ); - | - -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/option_map_unwrap_or.rs:28:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | .unwrap_or({ -LL | | 0 -LL | | }); - | |__________^ - | -help: use `map_or(a, f)` instead - | -LL | let _ = opt.map_or({ -LL | 0 -LL | }, |x| x + 1); - | - -error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead - --> $DIR/option_map_unwrap_or.rs:33:13 - | -LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use `and_then(f)` instead - | -LL | let _ = opt.and_then(|x| Some(x + 1)); - | ^^^^^^^^ -- - -error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead - --> $DIR/option_map_unwrap_or.rs:35:13 - | -LL | let _ = opt.map(|x| { - | _____________^ -LL | | Some(x + 1) -LL | | } -LL | | ).unwrap_or(None); - | |_____________________^ - | -help: use `and_then(f)` instead - | -LL | let _ = opt.and_then(|x| { -LL | Some(x + 1) -LL | } -LL | ); - | - -error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead - --> $DIR/option_map_unwrap_or.rs:39:13 - | -LL | let _ = opt - | _____________^ -LL | | .map(|x| Some(x + 1)) -LL | | .unwrap_or(None); - | |________________________^ - | -help: use `and_then(f)` instead - | -LL | .and_then(|x| Some(x + 1)); - | ^^^^^^^^ -- - -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/option_map_unwrap_or.rs:50:13 - | -LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use `map_or(a, f)` instead - | -LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); - | ^^^^^^ ^^^ -- - -error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead - --> $DIR/option_map_unwrap_or.rs:54:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | // Should lint even though this call is on a separate line. -LL | | .unwrap_or_else(|| 0); - | |_____________________________^ - | - = note: `-D clippy::option-map-unwrap-or-else` implied by `-D warnings` - = note: replace `map(|x| x + 1).unwrap_or_else(|| 0)` with `map_or_else(|| 0, |x| x + 1)` - -error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead - --> $DIR/option_map_unwrap_or.rs:58:13 - | -LL | let _ = opt.map(|x| { - | _____________^ -LL | | x + 1 -LL | | } -LL | | ).unwrap_or_else(|| 0); - | |__________________________^ - -error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead - --> $DIR/option_map_unwrap_or.rs:62:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | .unwrap_or_else(|| -LL | | 0 -LL | | ); - | |_________^ - -error: aborting due to 10 previous errors - diff --git a/tests/ui/result_map_unwrap_or_else.rs b/tests/ui/result_map_unwrap_or_else.rs deleted file mode 100644 index 40751bfebe6..00000000000 --- a/tests/ui/result_map_unwrap_or_else.rs +++ /dev/null @@ -1,23 +0,0 @@ -// aux-build:option_helpers.rs - -//! Checks implementation of `RESULT_MAP_UNWRAP_OR_ELSE` - -#![warn(clippy::result_map_unwrap_or_else)] - -#[macro_use] -extern crate option_helpers; - -fn result_methods() { - let res: Result = Ok(1); - - // Check RESULT_MAP_UNWRAP_OR_ELSE - // single line case - let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line - // multi line cases - let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - // macro case - let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|e| 0); // should not lint -} - -fn main() {} diff --git a/tests/ui/result_map_unwrap_or_else.stderr b/tests/ui/result_map_unwrap_or_else.stderr deleted file mode 100644 index ec7bc8f1241..00000000000 --- a/tests/ui/result_map_unwrap_or_else.stderr +++ /dev/null @@ -1,27 +0,0 @@ -error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead - --> $DIR/result_map_unwrap_or_else.rs:15:13 - | -LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::result-map-unwrap-or-else` implied by `-D warnings` - = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` - -error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead - --> $DIR/result_map_unwrap_or_else.rs:17:13 - | -LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` - -error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead - --> $DIR/result_map_unwrap_or_else.rs:18:13 - | -LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` - -error: aborting due to 3 previous errors - -- cgit 1.4.1-3-g733a5 From bcf61666bd903c0d13c081cf222b423e45cd854e Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 3 May 2020 13:11:18 +0200 Subject: Merge `option_unwrap_used` and `result_unwrap_used` lints into `unwrap_used` lint --- CHANGELOG.md | 2 +- clippy_lints/src/lib.rs | 6 ++-- clippy_lints/src/methods/mod.rs | 68 +++++++++++++++-------------------------- src/lintlist/mod.rs | 14 ++++----- tests/ui/unwrap.rs | 2 +- tests/ui/unwrap.stderr | 3 +- 6 files changed, 37 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 28b05044db6..78f98bba2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1501,7 +1501,6 @@ Released 2018-09-13 [`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn [`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option -[`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used [`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call [`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing [`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional @@ -1622,6 +1621,7 @@ Released 2018-09-13 [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit +[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used [`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug [`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self [`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9a2ef49907..bb3bc0b4545 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -680,11 +680,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::OPTION_AS_REF_DEREF, &methods::OPTION_EXPECT_USED, &methods::OPTION_MAP_OR_NONE, - &methods::OPTION_UNWRAP_USED, &methods::OR_FUN_CALL, &methods::RESULT_EXPECT_USED, &methods::RESULT_MAP_OR_INTO_OPTION, - &methods::RESULT_UNWRAP_USED, &methods::SEARCH_IS_SOME, &methods::SHOULD_IMPLEMENT_TRAIT, &methods::SINGLE_CHAR_PATTERN, @@ -695,6 +693,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::UNINIT_ASSUMED_INIT, &methods::UNNECESSARY_FILTER_MAP, &methods::UNNECESSARY_FOLD, + &methods::UNWRAP_USED, &methods::USELESS_ASREF, &methods::WRONG_PUB_SELF_CONVENTION, &methods::WRONG_SELF_CONVENTION, @@ -1090,9 +1089,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::FILETYPE_IS_FILE), LintId::of(&methods::GET_UNWRAP), LintId::of(&methods::OPTION_EXPECT_USED), - LintId::of(&methods::OPTION_UNWRAP_USED), LintId::of(&methods::RESULT_EXPECT_USED), - LintId::of(&methods::RESULT_UNWRAP_USED), + LintId::of(&methods::UNWRAP_USED), LintId::of(&methods::WRONG_PUB_SELF_CONVENTION), LintId::of(&misc::FLOAT_CMP_CONST), LintId::of(&misc_early::UNNEEDED_FIELD_PATTERN), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 401298b2d51..1af4d03c7a2 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -33,40 +33,15 @@ use crate::utils::{ }; declare_clippy_lint! { - /// **What it does:** Checks for `.unwrap()` calls on `Option`s. + /// **What it does:** Checks for `.unwrap()` calls on `Option`s and on `Result`s. /// - /// **Why is this bad?** Usually it is better to handle the `None` case, or to - /// at least call `.expect(_)` with a more helpful message. Still, for a lot of + /// **Why is this bad?** It is better to handle the `None` or `Err` case, + /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is /// `Allow` by default. /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// Using unwrap on an `Option`: - /// - /// ```rust - /// let opt = Some(1); - /// opt.unwrap(); - /// ``` - /// - /// Better: - /// - /// ```rust - /// let opt = Some(1); - /// opt.expect("more helpful message"); - /// ``` - pub OPTION_UNWRAP_USED, - restriction, - "using `Option.unwrap()`, which should at least get a better message using `expect()`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for `.unwrap()` calls on `Result`s. - /// - /// **Why is this bad?** `result.unwrap()` will let the thread panic on `Err` - /// values. Normally, you want to implement more sophisticated error handling, + /// `result.unwrap()` will let the thread panic on `Err` values. + /// Normally, you want to implement more sophisticated error handling, /// and propagate errors upwards with `?` operator. /// /// Even if you want to panic on errors, not all `Error`s implement good @@ -75,23 +50,31 @@ declare_clippy_lint! { /// /// **Known problems:** None. /// - /// **Example:** - /// Using unwrap on an `Result`: - /// + /// **Examples:** /// ```rust - /// let res: Result = Ok(1); - /// res.unwrap(); + /// # let opt = Some(1); + /// + /// // Bad + /// opt.unwrap(); + /// + /// // Good + /// opt.expect("more helpful message"); /// ``` /// - /// Better: + /// // or /// /// ```rust - /// let res: Result = Ok(1); + /// # let res: Result = Ok(1); + /// + /// // Bad + /// res.unwrap(); + /// + /// // Good /// res.expect("more helpful message"); /// ``` - pub RESULT_UNWRAP_USED, + pub UNWRAP_USED, restriction, - "using `Result.unwrap()`, which might be better handled" + "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`" } declare_clippy_lint! { @@ -1267,8 +1250,7 @@ declare_clippy_lint! { } declare_lint_pass!(Methods => [ - OPTION_UNWRAP_USED, - RESULT_UNWRAP_USED, + UNWRAP_USED, OPTION_EXPECT_USED, RESULT_EXPECT_USED, SHOULD_IMPLEMENT_TRAIT, @@ -2397,9 +2379,9 @@ fn lint_unwrap(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, unwrap_args: &[hi let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&unwrap_args[0])); let mess = if is_type_diagnostic_item(cx, obj_ty, sym!(option_type)) { - Some((OPTION_UNWRAP_USED, "an Option", "None")) + Some((UNWRAP_USED, "an Option", "None")) } else if is_type_diagnostic_item(cx, obj_ty, sym!(result_type)) { - Some((RESULT_UNWRAP_USED, "a Result", "Err")) + Some((UNWRAP_USED, "a Result", "Err")) } else { None }; diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index d3bd9f66e38..5cbf3ef028c 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1627,13 +1627,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "types", }, - Lint { - name: "option_unwrap_used", - group: "restriction", - desc: "using `Option.unwrap()`, which should at least get a better message using `expect()`", - deprecation: None, - module: "methods", - }, Lint { name: "or_fun_call", group: "perf", @@ -2404,6 +2397,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "returns", }, + Lint { + name: "unwrap_used", + group: "restriction", + desc: "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`", + deprecation: None, + module: "methods", + }, Lint { name: "use_debug", group: "restriction", diff --git a/tests/ui/unwrap.rs b/tests/ui/unwrap.rs index fcd1fcd14d4..a4a3cd1d379 100644 --- a/tests/ui/unwrap.rs +++ b/tests/ui/unwrap.rs @@ -1,4 +1,4 @@ -#![warn(clippy::option_unwrap_used, clippy::result_unwrap_used)] +#![warn(clippy::unwrap_used)] fn unwrap_option() { let opt = Some(0); diff --git a/tests/ui/unwrap.stderr b/tests/ui/unwrap.stderr index b90ce68fa97..4f0858005f6 100644 --- a/tests/ui/unwrap.stderr +++ b/tests/ui/unwrap.stderr @@ -4,7 +4,7 @@ error: used `unwrap()` on `an Option` value LL | let _ = opt.unwrap(); | ^^^^^^^^^^^^ | - = note: `-D clippy::option-unwrap-used` implied by `-D warnings` + = note: `-D clippy::unwrap-used` implied by `-D warnings` = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: used `unwrap()` on `a Result` value @@ -13,7 +13,6 @@ error: used `unwrap()` on `a Result` value LL | let _ = res.unwrap(); | ^^^^^^^^^^^^ | - = note: `-D clippy::result-unwrap-used` implied by `-D warnings` = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message error: aborting due to 2 previous errors -- cgit 1.4.1-3-g733a5 From 0e8be599cd04a8566224c63eeb07f5fa04605702 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 3 May 2020 13:32:17 +0200 Subject: Merge `option_expect_used` and `result_expect_used` lints into `expect_used` lint --- CHANGELOG.md | 3 +- clippy_lints/src/lib.rs | 6 ++-- clippy_lints/src/methods/mod.rs | 63 +++++++++++++++-------------------------- src/lintlist/mod.rs | 21 +++++--------- tests/ui/expect.rs | 2 +- tests/ui/expect.stderr | 3 +- 6 files changed, 35 insertions(+), 63 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 78f98bba2b4..4eeb71fa5c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1337,6 +1337,7 @@ Released 2018-09-13 [`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision [`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit [`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call +[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used [`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy [`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop [`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods @@ -1497,7 +1498,6 @@ Released 2018-09-13 [`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap -[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used [`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn [`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option @@ -1537,7 +1537,6 @@ Released 2018-09-13 [`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro [`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts [`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs -[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used [`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index bb3bc0b4545..eaef1f543d3 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -657,6 +657,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::CLONE_ON_COPY, &methods::CLONE_ON_REF_PTR, &methods::EXPECT_FUN_CALL, + &methods::EXPECT_USED, &methods::FILETYPE_IS_FILE, &methods::FILTER_MAP, &methods::FILTER_MAP_NEXT, @@ -678,10 +679,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::OK_EXPECT, &methods::OPTION_AND_THEN_SOME, &methods::OPTION_AS_REF_DEREF, - &methods::OPTION_EXPECT_USED, &methods::OPTION_MAP_OR_NONE, &methods::OR_FUN_CALL, - &methods::RESULT_EXPECT_USED, &methods::RESULT_MAP_OR_INTO_OPTION, &methods::SEARCH_IS_SOME, &methods::SHOULD_IMPLEMENT_TRAIT, @@ -1086,10 +1085,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::WILDCARD_ENUM_MATCH_ARM), LintId::of(&mem_forget::MEM_FORGET), LintId::of(&methods::CLONE_ON_REF_PTR), + LintId::of(&methods::EXPECT_USED), LintId::of(&methods::FILETYPE_IS_FILE), LintId::of(&methods::GET_UNWRAP), - LintId::of(&methods::OPTION_EXPECT_USED), - LintId::of(&methods::RESULT_EXPECT_USED), LintId::of(&methods::UNWRAP_USED), LintId::of(&methods::WRONG_PUB_SELF_CONVENTION), LintId::of(&misc::FLOAT_CMP_CONST), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 1af4d03c7a2..2e75de019b6 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -78,61 +78,45 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for `.expect()` calls on `Option`s. + /// **What it does:** Checks for `.expect()` calls on `Option`s and `Result`s. /// - /// **Why is this bad?** Usually it is better to handle the `None` case. Still, - /// for a lot of quick-and-dirty code, `expect` is a good choice, which is why - /// this lint is `Allow` by default. + /// **Why is this bad?** Usually it is better to handle the `None` or `Err` case. + /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why + /// this lint is `Allow` by default. /// - /// **Known problems:** None. + /// `result.expect()` will let the thread panic on `Err` + /// values. Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. /// - /// **Example:** + /// **Known problems:** None. /// - /// Using expect on an `Option`: + /// **Examples:** + /// ```rust,ignore + /// # let opt = Some(1); /// - /// ```rust - /// let opt = Some(1); + /// // Bad /// opt.expect("one"); - /// ``` - /// - /// Better: /// - /// ```rust,ignore + /// // Good /// let opt = Some(1); /// opt?; /// ``` - pub OPTION_EXPECT_USED, - restriction, - "using `Option.expect()`, which might be better handled" -} - -declare_clippy_lint! { - /// **What it does:** Checks for `.expect()` calls on `Result`s. - /// - /// **Why is this bad?** `result.expect()` will let the thread panic on `Err` - /// values. Normally, you want to implement more sophisticated error handling, - /// and propagate errors upwards with `?` operator. - /// - /// **Known problems:** None. /// - /// **Example:** - /// Using expect on an `Result`: + /// // or /// /// ```rust - /// let res: Result = Ok(1); - /// res.expect("one"); - /// ``` + /// # let res: Result = Ok(1); /// - /// Better: + /// // Bad + /// res.expect("one"); /// - /// ```rust - /// let res: Result = Ok(1); + /// // Good /// res?; /// # Ok::<(), ()>(()) /// ``` - pub RESULT_EXPECT_USED, + pub EXPECT_USED, restriction, - "using `Result.expect()`, which might be better handled" + "using `.expect()` on `Result` or `Option`, which might be better handled" } declare_clippy_lint! { @@ -1251,8 +1235,7 @@ declare_clippy_lint! { declare_lint_pass!(Methods => [ UNWRAP_USED, - OPTION_EXPECT_USED, - RESULT_EXPECT_USED, + EXPECT_USED, SHOULD_IMPLEMENT_TRAIT, WRONG_SELF_CONVENTION, WRONG_PUB_SELF_CONVENTION, @@ -2407,9 +2390,9 @@ fn lint_expect(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, expect_args: &[hi let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&expect_args[0])); let mess = if is_type_diagnostic_item(cx, obj_ty, sym!(option_type)) { - Some((OPTION_EXPECT_USED, "an Option", "None")) + Some((EXPECT_USED, "an Option", "None")) } else if is_type_diagnostic_item(cx, obj_ty, sym!(result_type)) { - Some((RESULT_EXPECT_USED, "a Result", "Err")) + Some((EXPECT_USED, "a Result", "Err")) } else { None }; diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 5cbf3ef028c..4e79ce96bb5 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -514,6 +514,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "methods", }, + Lint { + name: "expect_used", + group: "restriction", + desc: "using `.expect()` on `Result` or `Option`, which might be better handled", + deprecation: None, + module: "methods", + }, Lint { name: "expl_impl_clone_on_copy", group: "pedantic", @@ -1599,13 +1606,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "option_env_unwrap", }, - Lint { - name: "option_expect_used", - group: "restriction", - desc: "using `Option.expect()`, which might be better handled", - deprecation: None, - module: "methods", - }, Lint { name: "option_map_or_none", group: "style", @@ -1865,13 +1865,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "matches", }, - Lint { - name: "result_expect_used", - group: "restriction", - desc: "using `Result.expect()`, which might be better handled", - deprecation: None, - module: "methods", - }, Lint { name: "result_map_or_into_option", group: "style", diff --git a/tests/ui/expect.rs b/tests/ui/expect.rs index 0bd4252c49a..1073acf6f0c 100644 --- a/tests/ui/expect.rs +++ b/tests/ui/expect.rs @@ -1,4 +1,4 @@ -#![warn(clippy::option_expect_used, clippy::result_expect_used)] +#![warn(clippy::expect_used)] fn expect_option() { let opt = Some(0); diff --git a/tests/ui/expect.stderr b/tests/ui/expect.stderr index adf9f4f1921..9d3fc7df15c 100644 --- a/tests/ui/expect.stderr +++ b/tests/ui/expect.stderr @@ -4,7 +4,7 @@ error: used `expect()` on `an Option` value LL | let _ = opt.expect(""); | ^^^^^^^^^^^^^^ | - = note: `-D clippy::option-expect-used` implied by `-D warnings` + = note: `-D clippy::expect-used` implied by `-D warnings` = help: if this value is an `None`, it will panic error: used `expect()` on `a Result` value @@ -13,7 +13,6 @@ error: used `expect()` on `a Result` value LL | let _ = res.expect(""); | ^^^^^^^^^^^^^^ | - = note: `-D clippy::result-expect-used` implied by `-D warnings` = help: if this value is an `Err`, it will panic error: aborting due to 2 previous errors -- cgit 1.4.1-3-g733a5 From adbdf7549c6b24c37629eabdc4be0346e0c8fd56 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 3 May 2020 15:16:00 +0200 Subject: Merge `for_loop_over_option` and `for_loop_over_result` lints into `for_loop_over_fallible` lint --- CHANGELOG.md | 3 +- clippy_lints/src/lib.rs | 9 ++-- clippy_lints/src/loops.rs | 64 +++++++++++-------------- src/lintlist/mod.rs | 11 +---- tests/ui/for_loop_over_fallible.rs | 57 +++++++++++++++++++++++ tests/ui/for_loop_over_fallible.stderr | 71 ++++++++++++++++++++++++++++ tests/ui/for_loop_over_option_result.rs | 59 ----------------------- tests/ui/for_loop_over_option_result.stderr | 72 ----------------------------- 8 files changed, 161 insertions(+), 185 deletions(-) create mode 100644 tests/ui/for_loop_over_fallible.rs create mode 100644 tests/ui/for_loop_over_fallible.stderr delete mode 100644 tests/ui/for_loop_over_option_result.rs delete mode 100644 tests/ui/for_loop_over_option_result.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eeb71fa5c5..3f9486e0972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1361,8 +1361,7 @@ Released 2018-09-13 [`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast [`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation [`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map -[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option -[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result +[`for_loop_over_fallible`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_fallible [`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref [`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index eaef1f543d3..8de94d19d31 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -615,8 +615,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &loops::EXPLICIT_INTO_ITER_LOOP, &loops::EXPLICIT_ITER_LOOP, &loops::FOR_KV_MAP, - &loops::FOR_LOOP_OVER_OPTION, - &loops::FOR_LOOP_OVER_RESULT, + &loops::FOR_LOOP_OVER_FALLIBLE, &loops::ITER_NEXT_LOOP, &loops::MANUAL_MEMCPY, &loops::MUT_RANGE_BOUND, @@ -1265,8 +1264,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::EXPLICIT_COUNTER_LOOP), LintId::of(&loops::FOR_KV_MAP), - LintId::of(&loops::FOR_LOOP_OVER_OPTION), - LintId::of(&loops::FOR_LOOP_OVER_RESULT), + LintId::of(&loops::FOR_LOOP_OVER_FALLIBLE), LintId::of(&loops::ITER_NEXT_LOOP), LintId::of(&loops::MANUAL_MEMCPY), LintId::of(&loops::MUT_RANGE_BOUND), @@ -1641,8 +1639,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), - LintId::of(&loops::FOR_LOOP_OVER_OPTION), - LintId::of(&loops::FOR_LOOP_OVER_RESULT), + LintId::of(&loops::FOR_LOOP_OVER_FALLIBLE), LintId::of(&loops::ITER_NEXT_LOOP), LintId::of(&loops::NEVER_LOOP), LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index 0bc6b70855b..da6793a69d6 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -168,7 +168,7 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for `for` loops over `Option` values. + /// **What it does:** Checks for `for` loops over `Option` or `Result` values. /// /// **Why is this bad?** Readability. This is more clearly expressed as an `if /// let`. @@ -176,47 +176,38 @@ declare_clippy_lint! { /// **Known problems:** None. /// /// **Example:** - /// ```ignore - /// for x in option { - /// .. + /// ```rust + /// # let opt = Some(1); + /// + /// // Bad + /// for x in opt { + /// // .. /// } - /// ``` /// - /// This should be - /// ```ignore - /// if let Some(x) = option { - /// .. + /// // Good + /// if let Some(x) = opt { + /// // .. /// } /// ``` - pub FOR_LOOP_OVER_OPTION, - correctness, - "for-looping over an `Option`, which is more clearly expressed as an `if let`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for `for` loops over `Result` values. /// - /// **Why is this bad?** Readability. This is more clearly expressed as an `if - /// let`. + /// // or /// - /// **Known problems:** None. + /// ```rust + /// # let res: Result = Ok(1); /// - /// **Example:** - /// ```ignore - /// for x in result { - /// .. + /// // Bad + /// for x in &res { + /// // .. /// } - /// ``` /// - /// This should be - /// ```ignore - /// if let Ok(x) = result { - /// .. + /// // Good + /// if let Ok(x) = res { + /// // .. /// } /// ``` - pub FOR_LOOP_OVER_RESULT, + pub FOR_LOOP_OVER_FALLIBLE, correctness, - "for-looping over a `Result`, which is more clearly expressed as an `if let`" + "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" } declare_clippy_lint! { @@ -435,8 +426,7 @@ declare_lint_pass!(Loops => [ EXPLICIT_ITER_LOOP, EXPLICIT_INTO_ITER_LOOP, ITER_NEXT_LOOP, - FOR_LOOP_OVER_RESULT, - FOR_LOOP_OVER_OPTION, + FOR_LOOP_OVER_FALLIBLE, WHILE_LET_LOOP, NEEDLESS_COLLECT, EXPLICIT_COUNTER_LOOP, @@ -1283,7 +1273,7 @@ fn check_for_loop_arg(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>, e ITER_NEXT_LOOP, expr.span, "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ - probably not what you want", + probably not what you want", ); next_loop_linted = true; } @@ -1300,11 +1290,11 @@ fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) { if is_type_diagnostic_item(cx, ty, sym!(option_type)) { span_lint_and_help( cx, - FOR_LOOP_OVER_OPTION, + FOR_LOOP_OVER_FALLIBLE, arg.span, &format!( "for loop over `{0}`, which is an `Option`. This is more readably written as an \ - `if let` statement.", + `if let` statement.", snippet(cx, arg.span, "_") ), None, @@ -1317,11 +1307,11 @@ fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) { } else if is_type_diagnostic_item(cx, ty, sym!(result_type)) { span_lint_and_help( cx, - FOR_LOOP_OVER_RESULT, + FOR_LOOP_OVER_FALLIBLE, arg.span, &format!( "for loop over `{0}`, which is a `Result`. This is more readably written as an \ - `if let` statement.", + `if let` statement.", snippet(cx, arg.span, "_") ), None, diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 4e79ce96bb5..0ea0f55a381 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -676,16 +676,9 @@ pub static ref ALL_LINTS: Vec = vec![ module: "loops", }, Lint { - name: "for_loop_over_option", + name: "for_loop_over_fallible", group: "correctness", - desc: "for-looping over an `Option`, which is more clearly expressed as an `if let`", - deprecation: None, - module: "loops", - }, - Lint { - name: "for_loop_over_result", - group: "correctness", - desc: "for-looping over a `Result`, which is more clearly expressed as an `if let`", + desc: "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`", deprecation: None, module: "loops", }, diff --git a/tests/ui/for_loop_over_fallible.rs b/tests/ui/for_loop_over_fallible.rs new file mode 100644 index 00000000000..e52468cdd4b --- /dev/null +++ b/tests/ui/for_loop_over_fallible.rs @@ -0,0 +1,57 @@ +#![warn(clippy::for_loop_over_fallible)] + +fn for_loop_over_fallible() { + let option = Some(1); + let result = option.ok_or("x not found"); + let v = vec![0, 1, 2]; + + // check over an `Option` + for x in option { + println!("{}", x); + } + + // check over a `Result` + for x in result { + println!("{}", x); + } + + for x in option.ok_or("x not found") { + println!("{}", x); + } + + // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call + // in the chain + for x in v.iter().next() { + println!("{}", x); + } + + // make sure we lint when next() is not the last call in the chain + for x in v.iter().next().and(Some(0)) { + println!("{}", x); + } + + for x in v.iter().next().ok_or("x not found") { + println!("{}", x); + } + + // check for false positives + + // for loop false positive + for x in v { + println!("{}", x); + } + + // while let false positive for Option + while let Some(x) = option { + println!("{}", x); + break; + } + + // while let false positive for Result + while let Ok(x) = result { + println!("{}", x); + break; + } +} + +fn main() {} diff --git a/tests/ui/for_loop_over_fallible.stderr b/tests/ui/for_loop_over_fallible.stderr new file mode 100644 index 00000000000..4ce9a144ad8 --- /dev/null +++ b/tests/ui/for_loop_over_fallible.stderr @@ -0,0 +1,71 @@ +error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_fallible.rs:9:14 + | +LL | for x in option { + | ^^^^^^ + | + = note: `-D clippy::for-loop-over-fallible` implied by `-D warnings` + = help: consider replacing `for x in option` with `if let Some(x) = option` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_fallible.rs:14:14 + | +LL | for x in result { + | ^^^^^^ + | + = help: consider replacing `for x in result` with `if let Ok(x) = result` + +error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_fallible.rs:18:14 + | +LL | for x in option.ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` + +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want + --> $DIR/for_loop_over_fallible.rs:24:14 + | +LL | for x in v.iter().next() { + | ^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::iter_next_loop)]` on by default + +error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_fallible.rs:29:14 + | +LL | for x in v.iter().next().and(Some(0)) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` + +error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_fallible.rs:33:14 + | +LL | for x in v.iter().next().ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` + +error: this loop never actually loops + --> $DIR/for_loop_over_fallible.rs:45:5 + | +LL | / while let Some(x) = option { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + | + = note: `#[deny(clippy::never_loop)]` on by default + +error: this loop never actually loops + --> $DIR/for_loop_over_fallible.rs:51:5 + | +LL | / while let Ok(x) = result { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + +error: aborting due to 8 previous errors + diff --git a/tests/ui/for_loop_over_option_result.rs b/tests/ui/for_loop_over_option_result.rs deleted file mode 100644 index 6b207b26b6b..00000000000 --- a/tests/ui/for_loop_over_option_result.rs +++ /dev/null @@ -1,59 +0,0 @@ -#![warn(clippy::for_loop_over_option, clippy::for_loop_over_result)] - -/// Tests for_loop_over_result and for_loop_over_option - -fn for_loop_over_option_and_result() { - let option = Some(1); - let result = option.ok_or("x not found"); - let v = vec![0, 1, 2]; - - // check FOR_LOOP_OVER_OPTION lint - for x in option { - println!("{}", x); - } - - // check FOR_LOOP_OVER_RESULT lint - for x in result { - println!("{}", x); - } - - for x in option.ok_or("x not found") { - println!("{}", x); - } - - // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call - // in the chain - for x in v.iter().next() { - println!("{}", x); - } - - // make sure we lint when next() is not the last call in the chain - for x in v.iter().next().and(Some(0)) { - println!("{}", x); - } - - for x in v.iter().next().ok_or("x not found") { - println!("{}", x); - } - - // check for false positives - - // for loop false positive - for x in v { - println!("{}", x); - } - - // while let false positive for Option - while let Some(x) = option { - println!("{}", x); - break; - } - - // while let false positive for Result - while let Ok(x) = result { - println!("{}", x); - break; - } -} - -fn main() {} diff --git a/tests/ui/for_loop_over_option_result.stderr b/tests/ui/for_loop_over_option_result.stderr deleted file mode 100644 index 194a0bfec5b..00000000000 --- a/tests/ui/for_loop_over_option_result.stderr +++ /dev/null @@ -1,72 +0,0 @@ -error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_option_result.rs:11:14 - | -LL | for x in option { - | ^^^^^^ - | - = note: `-D clippy::for-loop-over-option` implied by `-D warnings` - = help: consider replacing `for x in option` with `if let Some(x) = option` - -error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_option_result.rs:16:14 - | -LL | for x in result { - | ^^^^^^ - | - = note: `-D clippy::for-loop-over-result` implied by `-D warnings` - = help: consider replacing `for x in result` with `if let Ok(x) = result` - -error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_option_result.rs:20:14 - | -LL | for x in option.ok_or("x not found") { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` - -error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want - --> $DIR/for_loop_over_option_result.rs:26:14 - | -LL | for x in v.iter().next() { - | ^^^^^^^^^^^^^^^ - | - = note: `#[deny(clippy::iter_next_loop)]` on by default - -error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_option_result.rs:31:14 - | -LL | for x in v.iter().next().and(Some(0)) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` - -error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_option_result.rs:35:14 - | -LL | for x in v.iter().next().ok_or("x not found") { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` - -error: this loop never actually loops - --> $DIR/for_loop_over_option_result.rs:47:5 - | -LL | / while let Some(x) = option { -LL | | println!("{}", x); -LL | | break; -LL | | } - | |_____^ - | - = note: `#[deny(clippy::never_loop)]` on by default - -error: this loop never actually loops - --> $DIR/for_loop_over_option_result.rs:53:5 - | -LL | / while let Ok(x) = result { -LL | | println!("{}", x); -LL | | break; -LL | | } - | |_____^ - -error: aborting due to 8 previous errors - -- cgit 1.4.1-3-g733a5 From 95399f8f941b89785c6e9d94e0bc32ff5d43ba06 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 14 May 2020 09:57:36 -0700 Subject: Downgrade useless_let_if_seq to nursery --- clippy_lints/src/let_if_seq.rs | 2 +- clippy_lints/src/lib.rs | 3 +-- src/lintlist/mod.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index 398a3103a03..d7bf8a14768 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -50,7 +50,7 @@ declare_clippy_lint! { /// }; /// ``` pub USELESS_LET_IF_SEQ, - style, + nursery, "unidiomatic `let mut` declaration followed by initialization in `if`" } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0c4daeb731f..b241ac5559c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1266,7 +1266,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), - LintId::of(&let_if_seq::USELESS_LET_IF_SEQ), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(&lifetimes::NEEDLESS_LIFETIMES), @@ -1476,7 +1475,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inherent_to_string::INHERENT_TO_STRING), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), - LintId::of(&let_if_seq::USELESS_LET_IF_SEQ), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::FOR_KV_MAP), @@ -1728,6 +1726,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&floating_point_arithmetic::IMPRECISE_FLOPS), LintId::of(&floating_point_arithmetic::SUBOPTIMAL_FLOPS), LintId::of(&future_not_send::FUTURE_NOT_SEND), + LintId::of(&let_if_seq::USELESS_LET_IF_SEQ), LintId::of(&missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(&mutex_atomic::MUTEX_INTEGER), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index e1a6d4bdd31..e1c68b58b86 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -2469,7 +2469,7 @@ pub static ref ALL_LINTS: Vec = vec![ }, Lint { name: "useless_let_if_seq", - group: "style", + group: "nursery", desc: "unidiomatic `let mut` declaration followed by initialization in `if`", deprecation: None, module: "let_if_seq", -- cgit 1.4.1-3-g733a5 From 94e4b5ec316993200d75276b4e7c16a059bf3a57 Mon Sep 17 00:00:00 2001 From: Vardan Margaryan Date: Sun, 10 May 2020 00:08:41 +0300 Subject: Add the redundant_wildcard_enum_match lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/matches.rs | 48 ++++++++++++++++++++++ src/lintlist/mod.rs | 7 ++++ tests/ui/match_wildcard_for_single_variants.fixed | 18 ++++++++ tests/ui/match_wildcard_for_single_variants.rs | 18 ++++++++ tests/ui/match_wildcard_for_single_variants.stderr | 10 +++++ 7 files changed, 104 insertions(+) create mode 100644 tests/ui/match_wildcard_for_single_variants.fixed create mode 100644 tests/ui/match_wildcard_for_single_variants.rs create mode 100644 tests/ui/match_wildcard_for_single_variants.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index b25ef049356..d6298fec65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1439,6 +1439,7 @@ Released 2018-09-13 [`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms [`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding [`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm +[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants [`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter [`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum [`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0c4daeb731f..41046c18ed2 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -641,6 +641,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &matches::MATCH_OVERLAPPING_ARM, &matches::MATCH_REF_PATS, &matches::MATCH_SINGLE_BINDING, + &matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, &matches::MATCH_WILD_ERR_ARM, &matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, &matches::SINGLE_MATCH, @@ -1147,6 +1148,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(¯o_use::MACRO_USE_IMPORTS), LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), + LintId::of(&matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), LintId::of(&methods::FILTER_MAP_NEXT), diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 8f86535ef1e..42a6c416619 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -229,6 +229,40 @@ declare_clippy_lint! { "a wildcard enum match arm using `_`" } +declare_clippy_lint! { + /// **What it does:** Checks for wildcard enum matches for a single variant. + /// + /// **Why is this bad?** New enum variants added by library updates can be missed. + /// + /// **Known problems:** Suggested replacements may not use correct path to enum + /// if it's not present in the current scope. + /// + /// **Example:** + /// + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// _ => {}, + /// } + /// ``` + /// Use instead: + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// Foo::C => {}, + /// } + /// ``` + pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + pedantic, + "a wildcard enum match for a single variant" +} + declare_clippy_lint! { /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm. /// @@ -356,6 +390,7 @@ impl_lint_pass!(Matches => [ MATCH_WILD_ERR_ARM, MATCH_AS_REF, WILDCARD_ENUM_MATCH_ARM, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_IN_OR_PATTERNS, MATCH_SINGLE_BINDING, INFALLIBLE_DESTRUCTURING_MATCH, @@ -766,6 +801,19 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ } } + if suggestion.len() == 1 { + // No need to check for non-exhaustive enum as in that case len would be greater than 1 + span_lint_and_sugg( + cx, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + wildcard_span, + message, + "try this", + suggestion[0].clone(), + Applicability::MachineApplicable, + ) + }; + span_lint_and_sugg( cx, WILDCARD_ENUM_MATCH_ARM, diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index e1a6d4bdd31..250a7c09f78 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1200,6 +1200,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "matches", }, + Lint { + name: "match_wildcard_for_single_variants", + group: "pedantic", + desc: "a wildcard enum match for a single variant", + deprecation: None, + module: "matches", + }, Lint { name: "maybe_infinite_iter", group: "pedantic", diff --git a/tests/ui/match_wildcard_for_single_variants.fixed b/tests/ui/match_wildcard_for_single_variants.fixed new file mode 100644 index 00000000000..5f1a559f591 --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +fn main() { + match Foo::A { + Foo::A => {}, + Foo::B => {}, + Foo::C => {}, + } +} diff --git a/tests/ui/match_wildcard_for_single_variants.rs b/tests/ui/match_wildcard_for_single_variants.rs new file mode 100644 index 00000000000..1159f9e722d --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +fn main() { + match Foo::A { + Foo::A => {}, + Foo::B => {}, + _ => {}, + } +} diff --git a/tests/ui/match_wildcard_for_single_variants.stderr b/tests/ui/match_wildcard_for_single_variants.stderr new file mode 100644 index 00000000000..128dd4808bf --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.stderr @@ -0,0 +1,10 @@ +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:16:9 + | +LL | _ => {}, + | ^ help: try this: `Foo::C` + | + = note: `-D clippy::match-wildcard-for-single-variants` implied by `-D warnings` + +error: aborting due to previous error + -- cgit 1.4.1-3-g733a5 From 93386563f66823ac7d10641c007b0bbc23ab09e6 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 3 May 2020 19:58:27 +0200 Subject: Rename lint `map_unwrap` to `map_unwrap_or` and register lints as renamed --- CHANGELOG.md | 3 +- clippy_lints/src/lib.rs | 15 ++- clippy_lints/src/methods/mod.rs | 8 +- clippy_lints/src/methods/option_map_unwrap_or.rs | 4 +- src/lintlist/mod.rs | 9 +- tests/ui/map_unwrap.rs | 99 -------------- tests/ui/map_unwrap.stderr | 161 ----------------------- tests/ui/map_unwrap_or.rs | 99 ++++++++++++++ tests/ui/map_unwrap_or.stderr | 161 +++++++++++++++++++++++ 9 files changed, 281 insertions(+), 278 deletions(-) delete mode 100644 tests/ui/map_unwrap.rs delete mode 100644 tests/ui/map_unwrap.stderr create mode 100644 tests/ui/map_unwrap_or.rs create mode 100644 tests/ui/map_unwrap_or.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f9486e0972..77272f4f78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1430,7 +1430,7 @@ Released 2018-09-13 [`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone [`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry [`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten -[`map_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap +[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or [`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref [`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool [`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items @@ -1538,7 +1538,6 @@ Released 2018-09-13 [`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs [`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn -[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8de94d19d31..ff67ccae794 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -673,7 +673,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::ITER_SKIP_NEXT, &methods::MANUAL_SATURATING_ARITHMETIC, &methods::MAP_FLATTEN, - &methods::MAP_UNWRAP, + &methods::MAP_UNWRAP_OR, &methods::NEW_RET_NO_SELF, &methods::OK_EXPECT, &methods::OPTION_AND_THEN_SOME, @@ -1145,7 +1145,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::FIND_MAP), LintId::of(&methods::INEFFICIENT_TO_STRING), LintId::of(&methods::MAP_FLATTEN), - LintId::of(&methods::MAP_UNWRAP), + LintId::of(&methods::MAP_UNWRAP_OR), LintId::of(&misc::USED_UNDERSCORE_BINDING), LintId::of(&misc_early::UNSEPARATED_LITERAL_SUFFIX), LintId::of(&mut_mut::MUT_MUT), @@ -1785,6 +1785,17 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"); + ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::block_in_if_condition"); + ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::block_in_if_condition"); + ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"); + ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"); + ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"); + ls.register_renamed("clippy::option_unwrap_used", "clippy::unwrap_used"); + ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used"); + ls.register_renamed("clippy::option_expect_used", "clippy::expect_used"); + ls.register_renamed("clippy::result_expect_used", "clippy::expect_used"); + ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loop_over_fallible"); + ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loop_over_fallible"); } // only exists to let the dogfood integration test works. diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 2e75de019b6..e6094edc5d7 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -255,7 +255,7 @@ declare_clippy_lint! { /// // Good /// x.map_or_else(some_function, |a| a + 1); /// ``` - pub MAP_UNWRAP, + pub MAP_UNWRAP_OR, pedantic, "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`" } @@ -1240,7 +1240,7 @@ declare_lint_pass!(Methods => [ WRONG_SELF_CONVENTION, WRONG_PUB_SELF_CONVENTION, OK_EXPECT, - MAP_UNWRAP, + MAP_UNWRAP_OR, RESULT_MAP_OR_INTO_OPTION, OPTION_MAP_OR_NONE, OPTION_AND_THEN_SOME, @@ -2512,7 +2512,7 @@ fn lint_map_unwrap_or_else<'a, 'tcx>( if same_span && !multiline { span_lint_and_note( cx, - MAP_UNWRAP, + MAP_UNWRAP_OR, expr.span, msg, None, @@ -2522,7 +2522,7 @@ fn lint_map_unwrap_or_else<'a, 'tcx>( ), ); } else if same_span && multiline { - span_lint(cx, MAP_UNWRAP, expr.span, msg); + span_lint(cx, MAP_UNWRAP_OR, expr.span, msg); }; } } diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index fcaa9b47e64..20c60ef3318 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -9,7 +9,7 @@ use rustc_middle::hir::map::Map; use rustc_span::source_map::Span; use rustc_span::symbol::Symbol; -use super::MAP_UNWRAP; +use super::MAP_UNWRAP_OR; /// lint use of `map().unwrap_or()` for `Option`s pub(super) fn lint<'a, 'tcx>( @@ -66,7 +66,7 @@ pub(super) fn lint<'a, 'tcx>( arg, suggest ); - span_lint_and_then(cx, MAP_UNWRAP, expr.span, msg, |diag| { + span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { let map_arg_span = map_args[1].span; let mut suggestion = vec![ diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 0ea0f55a381..e90b9c15747 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1138,7 +1138,7 @@ pub static ref ALL_LINTS: Vec = vec![ module: "methods", }, Lint { - name: "map_unwrap", + name: "map_unwrap_or", group: "pedantic", desc: "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`", deprecation: None, @@ -1872,13 +1872,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "map_unit_fn", }, - Lint { - name: "result_unwrap_used", - group: "restriction", - desc: "using `Result.unwrap()`, which might be better handled", - deprecation: None, - module: "methods", - }, Lint { name: "reversed_empty_ranges", group: "correctness", diff --git a/tests/ui/map_unwrap.rs b/tests/ui/map_unwrap.rs deleted file mode 100644 index 53e50368231..00000000000 --- a/tests/ui/map_unwrap.rs +++ /dev/null @@ -1,99 +0,0 @@ -// FIXME: Add "run-rustfix" once it's supported for multipart suggestions -// aux-build:option_helpers.rs - -#![warn(clippy::map_unwrap)] - -#[macro_use] -extern crate option_helpers; - -use std::collections::HashMap; - -#[rustfmt::skip] -fn option_methods() { - let opt = Some(1); - - // Check for `option.map(_).unwrap_or(_)` use. - // Single line case. - let _ = opt.map(|x| x + 1) - // Should lint even though this call is on a separate line. - .unwrap_or(0); - // Multi-line cases. - let _ = opt.map(|x| { - x + 1 - } - ).unwrap_or(0); - let _ = opt.map(|x| x + 1) - .unwrap_or({ - 0 - }); - // Single line `map(f).unwrap_or(None)` case. - let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); - // Multi-line `map(f).unwrap_or(None)` cases. - let _ = opt.map(|x| { - Some(x + 1) - } - ).unwrap_or(None); - let _ = opt - .map(|x| Some(x + 1)) - .unwrap_or(None); - // macro case - let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint - - // Should not lint if not copyable - let id: String = "identifier".to_string(); - let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id); - // ...but DO lint if the `unwrap_or` argument is not used in the `map` - let id: String = "identifier".to_string(); - let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); - - // Check for `option.map(_).unwrap_or_else(_)` use. - // single line case - let _ = opt.map(|x| x + 1) - // Should lint even though this call is on a separate line. - .unwrap_or_else(|| 0); - // Multi-line cases. - let _ = opt.map(|x| { - x + 1 - } - ).unwrap_or_else(|| 0); - let _ = opt.map(|x| x + 1) - .unwrap_or_else(|| - 0 - ); - // Macro case. - // Should not lint. - let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); - - // Issue #4144 - { - let mut frequencies = HashMap::new(); - let word = "foo"; - - frequencies - .get_mut(word) - .map(|count| { - *count += 1; - }) - .unwrap_or_else(|| { - frequencies.insert(word.to_owned(), 1); - }); - } -} - -fn result_methods() { - let res: Result = Ok(1); - - // Check for `result.map(_).unwrap_or_else(_)` use. - // single line case - let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line - // multi line cases - let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - // macro case - let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|e| 0); // should not lint -} - -fn main() { - option_methods(); - result_methods(); -} diff --git a/tests/ui/map_unwrap.stderr b/tests/ui/map_unwrap.stderr deleted file mode 100644 index 2610923275d..00000000000 --- a/tests/ui/map_unwrap.stderr +++ /dev/null @@ -1,161 +0,0 @@ -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/map_unwrap.rs:17:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | // Should lint even though this call is on a separate line. -LL | | .unwrap_or(0); - | |_____________________^ - | - = note: `-D clippy::map-unwrap` implied by `-D warnings` -help: use `map_or(a, f)` instead - | -LL | let _ = opt.map_or(0, |x| x + 1); - | ^^^^^^ ^^ -- - -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/map_unwrap.rs:21:13 - | -LL | let _ = opt.map(|x| { - | _____________^ -LL | | x + 1 -LL | | } -LL | | ).unwrap_or(0); - | |__________________^ - | -help: use `map_or(a, f)` instead - | -LL | let _ = opt.map_or(0, |x| { -LL | x + 1 -LL | } -LL | ); - | - -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/map_unwrap.rs:25:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | .unwrap_or({ -LL | | 0 -LL | | }); - | |__________^ - | -help: use `map_or(a, f)` instead - | -LL | let _ = opt.map_or({ -LL | 0 -LL | }, |x| x + 1); - | - -error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead - --> $DIR/map_unwrap.rs:30:13 - | -LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use `and_then(f)` instead - | -LL | let _ = opt.and_then(|x| Some(x + 1)); - | ^^^^^^^^ -- - -error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead - --> $DIR/map_unwrap.rs:32:13 - | -LL | let _ = opt.map(|x| { - | _____________^ -LL | | Some(x + 1) -LL | | } -LL | | ).unwrap_or(None); - | |_____________________^ - | -help: use `and_then(f)` instead - | -LL | let _ = opt.and_then(|x| { -LL | Some(x + 1) -LL | } -LL | ); - | - -error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead - --> $DIR/map_unwrap.rs:36:13 - | -LL | let _ = opt - | _____________^ -LL | | .map(|x| Some(x + 1)) -LL | | .unwrap_or(None); - | |________________________^ - | -help: use `and_then(f)` instead - | -LL | .and_then(|x| Some(x + 1)); - | ^^^^^^^^ -- - -error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead - --> $DIR/map_unwrap.rs:47:13 - | -LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use `map_or(a, f)` instead - | -LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); - | ^^^^^^ ^^^ -- - -error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead - --> $DIR/map_unwrap.rs:51:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | // Should lint even though this call is on a separate line. -LL | | .unwrap_or_else(|| 0); - | |_____________________________^ - | - = note: replace `map(|x| x + 1).unwrap_or_else(|| 0)` with `map_or_else(|| 0, |x| x + 1)` - -error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead - --> $DIR/map_unwrap.rs:55:13 - | -LL | let _ = opt.map(|x| { - | _____________^ -LL | | x + 1 -LL | | } -LL | | ).unwrap_or_else(|| 0); - | |__________________________^ - -error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead - --> $DIR/map_unwrap.rs:59:13 - | -LL | let _ = opt.map(|x| x + 1) - | _____________^ -LL | | .unwrap_or_else(|| -LL | | 0 -LL | | ); - | |_________^ - -error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead - --> $DIR/map_unwrap.rs:88:13 - | -LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` - -error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead - --> $DIR/map_unwrap.rs:90:13 - | -LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` - -error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead - --> $DIR/map_unwrap.rs:91:13 - | -LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` - -error: aborting due to 13 previous errors - diff --git a/tests/ui/map_unwrap_or.rs b/tests/ui/map_unwrap_or.rs new file mode 100644 index 00000000000..585944032e7 --- /dev/null +++ b/tests/ui/map_unwrap_or.rs @@ -0,0 +1,99 @@ +// FIXME: Add "run-rustfix" once it's supported for multipart suggestions +// aux-build:option_helpers.rs + +#![warn(clippy::map_unwrap_or)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::HashMap; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or(_)` use. + // Single line case. + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or(0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or(0); + let _ = opt.map(|x| x + 1) + .unwrap_or({ + 0 + }); + // Single line `map(f).unwrap_or(None)` case. + let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + // Multi-line `map(f).unwrap_or(None)` cases. + let _ = opt.map(|x| { + Some(x + 1) + } + ).unwrap_or(None); + let _ = opt + .map(|x| Some(x + 1)) + .unwrap_or(None); + // macro case + let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint + + // Should not lint if not copyable + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id); + // ...but DO lint if the `unwrap_or` argument is not used in the `map` + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + + // Check for `option.map(_).unwrap_or_else(_)` use. + // single line case + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or_else(|| 0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or_else(|| 0); + let _ = opt.map(|x| x + 1) + .unwrap_or_else(|| + 0 + ); + // Macro case. + // Should not lint. + let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); + + // Issue #4144 + { + let mut frequencies = HashMap::new(); + let word = "foo"; + + frequencies + .get_mut(word) + .map(|count| { + *count += 1; + }) + .unwrap_or_else(|| { + frequencies.insert(word.to_owned(), 1); + }); + } +} + +fn result_methods() { + let res: Result = Ok(1); + + // Check for `result.map(_).unwrap_or_else(_)` use. + // single line case + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line + // multi line cases + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + // macro case + let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|e| 0); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/tests/ui/map_unwrap_or.stderr b/tests/ui/map_unwrap_or.stderr new file mode 100644 index 00000000000..b62080a073f --- /dev/null +++ b/tests/ui/map_unwrap_or.stderr @@ -0,0 +1,161 @@ +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap_or.rs:17:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or(0); + | |_____________________^ + | + = note: `-D clippy::map-unwrap-or` implied by `-D warnings` +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or(0, |x| x + 1); + | ^^^^^^ ^^ -- + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap_or.rs:21:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or(0); + | |__________________^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or(0, |x| { +LL | x + 1 +LL | } +LL | ); + | + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap_or.rs:25:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or({ +LL | | 0 +LL | | }); + | |__________^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or({ +LL | 0 +LL | }, |x| x + 1); + | + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/map_unwrap_or.rs:30:13 + | +LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `and_then(f)` instead + | +LL | let _ = opt.and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/map_unwrap_or.rs:32:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | Some(x + 1) +LL | | } +LL | | ).unwrap_or(None); + | |_____________________^ + | +help: use `and_then(f)` instead + | +LL | let _ = opt.and_then(|x| { +LL | Some(x + 1) +LL | } +LL | ); + | + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/map_unwrap_or.rs:36:13 + | +LL | let _ = opt + | _____________^ +LL | | .map(|x| Some(x + 1)) +LL | | .unwrap_or(None); + | |________________________^ + | +help: use `and_then(f)` instead + | +LL | .and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/map_unwrap_or.rs:47:13 + | +LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); + | ^^^^^^ ^^^ -- + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/map_unwrap_or.rs:51:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or_else(|| 0); + | |_____________________________^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|| 0)` with `map_or_else(|| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/map_unwrap_or.rs:55:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or_else(|| 0); + | |__________________________^ + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/map_unwrap_or.rs:59:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or_else(|| +LL | | 0 +LL | | ); + | |_________^ + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/map_unwrap_or.rs:88:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/map_unwrap_or.rs:90:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/map_unwrap_or.rs:91:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: aborting due to 13 previous errors + -- cgit 1.4.1-3-g733a5 From ab87f87ba03518da23ca510249aa3f5908a42368 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 15 May 2020 18:20:07 +0200 Subject: Fix CHANGELOG.md and lint names plural --- CHANGELOG.md | 22 ++-- clippy_lints/src/block_in_if_condition.rs | 145 ------------------------ clippy_lints/src/blocks_in_if_conditions.rs | 145 ++++++++++++++++++++++++ clippy_lints/src/lib.rs | 24 ++-- clippy_lints/src/loops.rs | 8 +- src/lintlist/mod.rs | 6 +- tests/ui/block_in_if_condition.fixed | 74 ------------ tests/ui/block_in_if_condition.rs | 74 ------------ tests/ui/block_in_if_condition.stderr | 34 ------ tests/ui/block_in_if_condition_closure.rs | 47 -------- tests/ui/block_in_if_condition_closure.stderr | 24 ---- tests/ui/blocks_in_if_conditions.fixed | 74 ++++++++++++ tests/ui/blocks_in_if_conditions.rs | 74 ++++++++++++ tests/ui/blocks_in_if_conditions.stderr | 34 ++++++ tests/ui/blocks_in_if_conditions_closure.rs | 47 ++++++++ tests/ui/blocks_in_if_conditions_closure.stderr | 24 ++++ tests/ui/for_loop_over_fallible.rs | 57 ---------- tests/ui/for_loop_over_fallible.stderr | 71 ------------ tests/ui/for_loops_over_fallibles.rs | 57 ++++++++++ tests/ui/for_loops_over_fallibles.stderr | 71 ++++++++++++ 20 files changed, 556 insertions(+), 556 deletions(-) delete mode 100644 clippy_lints/src/block_in_if_condition.rs create mode 100644 clippy_lints/src/blocks_in_if_conditions.rs delete mode 100644 tests/ui/block_in_if_condition.fixed delete mode 100644 tests/ui/block_in_if_condition.rs delete mode 100644 tests/ui/block_in_if_condition.stderr delete mode 100644 tests/ui/block_in_if_condition_closure.rs delete mode 100644 tests/ui/block_in_if_condition_closure.stderr create mode 100644 tests/ui/blocks_in_if_conditions.fixed create mode 100644 tests/ui/blocks_in_if_conditions.rs create mode 100644 tests/ui/blocks_in_if_conditions.stderr create mode 100644 tests/ui/blocks_in_if_conditions_closure.rs create mode 100644 tests/ui/blocks_in_if_conditions_closure.stderr delete mode 100644 tests/ui/for_loop_over_fallible.rs delete mode 100644 tests/ui/for_loop_over_fallible.stderr create mode 100644 tests/ui/for_loops_over_fallibles.rs create mode 100644 tests/ui/for_loops_over_fallibles.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 77272f4f78b..d05819a973a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -198,7 +198,7 @@ Released 2020-03-12 ### Suggestion Improvements -* [`option_map_unwrap_or`] [#4634](https://github.com/rust-lang/rust-clippy/pull/4634) +* `option_map_unwrap_or` [#4634](https://github.com/rust-lang/rust-clippy/pull/4634) * [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934) * [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935) * [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956) @@ -282,8 +282,8 @@ Released 2019-12-19 * [`panic`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) * [`unreachable`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) * [`todo`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) - * [`option_expect_used`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) - * [`result_expect_used`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * `option_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * `result_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) * Move `redundant_clone` to perf group [#4509](https://github.com/rust-lang/rust-clippy/pull/4509) * Move `manual_mul_add` to nursery group [#4736](https://github.com/rust-lang/rust-clippy/pull/4736) * Expand `unit_cmp` to also work with `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` [#4613](https://github.com/rust-lang/rust-clippy/pull/4613) @@ -395,7 +395,7 @@ Released 2019-08-15 * Fix false positive in [`useless_attribute`] [#4107](https://github.com/rust-lang/rust-clippy/pull/4107) * Fix incorrect suggestion for [`float_cmp`] [#4214](https://github.com/rust-lang/rust-clippy/pull/4214) * Add suggestions for [`print_with_newline`] and [`write_with_newline`] [#4136](https://github.com/rust-lang/rust-clippy/pull/4136) -* Improve suggestions for [`option_map_unwrap_or_else`] and [`result_map_unwrap_or_else`] [#4164](https://github.com/rust-lang/rust-clippy/pull/4164) +* Improve suggestions for `option_map_unwrap_or_else` and `result_map_unwrap_or_else` [#4164](https://github.com/rust-lang/rust-clippy/pull/4164) * Improve suggestions for [`non_ascii_literal`] [#4119](https://github.com/rust-lang/rust-clippy/pull/4119) * Improve diagnostics for [`let_and_return`] [#4137](https://github.com/rust-lang/rust-clippy/pull/4137) * Improve diagnostics for [`trivially_copy_pass_by_ref`] [#4071](https://github.com/rust-lang/rust-clippy/pull/4071) @@ -448,7 +448,7 @@ Released 2019-05-20 * Fix false positive in [`needless_range_loop`] pertaining to structs without a `.iter()` * Fix false positive in [`bool_comparison`] pertaining to non-bool types * Fix false positive in [`redundant_closure`] pertaining to differences in borrows -* Fix false positive in [`option_map_unwrap_or`] on non-copy types +* Fix false positive in `option_map_unwrap_or` on non-copy types * Fix false positives in [`missing_const_for_fn`] pertaining to macros and trait method impls * Fix false positive in [`needless_pass_by_value`] pertaining to procedural macros * Fix false positive in [`needless_continue`] pertaining to loop labels @@ -794,7 +794,7 @@ Released 2018-09-13 ## 0.0.169 * Rustup to *rustc 1.23.0-nightly (3b82e4c74 2017-11-05)* -* New lints: [`just_underscores_and_digits`], [`result_map_unwrap_or_else`], [`transmute_bytes_to_str`] +* New lints: [`just_underscores_and_digits`], `result_map_unwrap_or_else`, [`transmute_bytes_to_str`] ## 0.0.168 * Rustup to *rustc 1.23.0-nightly (f0fe716db 2017-10-30)* @@ -1068,7 +1068,7 @@ Released 2018-09-13 ## 0.0.93 — 2016-10-03 * Rustup to *rustc 1.14.0-nightly (144af3e97 2016-10-02)* -* [`option_map_unwrap_or`] and [`option_map_unwrap_or_else`] are now +* `option_map_unwrap_or` and `option_map_unwrap_or_else` are now allowed by default. * New lint: [`explicit_into_iter_loop`] @@ -1087,8 +1087,8 @@ Released 2018-09-13 ## 0.0.88 — 2016-09-04 * Rustup to *rustc 1.13.0-nightly (70598e04f 2016-09-03)* * The following lints are not new but were only usable through the `clippy` - lint groups: [`filter_next`], [`for_loop_over_option`], - [`for_loop_over_result`] and [`match_overlapping_arm`]. You should now be + lint groups: [`filter_next`], `for_loop_over_option`, + `for_loop_over_result` and [`match_overlapping_arm`]. You should now be able to `#[allow/deny]` them individually and they are available directly through `cargo clippy`. @@ -1274,7 +1274,7 @@ Released 2018-09-13 [`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock [`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask [`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name -[`block_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition +[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions [`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison [`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const [`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box @@ -1361,7 +1361,7 @@ Released 2018-09-13 [`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast [`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation [`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map -[`for_loop_over_fallible`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_fallible +[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles [`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref [`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send diff --git a/clippy_lints/src/block_in_if_condition.rs b/clippy_lints/src/block_in_if_condition.rs deleted file mode 100644 index 8a5e595749f..00000000000 --- a/clippy_lints/src/block_in_if_condition.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::utils::{differing_macro_contexts, higher, snippet_block_with_applicability, span_lint, span_lint_and_sugg}; -use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{BlockCheckMode, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; -use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// **What it does:** Checks for `if` conditions that use blocks containing an - /// expression, statements or conditions that use closures with blocks. - /// - /// **Why is this bad?** Style, using blocks in the condition makes it hard to read. - /// - /// **Known problems:** None. - /// - /// **Examples:** - /// ```rust - /// // Bad - /// if { true } { /* ... */ } - /// - /// // Good - /// if true { /* ... */ } - /// ``` - /// - /// // or - /// - /// ```rust - /// # fn somefunc() -> bool { true }; - /// - /// // Bad - /// if { let x = somefunc(); x } { /* ... */ } - /// - /// // Good - /// let res = { let x = somefunc(); x }; - /// if res { /* ... */ } - /// ``` - pub BLOCK_IN_IF_CONDITION, - style, - "useless or complex blocks that can be eliminated in conditions" -} - -declare_lint_pass!(BlockInIfCondition => [BLOCK_IN_IF_CONDITION]); - -struct ExVisitor<'a, 'tcx> { - found_block: Option<&'tcx Expr<'tcx>>, - cx: &'a LateContext<'a, 'tcx>, -} - -impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if let ExprKind::Closure(_, _, eid, _, _) = expr.kind { - let body = self.cx.tcx.hir().body(eid); - let ex = &body.value; - if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() { - self.found_block = Some(ex); - return; - } - } - walk_expr(self, expr); - } - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition"; -const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \ - instead, move the block or closure higher and bind it with a `let`"; - -impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition { - fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - if let Some((cond, _, _)) = higher::if_block(&expr) { - if let ExprKind::Block(block, _) = &cond.kind { - if block.rules == BlockCheckMode::DefaultBlock { - if block.stmts.is_empty() { - if let Some(ex) = &block.expr { - // don't dig into the expression here, just suggest that they remove - // the block - if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) { - return; - } - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BLOCK_IN_IF_CONDITION, - cond.span, - BRACED_EXPR_MESSAGE, - "try", - format!( - "{}", - snippet_block_with_applicability( - cx, - ex.span, - "..", - Some(expr.span), - &mut applicability - ) - ), - applicability, - ); - } - } else { - let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span); - if span.from_expansion() || differing_macro_contexts(expr.span, span) { - return; - } - // move block higher - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BLOCK_IN_IF_CONDITION, - expr.span.with_hi(cond.span.hi()), - COMPLEX_BLOCK_MESSAGE, - "try", - format!( - "let res = {}; if res", - snippet_block_with_applicability( - cx, - block.span, - "..", - Some(expr.span), - &mut applicability - ), - ), - applicability, - ); - } - } - } else { - let mut visitor = ExVisitor { found_block: None, cx }; - walk_expr(&mut visitor, cond); - if let Some(block) = visitor.found_block { - span_lint(cx, BLOCK_IN_IF_CONDITION, block.span, COMPLEX_BLOCK_MESSAGE); - } - } - } - } -} diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs new file mode 100644 index 00000000000..8fa9b05ca32 --- /dev/null +++ b/clippy_lints/src/blocks_in_if_conditions.rs @@ -0,0 +1,145 @@ +use crate::utils::{differing_macro_contexts, higher, snippet_block_with_applicability, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BlockCheckMode, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `if` conditions that use blocks containing an + /// expression, statements or conditions that use closures with blocks. + /// + /// **Why is this bad?** Style, using blocks in the condition makes it hard to read. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// // Bad + /// if { true } { /* ... */ } + /// + /// // Good + /// if true { /* ... */ } + /// ``` + /// + /// // or + /// + /// ```rust + /// # fn somefunc() -> bool { true }; + /// + /// // Bad + /// if { let x = somefunc(); x } { /* ... */ } + /// + /// // Good + /// let res = { let x = somefunc(); x }; + /// if res { /* ... */ } + /// ``` + pub BLOCKS_IN_IF_CONDITIONS, + style, + "useless or complex blocks that can be eliminated in conditions" +} + +declare_lint_pass!(BlocksInIfConditions => [BLOCKS_IN_IF_CONDITIONS]); + +struct ExVisitor<'a, 'tcx> { + found_block: Option<&'tcx Expr<'tcx>>, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Closure(_, _, eid, _, _) = expr.kind { + let body = self.cx.tcx.hir().body(eid); + let ex = &body.value; + if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() { + self.found_block = Some(ex); + return; + } + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition"; +const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \ + instead, move the block or closure higher and bind it with a `let`"; + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlocksInIfConditions { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + if let Some((cond, _, _)) = higher::if_block(&expr) { + if let ExprKind::Block(block, _) = &cond.kind { + if block.rules == BlockCheckMode::DefaultBlock { + if block.stmts.is_empty() { + if let Some(ex) = &block.expr { + // don't dig into the expression here, just suggest that they remove + // the block + if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) { + return; + } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BLOCKS_IN_IF_CONDITIONS, + cond.span, + BRACED_EXPR_MESSAGE, + "try", + format!( + "{}", + snippet_block_with_applicability( + cx, + ex.span, + "..", + Some(expr.span), + &mut applicability + ) + ), + applicability, + ); + } + } else { + let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span); + if span.from_expansion() || differing_macro_contexts(expr.span, span) { + return; + } + // move block higher + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BLOCKS_IN_IF_CONDITIONS, + expr.span.with_hi(cond.span.hi()), + COMPLEX_BLOCK_MESSAGE, + "try", + format!( + "let res = {}; if res", + snippet_block_with_applicability( + cx, + block.span, + "..", + Some(expr.span), + &mut applicability + ), + ), + applicability, + ); + } + } + } else { + let mut visitor = ExVisitor { found_block: None, cx }; + walk_expr(&mut visitor, cond); + if let Some(block) = visitor.found_block { + span_lint(cx, BLOCKS_IN_IF_CONDITIONS, block.span, COMPLEX_BLOCK_MESSAGE); + } + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ff67ccae794..eba4ab5056b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -180,7 +180,7 @@ mod attrs; mod await_holding_lock; mod bit_mask; mod blacklisted_name; -mod block_in_if_condition; +mod blocks_in_if_conditions; mod booleans; mod bytecount; mod cargo_common_metadata; @@ -507,7 +507,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &bit_mask::INEFFECTIVE_BIT_MASK, &bit_mask::VERBOSE_BIT_MASK, &blacklisted_name::BLACKLISTED_NAME, - &block_in_if_condition::BLOCK_IN_IF_CONDITION, + &blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS, &booleans::LOGIC_BUG, &booleans::NONMINIMAL_BOOL, &bytecount::NAIVE_BYTECOUNT, @@ -615,7 +615,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &loops::EXPLICIT_INTO_ITER_LOOP, &loops::EXPLICIT_ITER_LOOP, &loops::FOR_KV_MAP, - &loops::FOR_LOOP_OVER_FALLIBLE, + &loops::FOR_LOOPS_OVER_FALLIBLES, &loops::ITER_NEXT_LOOP, &loops::MANUAL_MEMCPY, &loops::MUT_RANGE_BOUND, @@ -894,7 +894,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box mut_reference::UnnecessaryMutPassed); store.register_late_pass(|| box len_zero::LenZero); store.register_late_pass(|| box attrs::Attributes); - store.register_late_pass(|| box block_in_if_condition::BlockInIfCondition); + store.register_late_pass(|| box blocks_in_if_conditions::BlocksInIfConditions); store.register_late_pass(|| box unicode::Unicode); store.register_late_pass(|| box strings::StringAdd); store.register_late_pass(|| box implicit_return::ImplicitReturn); @@ -1199,7 +1199,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(&bit_mask::VERBOSE_BIT_MASK), LintId::of(&blacklisted_name::BLACKLISTED_NAME), - LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION), + LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(&booleans::LOGIC_BUG), LintId::of(&booleans::NONMINIMAL_BOOL), LintId::of(&bytecount::NAIVE_BYTECOUNT), @@ -1264,7 +1264,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::EXPLICIT_COUNTER_LOOP), LintId::of(&loops::FOR_KV_MAP), - LintId::of(&loops::FOR_LOOP_OVER_FALLIBLE), + LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(&loops::ITER_NEXT_LOOP), LintId::of(&loops::MANUAL_MEMCPY), LintId::of(&loops::MUT_RANGE_BOUND), @@ -1444,7 +1444,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&attrs::UNKNOWN_CLIPPY_LINTS), LintId::of(&bit_mask::VERBOSE_BIT_MASK), LintId::of(&blacklisted_name::BLACKLISTED_NAME), - LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION), + LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(&collapsible_if::COLLAPSIBLE_IF), LintId::of(&comparison_chain::COMPARISON_CHAIN), LintId::of(&doc::MISSING_SAFETY_DOC), @@ -1639,7 +1639,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), - LintId::of(&loops::FOR_LOOP_OVER_FALLIBLE), + LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(&loops::ITER_NEXT_LOOP), LintId::of(&loops::NEVER_LOOP), LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), @@ -1785,8 +1785,8 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"); - ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::block_in_if_condition"); - ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::block_in_if_condition"); + ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"); + ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"); ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"); ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"); ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"); @@ -1794,8 +1794,8 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used"); ls.register_renamed("clippy::option_expect_used", "clippy::expect_used"); ls.register_renamed("clippy::result_expect_used", "clippy::expect_used"); - ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loop_over_fallible"); - ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loop_over_fallible"); + ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles"); + ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"); } // only exists to let the dogfood integration test works. diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index da6793a69d6..9c9d1a84003 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -205,7 +205,7 @@ declare_clippy_lint! { /// // .. /// } /// ``` - pub FOR_LOOP_OVER_FALLIBLE, + pub FOR_LOOPS_OVER_FALLIBLES, correctness, "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" } @@ -426,7 +426,7 @@ declare_lint_pass!(Loops => [ EXPLICIT_ITER_LOOP, EXPLICIT_INTO_ITER_LOOP, ITER_NEXT_LOOP, - FOR_LOOP_OVER_FALLIBLE, + FOR_LOOPS_OVER_FALLIBLES, WHILE_LET_LOOP, NEEDLESS_COLLECT, EXPLICIT_COUNTER_LOOP, @@ -1290,7 +1290,7 @@ fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) { if is_type_diagnostic_item(cx, ty, sym!(option_type)) { span_lint_and_help( cx, - FOR_LOOP_OVER_FALLIBLE, + FOR_LOOPS_OVER_FALLIBLES, arg.span, &format!( "for loop over `{0}`, which is an `Option`. This is more readably written as an \ @@ -1307,7 +1307,7 @@ fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) { } else if is_type_diagnostic_item(cx, ty, sym!(result_type)) { span_lint_and_help( cx, - FOR_LOOP_OVER_FALLIBLE, + FOR_LOOPS_OVER_FALLIBLES, arg.span, &format!( "for loop over `{0}`, which is a `Result`. This is more readably written as an \ diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index e90b9c15747..feada261a4c 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -74,11 +74,11 @@ pub static ref ALL_LINTS: Vec = vec![ module: "blacklisted_name", }, Lint { - name: "block_in_if_condition", + name: "blocks_in_if_conditions", group: "style", desc: "useless or complex blocks that can be eliminated in conditions", deprecation: None, - module: "block_in_if_condition", + module: "blocks_in_if_conditions", }, Lint { name: "bool_comparison", @@ -676,7 +676,7 @@ pub static ref ALL_LINTS: Vec = vec![ module: "loops", }, Lint { - name: "for_loop_over_fallible", + name: "for_loops_over_fallibles", group: "correctness", desc: "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`", deprecation: None, diff --git a/tests/ui/block_in_if_condition.fixed b/tests/ui/block_in_if_condition.fixed deleted file mode 100644 index ae01c6d3042..00000000000 --- a/tests/ui/block_in_if_condition.fixed +++ /dev/null @@ -1,74 +0,0 @@ -// run-rustfix -#![warn(clippy::block_in_if_condition)] -#![allow(unused, clippy::let_and_return)] -#![warn(clippy::nonminimal_bool)] - -macro_rules! blocky { - () => {{ - true - }}; -} - -macro_rules! blocky_too { - () => {{ - let r = true; - r - }}; -} - -fn macro_if() { - if blocky!() {} - - if blocky_too!() {} -} - -fn condition_has_block() -> i32 { - let res = { - let x = 3; - x == 3 - }; if res { - 6 - } else { - 10 - } -} - -fn condition_has_block_with_single_expression() -> i32 { - if true { - 6 - } else { - 10 - } -} - -fn condition_is_normal() -> i32 { - let x = 3; - if x == 3 { - 6 - } else { - 10 - } -} - -fn condition_is_unsafe_block() { - let a: i32 = 1; - - // this should not warn because the condition is an unsafe block - if unsafe { 1u32 == std::mem::transmute(a) } { - println!("1u32 == a"); - } -} - -fn block_in_assert() { - let opt = Some(42); - assert!(opt - .as_ref() - .and_then(|val| { - let mut v = val * 2; - v -= 1; - Some(v * 3) - }) - .is_some()); -} - -fn main() {} diff --git a/tests/ui/block_in_if_condition.rs b/tests/ui/block_in_if_condition.rs deleted file mode 100644 index 88555dc47c2..00000000000 --- a/tests/ui/block_in_if_condition.rs +++ /dev/null @@ -1,74 +0,0 @@ -// run-rustfix -#![warn(clippy::block_in_if_condition)] -#![allow(unused, clippy::let_and_return)] -#![warn(clippy::nonminimal_bool)] - -macro_rules! blocky { - () => {{ - true - }}; -} - -macro_rules! blocky_too { - () => {{ - let r = true; - r - }}; -} - -fn macro_if() { - if blocky!() {} - - if blocky_too!() {} -} - -fn condition_has_block() -> i32 { - if { - let x = 3; - x == 3 - } { - 6 - } else { - 10 - } -} - -fn condition_has_block_with_single_expression() -> i32 { - if { true } { - 6 - } else { - 10 - } -} - -fn condition_is_normal() -> i32 { - let x = 3; - if true && x == 3 { - 6 - } else { - 10 - } -} - -fn condition_is_unsafe_block() { - let a: i32 = 1; - - // this should not warn because the condition is an unsafe block - if unsafe { 1u32 == std::mem::transmute(a) } { - println!("1u32 == a"); - } -} - -fn block_in_assert() { - let opt = Some(42); - assert!(opt - .as_ref() - .and_then(|val| { - let mut v = val * 2; - v -= 1; - Some(v * 3) - }) - .is_some()); -} - -fn main() {} diff --git a/tests/ui/block_in_if_condition.stderr b/tests/ui/block_in_if_condition.stderr deleted file mode 100644 index 89e9ad26f49..00000000000 --- a/tests/ui/block_in_if_condition.stderr +++ /dev/null @@ -1,34 +0,0 @@ -error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> $DIR/block_in_if_condition.rs:26:5 - | -LL | / if { -LL | | let x = 3; -LL | | x == 3 -LL | | } { - | |_____^ - | - = note: `-D clippy::block-in-if-condition` implied by `-D warnings` -help: try - | -LL | let res = { -LL | let x = 3; -LL | x == 3 -LL | }; if res { - | - -error: omit braces around single expression condition - --> $DIR/block_in_if_condition.rs:37:8 - | -LL | if { true } { - | ^^^^^^^^ help: try: `true` - -error: this boolean expression can be simplified - --> $DIR/block_in_if_condition.rs:46:8 - | -LL | if true && x == 3 { - | ^^^^^^^^^^^^^^ help: try: `x == 3` - | - = note: `-D clippy::nonminimal-bool` implied by `-D warnings` - -error: aborting due to 3 previous errors - diff --git a/tests/ui/block_in_if_condition_closure.rs b/tests/ui/block_in_if_condition_closure.rs deleted file mode 100644 index 87b3fb94daf..00000000000 --- a/tests/ui/block_in_if_condition_closure.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![warn(clippy::block_in_if_condition)] -#![allow(unused, clippy::let_and_return)] - -fn predicate bool, T>(pfn: F, val: T) -> bool { - pfn(val) -} - -fn pred_test() { - let v = 3; - let sky = "blue"; - // This is a sneaky case, where the block isn't directly in the condition, - // but is actually inside a closure that the condition is using. - // The same principle applies -- add some extra expressions to make sure - // linter isn't confused by them. - if v == 3 - && sky == "blue" - && predicate( - |x| { - let target = 3; - x == target - }, - v, - ) - {} - - if predicate( - |x| { - let target = 3; - x == target - }, - v, - ) {} -} - -fn closure_without_block() { - if predicate(|x| x == 3, 6) {} -} - -fn macro_in_closure() { - let option = Some(true); - - if option.unwrap_or_else(|| unimplemented!()) { - unimplemented!() - } -} - -fn main() {} diff --git a/tests/ui/block_in_if_condition_closure.stderr b/tests/ui/block_in_if_condition_closure.stderr deleted file mode 100644 index 3df25691c3c..00000000000 --- a/tests/ui/block_in_if_condition_closure.stderr +++ /dev/null @@ -1,24 +0,0 @@ -error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> $DIR/block_in_if_condition_closure.rs:18:17 - | -LL | |x| { - | _________________^ -LL | | let target = 3; -LL | | x == target -LL | | }, - | |_____________^ - | - = note: `-D clippy::block-in-if-condition` implied by `-D warnings` - -error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> $DIR/block_in_if_condition_closure.rs:27:13 - | -LL | |x| { - | _____________^ -LL | | let target = 3; -LL | | x == target -LL | | }, - | |_________^ - -error: aborting due to 2 previous errors - diff --git a/tests/ui/blocks_in_if_conditions.fixed b/tests/ui/blocks_in_if_conditions.fixed new file mode 100644 index 00000000000..9040552cefc --- /dev/null +++ b/tests/ui/blocks_in_if_conditions.fixed @@ -0,0 +1,74 @@ +// run-rustfix +#![warn(clippy::blocks_in_if_conditions)] +#![allow(unused, clippy::let_and_return)] +#![warn(clippy::nonminimal_bool)] + +macro_rules! blocky { + () => {{ + true + }}; +} + +macro_rules! blocky_too { + () => {{ + let r = true; + r + }}; +} + +fn macro_if() { + if blocky!() {} + + if blocky_too!() {} +} + +fn condition_has_block() -> i32 { + let res = { + let x = 3; + x == 3 + }; if res { + 6 + } else { + 10 + } +} + +fn condition_has_block_with_single_expression() -> i32 { + if true { + 6 + } else { + 10 + } +} + +fn condition_is_normal() -> i32 { + let x = 3; + if x == 3 { + 6 + } else { + 10 + } +} + +fn condition_is_unsafe_block() { + let a: i32 = 1; + + // this should not warn because the condition is an unsafe block + if unsafe { 1u32 == std::mem::transmute(a) } { + println!("1u32 == a"); + } +} + +fn block_in_assert() { + let opt = Some(42); + assert!(opt + .as_ref() + .and_then(|val| { + let mut v = val * 2; + v -= 1; + Some(v * 3) + }) + .is_some()); +} + +fn main() {} diff --git a/tests/ui/blocks_in_if_conditions.rs b/tests/ui/blocks_in_if_conditions.rs new file mode 100644 index 00000000000..2fe409b22d3 --- /dev/null +++ b/tests/ui/blocks_in_if_conditions.rs @@ -0,0 +1,74 @@ +// run-rustfix +#![warn(clippy::blocks_in_if_conditions)] +#![allow(unused, clippy::let_and_return)] +#![warn(clippy::nonminimal_bool)] + +macro_rules! blocky { + () => {{ + true + }}; +} + +macro_rules! blocky_too { + () => {{ + let r = true; + r + }}; +} + +fn macro_if() { + if blocky!() {} + + if blocky_too!() {} +} + +fn condition_has_block() -> i32 { + if { + let x = 3; + x == 3 + } { + 6 + } else { + 10 + } +} + +fn condition_has_block_with_single_expression() -> i32 { + if { true } { + 6 + } else { + 10 + } +} + +fn condition_is_normal() -> i32 { + let x = 3; + if true && x == 3 { + 6 + } else { + 10 + } +} + +fn condition_is_unsafe_block() { + let a: i32 = 1; + + // this should not warn because the condition is an unsafe block + if unsafe { 1u32 == std::mem::transmute(a) } { + println!("1u32 == a"); + } +} + +fn block_in_assert() { + let opt = Some(42); + assert!(opt + .as_ref() + .and_then(|val| { + let mut v = val * 2; + v -= 1; + Some(v * 3) + }) + .is_some()); +} + +fn main() {} diff --git a/tests/ui/blocks_in_if_conditions.stderr b/tests/ui/blocks_in_if_conditions.stderr new file mode 100644 index 00000000000..9bdddc8e152 --- /dev/null +++ b/tests/ui/blocks_in_if_conditions.stderr @@ -0,0 +1,34 @@ +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/blocks_in_if_conditions.rs:26:5 + | +LL | / if { +LL | | let x = 3; +LL | | x == 3 +LL | | } { + | |_____^ + | + = note: `-D clippy::blocks-in-if-conditions` implied by `-D warnings` +help: try + | +LL | let res = { +LL | let x = 3; +LL | x == 3 +LL | }; if res { + | + +error: omit braces around single expression condition + --> $DIR/blocks_in_if_conditions.rs:37:8 + | +LL | if { true } { + | ^^^^^^^^ help: try: `true` + +error: this boolean expression can be simplified + --> $DIR/blocks_in_if_conditions.rs:46:8 + | +LL | if true && x == 3 { + | ^^^^^^^^^^^^^^ help: try: `x == 3` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/blocks_in_if_conditions_closure.rs b/tests/ui/blocks_in_if_conditions_closure.rs new file mode 100644 index 00000000000..acbabfa20d7 --- /dev/null +++ b/tests/ui/blocks_in_if_conditions_closure.rs @@ -0,0 +1,47 @@ +#![warn(clippy::blocks_in_if_conditions)] +#![allow(unused, clippy::let_and_return)] + +fn predicate bool, T>(pfn: F, val: T) -> bool { + pfn(val) +} + +fn pred_test() { + let v = 3; + let sky = "blue"; + // This is a sneaky case, where the block isn't directly in the condition, + // but is actually inside a closure that the condition is using. + // The same principle applies -- add some extra expressions to make sure + // linter isn't confused by them. + if v == 3 + && sky == "blue" + && predicate( + |x| { + let target = 3; + x == target + }, + v, + ) + {} + + if predicate( + |x| { + let target = 3; + x == target + }, + v, + ) {} +} + +fn closure_without_block() { + if predicate(|x| x == 3, 6) {} +} + +fn macro_in_closure() { + let option = Some(true); + + if option.unwrap_or_else(|| unimplemented!()) { + unimplemented!() + } +} + +fn main() {} diff --git a/tests/ui/blocks_in_if_conditions_closure.stderr b/tests/ui/blocks_in_if_conditions_closure.stderr new file mode 100644 index 00000000000..941d604dd5f --- /dev/null +++ b/tests/ui/blocks_in_if_conditions_closure.stderr @@ -0,0 +1,24 @@ +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/blocks_in_if_conditions_closure.rs:18:17 + | +LL | |x| { + | _________________^ +LL | | let target = 3; +LL | | x == target +LL | | }, + | |_____________^ + | + = note: `-D clippy::blocks-in-if-conditions` implied by `-D warnings` + +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/blocks_in_if_conditions_closure.rs:27:13 + | +LL | |x| { + | _____________^ +LL | | let target = 3; +LL | | x == target +LL | | }, + | |_________^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/for_loop_over_fallible.rs b/tests/ui/for_loop_over_fallible.rs deleted file mode 100644 index e52468cdd4b..00000000000 --- a/tests/ui/for_loop_over_fallible.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![warn(clippy::for_loop_over_fallible)] - -fn for_loop_over_fallible() { - let option = Some(1); - let result = option.ok_or("x not found"); - let v = vec![0, 1, 2]; - - // check over an `Option` - for x in option { - println!("{}", x); - } - - // check over a `Result` - for x in result { - println!("{}", x); - } - - for x in option.ok_or("x not found") { - println!("{}", x); - } - - // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call - // in the chain - for x in v.iter().next() { - println!("{}", x); - } - - // make sure we lint when next() is not the last call in the chain - for x in v.iter().next().and(Some(0)) { - println!("{}", x); - } - - for x in v.iter().next().ok_or("x not found") { - println!("{}", x); - } - - // check for false positives - - // for loop false positive - for x in v { - println!("{}", x); - } - - // while let false positive for Option - while let Some(x) = option { - println!("{}", x); - break; - } - - // while let false positive for Result - while let Ok(x) = result { - println!("{}", x); - break; - } -} - -fn main() {} diff --git a/tests/ui/for_loop_over_fallible.stderr b/tests/ui/for_loop_over_fallible.stderr deleted file mode 100644 index 4ce9a144ad8..00000000000 --- a/tests/ui/for_loop_over_fallible.stderr +++ /dev/null @@ -1,71 +0,0 @@ -error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_fallible.rs:9:14 - | -LL | for x in option { - | ^^^^^^ - | - = note: `-D clippy::for-loop-over-fallible` implied by `-D warnings` - = help: consider replacing `for x in option` with `if let Some(x) = option` - -error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_fallible.rs:14:14 - | -LL | for x in result { - | ^^^^^^ - | - = help: consider replacing `for x in result` with `if let Ok(x) = result` - -error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_fallible.rs:18:14 - | -LL | for x in option.ok_or("x not found") { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` - -error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want - --> $DIR/for_loop_over_fallible.rs:24:14 - | -LL | for x in v.iter().next() { - | ^^^^^^^^^^^^^^^ - | - = note: `#[deny(clippy::iter_next_loop)]` on by default - -error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_fallible.rs:29:14 - | -LL | for x in v.iter().next().and(Some(0)) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` - -error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. - --> $DIR/for_loop_over_fallible.rs:33:14 - | -LL | for x in v.iter().next().ok_or("x not found") { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` - -error: this loop never actually loops - --> $DIR/for_loop_over_fallible.rs:45:5 - | -LL | / while let Some(x) = option { -LL | | println!("{}", x); -LL | | break; -LL | | } - | |_____^ - | - = note: `#[deny(clippy::never_loop)]` on by default - -error: this loop never actually loops - --> $DIR/for_loop_over_fallible.rs:51:5 - | -LL | / while let Ok(x) = result { -LL | | println!("{}", x); -LL | | break; -LL | | } - | |_____^ - -error: aborting due to 8 previous errors - diff --git a/tests/ui/for_loops_over_fallibles.rs b/tests/ui/for_loops_over_fallibles.rs new file mode 100644 index 00000000000..1b9dde87cd5 --- /dev/null +++ b/tests/ui/for_loops_over_fallibles.rs @@ -0,0 +1,57 @@ +#![warn(clippy::for_loops_over_fallibles)] + +fn for_loops_over_fallibles() { + let option = Some(1); + let result = option.ok_or("x not found"); + let v = vec![0, 1, 2]; + + // check over an `Option` + for x in option { + println!("{}", x); + } + + // check over a `Result` + for x in result { + println!("{}", x); + } + + for x in option.ok_or("x not found") { + println!("{}", x); + } + + // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call + // in the chain + for x in v.iter().next() { + println!("{}", x); + } + + // make sure we lint when next() is not the last call in the chain + for x in v.iter().next().and(Some(0)) { + println!("{}", x); + } + + for x in v.iter().next().ok_or("x not found") { + println!("{}", x); + } + + // check for false positives + + // for loop false positive + for x in v { + println!("{}", x); + } + + // while let false positive for Option + while let Some(x) = option { + println!("{}", x); + break; + } + + // while let false positive for Result + while let Ok(x) = result { + println!("{}", x); + break; + } +} + +fn main() {} diff --git a/tests/ui/for_loops_over_fallibles.stderr b/tests/ui/for_loops_over_fallibles.stderr new file mode 100644 index 00000000000..bef228d4b93 --- /dev/null +++ b/tests/ui/for_loops_over_fallibles.stderr @@ -0,0 +1,71 @@ +error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement. + --> $DIR/for_loops_over_fallibles.rs:9:14 + | +LL | for x in option { + | ^^^^^^ + | + = note: `-D clippy::for-loops-over-fallibles` implied by `-D warnings` + = help: consider replacing `for x in option` with `if let Some(x) = option` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loops_over_fallibles.rs:14:14 + | +LL | for x in result { + | ^^^^^^ + | + = help: consider replacing `for x in result` with `if let Ok(x) = result` + +error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loops_over_fallibles.rs:18:14 + | +LL | for x in option.ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` + +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want + --> $DIR/for_loops_over_fallibles.rs:24:14 + | +LL | for x in v.iter().next() { + | ^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::iter_next_loop)]` on by default + +error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement. + --> $DIR/for_loops_over_fallibles.rs:29:14 + | +LL | for x in v.iter().next().and(Some(0)) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` + +error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loops_over_fallibles.rs:33:14 + | +LL | for x in v.iter().next().ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` + +error: this loop never actually loops + --> $DIR/for_loops_over_fallibles.rs:45:5 + | +LL | / while let Some(x) = option { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + | + = note: `#[deny(clippy::never_loop)]` on by default + +error: this loop never actually loops + --> $DIR/for_loops_over_fallibles.rs:51:5 + | +LL | / while let Ok(x) = result { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + +error: aborting due to 8 previous errors + -- cgit 1.4.1-3-g733a5 From e55b920970fdc33f5ddaf7757738fbacdadf15ab Mon Sep 17 00:00:00 2001 From: ThibsG Date: Mon, 4 May 2020 17:09:02 +0200 Subject: Rename lint `identity_conversion` to `useless_conversion` --- CHANGELOG.md | 4 +- clippy_lints/src/identity_conversion.rs | 124 ------------------------------ clippy_lints/src/lib.rs | 11 +-- clippy_lints/src/useless_conversion.rs | 130 ++++++++++++++++++++++++++++++++ src/lintlist/mod.rs | 14 ++-- tests/ui/identity_conversion.fixed | 58 -------------- tests/ui/identity_conversion.rs | 58 -------------- tests/ui/identity_conversion.stderr | 68 ----------------- tests/ui/useless_conversion.fixed | 58 ++++++++++++++ tests/ui/useless_conversion.rs | 58 ++++++++++++++ tests/ui/useless_conversion.stderr | 68 +++++++++++++++++ 11 files changed, 329 insertions(+), 322 deletions(-) delete mode 100644 clippy_lints/src/identity_conversion.rs create mode 100644 clippy_lints/src/useless_conversion.rs delete mode 100644 tests/ui/identity_conversion.fixed delete mode 100644 tests/ui/identity_conversion.rs delete mode 100644 tests/ui/identity_conversion.stderr create mode 100644 tests/ui/useless_conversion.fixed create mode 100644 tests/ui/useless_conversion.rs create mode 100644 tests/ui/useless_conversion.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index d05819a973a..9e85e6da3b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -805,7 +805,7 @@ Released 2018-09-13 ## 0.0.166 * Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)* -* New lints: [`explicit_write`], [`identity_conversion`], [`implicit_hasher`], [`invalid_ref`], [`option_map_or_none`], +* New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], [`invalid_ref`], [`option_map_or_none`], [`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`], [`transmute_int_to_float`] @@ -1367,7 +1367,6 @@ Released 2018-09-13 [`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send [`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len [`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap -[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion [`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op [`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex [`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching @@ -1624,6 +1623,7 @@ Released 2018-09-13 [`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding [`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref [`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute +[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion [`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format [`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq [`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute diff --git a/clippy_lints/src/identity_conversion.rs b/clippy_lints/src/identity_conversion.rs deleted file mode 100644 index 33a9478f058..00000000000 --- a/clippy_lints/src/identity_conversion.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::utils::{ - match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, span_lint_and_sugg, -}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; - -declare_clippy_lint! { - /// **What it does:** Checks for always-identical `Into`/`From`/`IntoIter` conversions. - /// - /// **Why is this bad?** Redundant code. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// // format!() returns a `String` - /// let s: String = format!("hello").into(); - /// ``` - pub IDENTITY_CONVERSION, - complexity, - "using always-identical `Into`/`From`/`IntoIter` conversions" -} - -#[derive(Default)] -pub struct IdentityConversion { - try_desugar_arm: Vec, -} - -impl_lint_pass!(IdentityConversion => [IDENTITY_CONVERSION]); - -impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityConversion { - fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { - if e.span.from_expansion() { - return; - } - - if Some(&e.hir_id) == self.try_desugar_arm.last() { - return; - } - - match e.kind { - ExprKind::Match(_, ref arms, MatchSource::TryDesugar) => { - let e = match arms[0].body.kind { - ExprKind::Ret(Some(ref e)) | ExprKind::Break(_, Some(ref e)) => e, - _ => return, - }; - if let ExprKind::Call(_, ref args) = e.kind { - self.try_desugar_arm.push(args[0].hir_id); - } - }, - - ExprKind::MethodCall(ref name, .., ref args) => { - if match_trait_method(cx, e, &paths::INTO) && &*name.ident.as_str() == "into" { - let a = cx.tables.expr_ty(e); - let b = cx.tables.expr_ty(&args[0]); - if same_tys(cx, a, b) { - let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); - - span_lint_and_sugg( - cx, - IDENTITY_CONVERSION, - e.span, - "identical conversion", - "consider removing `.into()`", - sugg, - Applicability::MachineApplicable, // snippet - ); - } - } - if match_trait_method(cx, e, &paths::INTO_ITERATOR) && &*name.ident.as_str() == "into_iter" { - let a = cx.tables.expr_ty(e); - let b = cx.tables.expr_ty(&args[0]); - if same_tys(cx, a, b) { - let sugg = snippet(cx, args[0].span, "").into_owned(); - span_lint_and_sugg( - cx, - IDENTITY_CONVERSION, - e.span, - "identical conversion", - "consider removing `.into_iter()`", - sugg, - Applicability::MachineApplicable, // snippet - ); - } - } - }, - - ExprKind::Call(ref path, ref args) => { - if let ExprKind::Path(ref qpath) = path.kind { - if let Some(def_id) = cx.tables.qpath_res(qpath, path.hir_id).opt_def_id() { - if match_def_path(cx, def_id, &paths::FROM_FROM) { - let a = cx.tables.expr_ty(e); - let b = cx.tables.expr_ty(&args[0]); - if same_tys(cx, a, b) { - let sugg = snippet(cx, args[0].span.source_callsite(), "").into_owned(); - let sugg_msg = - format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); - span_lint_and_sugg( - cx, - IDENTITY_CONVERSION, - e.span, - "identical conversion", - &sugg_msg, - sugg, - Applicability::MachineApplicable, // snippet - ); - } - } - } - } - }, - - _ => {}, - } - } - - fn check_expr_post(&mut self, _: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { - if Some(&e.hir_id) == self.try_desugar_arm.last() { - self.try_desugar_arm.pop(); - } - } -} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index bda0d5c0458..4dda373738b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -221,7 +221,6 @@ mod formatting; mod functions; mod future_not_send; mod get_last_with_len; -mod identity_conversion; mod identity_op; mod if_let_mutex; mod if_let_some_result; @@ -324,6 +323,7 @@ mod unused_io_amount; mod unused_self; mod unwrap; mod use_self; +mod useless_conversion; mod vec; mod verbose_file_reads; mod wildcard_dependencies; @@ -577,7 +577,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &functions::TOO_MANY_LINES, &future_not_send::FUTURE_NOT_SEND, &get_last_with_len::GET_LAST_WITH_LEN, - &identity_conversion::IDENTITY_CONVERSION, &identity_op::IDENTITY_OP, &if_let_mutex::IF_LET_MUTEX, &if_let_some_result::IF_LET_SOME_RESULT, @@ -843,6 +842,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unwrap::PANICKING_UNWRAP, &unwrap::UNNECESSARY_UNWRAP, &use_self::USE_SELF, + &useless_conversion::USELESS_CONVERSION, &utils::internal_lints::CLIPPY_LINTS_INTERNAL, &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS, &utils::internal_lints::COMPILER_LINT_FUNCTIONS, @@ -980,7 +980,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box bytecount::ByteCount); store.register_late_pass(|| box infinite_iter::InfiniteIter); store.register_late_pass(|| box inline_fn_without_body::InlineFnWithoutBody); - store.register_late_pass(|| box identity_conversion::IdentityConversion::default()); + store.register_late_pass(|| box useless_conversion::UselessConversion::default()); store.register_late_pass(|| box types::ImplicitHasher); store.register_late_pass(|| box fallible_impl_from::FallibleImplFrom); store.register_late_pass(|| box types::UnitArg); @@ -1241,7 +1241,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF), LintId::of(&functions::TOO_MANY_ARGUMENTS), LintId::of(&get_last_with_len::GET_LAST_WITH_LEN), - LintId::of(&identity_conversion::IDENTITY_CONVERSION), LintId::of(&identity_op::IDENTITY_OP), LintId::of(&if_let_mutex::IF_LET_MUTEX), LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), @@ -1427,6 +1426,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&vec::USELESS_VEC), LintId::of(&write::PRINTLN_EMPTY_STRING), LintId::of(&write::PRINT_LITERAL), @@ -1546,7 +1546,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&format::USELESS_FORMAT), LintId::of(&functions::TOO_MANY_ARGUMENTS), LintId::of(&get_last_with_len::GET_LAST_WITH_LEN), - LintId::of(&identity_conversion::IDENTITY_CONVERSION), LintId::of(&identity_op::IDENTITY_OP), LintId::of(&int_plus_one::INT_PLUS_ONE), LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), @@ -1605,6 +1604,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), ]); @@ -1795,6 +1795,7 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::result_expect_used", "clippy::expect_used"); ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles"); ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"); + ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion"); } // only exists to let the dogfood integration test works. diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs new file mode 100644 index 00000000000..95921518986 --- /dev/null +++ b/clippy_lints/src/useless_conversion.rs @@ -0,0 +1,130 @@ +use crate::utils::{ + match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, span_lint_and_sugg, +}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for `Into`/`From`/`IntoIter` calls that useless converts + /// to the same type as caller. + /// + /// **Why is this bad?** Redundant code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// // format!() returns a `String` + /// let s: String = format!("hello").into(); + /// + /// // Good + /// let s: String = format!("hello"); + /// ``` + pub USELESS_CONVERSION, + complexity, + "calls to `Into`/`From`/`IntoIter` that performs useless conversions to the same type" +} + +#[derive(Default)] +pub struct UselessConversion { + try_desugar_arm: Vec, +} + +impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if Some(&e.hir_id) == self.try_desugar_arm.last() { + return; + } + + match e.kind { + ExprKind::Match(_, ref arms, MatchSource::TryDesugar) => { + let e = match arms[0].body.kind { + ExprKind::Ret(Some(ref e)) | ExprKind::Break(_, Some(ref e)) => e, + _ => return, + }; + if let ExprKind::Call(_, ref args) = e.kind { + self.try_desugar_arm.push(args[0].hir_id); + } + }, + + ExprKind::MethodCall(ref name, .., ref args) => { + if match_trait_method(cx, e, &paths::INTO) && &*name.ident.as_str() == "into" { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if same_tys(cx, a, b) { + let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); + + span_lint_and_sugg( + cx, + USELESS_CONVERSION, + e.span, + "useless conversion", + "consider removing `.into()`", + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + if match_trait_method(cx, e, &paths::INTO_ITERATOR) && &*name.ident.as_str() == "into_iter" { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if same_tys(cx, a, b) { + let sugg = snippet(cx, args[0].span, "").into_owned(); + span_lint_and_sugg( + cx, + USELESS_CONVERSION, + e.span, + "useless conversion", + "consider removing `.into_iter()`", + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + }, + + ExprKind::Call(ref path, ref args) => { + if let ExprKind::Path(ref qpath) = path.kind { + if let Some(def_id) = cx.tables.qpath_res(qpath, path.hir_id).opt_def_id() { + if match_def_path(cx, def_id, &paths::FROM_FROM) { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if same_tys(cx, a, b) { + let sugg = snippet(cx, args[0].span.source_callsite(), "").into_owned(); + let sugg_msg = + format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); + span_lint_and_sugg( + cx, + USELESS_CONVERSION, + e.span, + "useless conversion", + &sugg_msg, + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + } + } + }, + + _ => {}, + } + } + + fn check_expr_post(&mut self, _: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if Some(&e.hir_id) == self.try_desugar_arm.last() { + self.try_desugar_arm.pop(); + } + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index e5e3bf453a0..e411e60782a 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -717,13 +717,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "methods", }, - Lint { - name: "identity_conversion", - group: "complexity", - desc: "using always-identical `Into`/`From`/`IntoIter` conversions", - deprecation: None, - module: "identity_conversion", - }, Lint { name: "identity_op", group: "complexity", @@ -2418,6 +2411,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "attrs", }, + Lint { + name: "useless_conversion", + group: "complexity", + desc: "calls to `Into`/`From`/`IntoIter` that performs useless conversions to the same type", + deprecation: None, + module: "useless_conversion", + }, Lint { name: "useless_format", group: "complexity", diff --git a/tests/ui/identity_conversion.fixed b/tests/ui/identity_conversion.fixed deleted file mode 100644 index dd3fc56e98b..00000000000 --- a/tests/ui/identity_conversion.fixed +++ /dev/null @@ -1,58 +0,0 @@ -// run-rustfix - -#![deny(clippy::identity_conversion)] - -fn test_generic(val: T) -> T { - let _ = val; - val -} - -fn test_generic2 + Into, U: From>(val: T) { - // ok - let _: i32 = val.into(); - let _: U = val.into(); - let _ = U::from(val); -} - -fn test_questionmark() -> Result<(), ()> { - { - let _: i32 = 0i32; - Ok(Ok(())) - }??; - Ok(()) -} - -fn test_issue_3913() -> Result<(), std::io::Error> { - use std::fs; - use std::path::Path; - - let path = Path::new("."); - for _ in fs::read_dir(path)? {} - - Ok(()) -} - -fn main() { - test_generic(10i32); - test_generic2::(10i32); - test_questionmark().unwrap(); - test_issue_3913().unwrap(); - - let _: String = "foo".into(); - let _: String = From::from("foo"); - let _ = String::from("foo"); - #[allow(clippy::identity_conversion)] - { - let _: String = "foo".into(); - let _ = String::from("foo"); - let _ = "".lines().into_iter(); - } - - let _: String = "foo".to_string(); - let _: String = "foo".to_string(); - let _ = "foo".to_string(); - let _ = format!("A: {:04}", 123); - let _ = "".lines(); - let _ = vec![1, 2, 3].into_iter(); - let _: String = format!("Hello {}", "world"); -} diff --git a/tests/ui/identity_conversion.rs b/tests/ui/identity_conversion.rs deleted file mode 100644 index 875ed7db373..00000000000 --- a/tests/ui/identity_conversion.rs +++ /dev/null @@ -1,58 +0,0 @@ -// run-rustfix - -#![deny(clippy::identity_conversion)] - -fn test_generic(val: T) -> T { - let _ = T::from(val); - val.into() -} - -fn test_generic2 + Into, U: From>(val: T) { - // ok - let _: i32 = val.into(); - let _: U = val.into(); - let _ = U::from(val); -} - -fn test_questionmark() -> Result<(), ()> { - { - let _: i32 = 0i32.into(); - Ok(Ok(())) - }??; - Ok(()) -} - -fn test_issue_3913() -> Result<(), std::io::Error> { - use std::fs; - use std::path::Path; - - let path = Path::new("."); - for _ in fs::read_dir(path)? {} - - Ok(()) -} - -fn main() { - test_generic(10i32); - test_generic2::(10i32); - test_questionmark().unwrap(); - test_issue_3913().unwrap(); - - let _: String = "foo".into(); - let _: String = From::from("foo"); - let _ = String::from("foo"); - #[allow(clippy::identity_conversion)] - { - let _: String = "foo".into(); - let _ = String::from("foo"); - let _ = "".lines().into_iter(); - } - - let _: String = "foo".to_string().into(); - let _: String = From::from("foo".to_string()); - let _ = String::from("foo".to_string()); - let _ = String::from(format!("A: {:04}", 123)); - let _ = "".lines().into_iter(); - let _ = vec![1, 2, 3].into_iter().into_iter(); - let _: String = format!("Hello {}", "world").into(); -} diff --git a/tests/ui/identity_conversion.stderr b/tests/ui/identity_conversion.stderr deleted file mode 100644 index 57626b23795..00000000000 --- a/tests/ui/identity_conversion.stderr +++ /dev/null @@ -1,68 +0,0 @@ -error: identical conversion - --> $DIR/identity_conversion.rs:6:13 - | -LL | let _ = T::from(val); - | ^^^^^^^^^^^^ help: consider removing `T::from()`: `val` - | -note: the lint level is defined here - --> $DIR/identity_conversion.rs:3:9 - | -LL | #![deny(clippy::identity_conversion)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: identical conversion - --> $DIR/identity_conversion.rs:7:5 - | -LL | val.into() - | ^^^^^^^^^^ help: consider removing `.into()`: `val` - -error: identical conversion - --> $DIR/identity_conversion.rs:19:22 - | -LL | let _: i32 = 0i32.into(); - | ^^^^^^^^^^^ help: consider removing `.into()`: `0i32` - -error: identical conversion - --> $DIR/identity_conversion.rs:51:21 - | -LL | let _: String = "foo".to_string().into(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()` - -error: identical conversion - --> $DIR/identity_conversion.rs:52:21 - | -LL | let _: String = From::from("foo".to_string()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()` - -error: identical conversion - --> $DIR/identity_conversion.rs:53:13 - | -LL | let _ = String::from("foo".to_string()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()` - -error: identical conversion - --> $DIR/identity_conversion.rs:54:13 - | -LL | let _ = String::from(format!("A: {:04}", 123)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `format!("A: {:04}", 123)` - -error: identical conversion - --> $DIR/identity_conversion.rs:55:13 - | -LL | let _ = "".lines().into_iter(); - | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `"".lines()` - -error: identical conversion - --> $DIR/identity_conversion.rs:56:13 - | -LL | let _ = vec![1, 2, 3].into_iter().into_iter(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2, 3].into_iter()` - -error: identical conversion - --> $DIR/identity_conversion.rs:57:21 - | -LL | let _: String = format!("Hello {}", "world").into(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `format!("Hello {}", "world")` - -error: aborting due to 10 previous errors - diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed new file mode 100644 index 00000000000..fdd4bc581f3 --- /dev/null +++ b/tests/ui/useless_conversion.fixed @@ -0,0 +1,58 @@ +// run-rustfix + +#![deny(clippy::useless_conversion)] + +fn test_generic(val: T) -> T { + let _ = val; + val +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.into(); + let _: U = val.into(); + let _ = U::from(val); +} + +fn test_questionmark() -> Result<(), ()> { + { + let _: i32 = 0i32; + Ok(Ok(())) + }??; + Ok(()) +} + +fn test_issue_3913() -> Result<(), std::io::Error> { + use std::fs; + use std::path::Path; + + let path = Path::new("."); + for _ in fs::read_dir(path)? {} + + Ok(()) +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + test_questionmark().unwrap(); + test_issue_3913().unwrap(); + + let _: String = "foo".into(); + let _: String = From::from("foo"); + let _ = String::from("foo"); + #[allow(clippy::useless_conversion)] + { + let _: String = "foo".into(); + let _ = String::from("foo"); + let _ = "".lines().into_iter(); + } + + let _: String = "foo".to_string(); + let _: String = "foo".to_string(); + let _ = "foo".to_string(); + let _ = format!("A: {:04}", 123); + let _ = "".lines(); + let _ = vec![1, 2, 3].into_iter(); + let _: String = format!("Hello {}", "world"); +} diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs new file mode 100644 index 00000000000..4cae745e7c0 --- /dev/null +++ b/tests/ui/useless_conversion.rs @@ -0,0 +1,58 @@ +// run-rustfix + +#![deny(clippy::useless_conversion)] + +fn test_generic(val: T) -> T { + let _ = T::from(val); + val.into() +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.into(); + let _: U = val.into(); + let _ = U::from(val); +} + +fn test_questionmark() -> Result<(), ()> { + { + let _: i32 = 0i32.into(); + Ok(Ok(())) + }??; + Ok(()) +} + +fn test_issue_3913() -> Result<(), std::io::Error> { + use std::fs; + use std::path::Path; + + let path = Path::new("."); + for _ in fs::read_dir(path)? {} + + Ok(()) +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + test_questionmark().unwrap(); + test_issue_3913().unwrap(); + + let _: String = "foo".into(); + let _: String = From::from("foo"); + let _ = String::from("foo"); + #[allow(clippy::useless_conversion)] + { + let _: String = "foo".into(); + let _ = String::from("foo"); + let _ = "".lines().into_iter(); + } + + let _: String = "foo".to_string().into(); + let _: String = From::from("foo".to_string()); + let _ = String::from("foo".to_string()); + let _ = String::from(format!("A: {:04}", 123)); + let _ = "".lines().into_iter(); + let _ = vec![1, 2, 3].into_iter().into_iter(); + let _: String = format!("Hello {}", "world").into(); +} diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr new file mode 100644 index 00000000000..7df3507edfd --- /dev/null +++ b/tests/ui/useless_conversion.stderr @@ -0,0 +1,68 @@ +error: useless conversion + --> $DIR/useless_conversion.rs:6:13 + | +LL | let _ = T::from(val); + | ^^^^^^^^^^^^ help: consider removing `T::from()`: `val` + | +note: the lint level is defined here + --> $DIR/useless_conversion.rs:3:9 + | +LL | #![deny(clippy::useless_conversion)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: useless conversion + --> $DIR/useless_conversion.rs:7:5 + | +LL | val.into() + | ^^^^^^^^^^ help: consider removing `.into()`: `val` + +error: useless conversion + --> $DIR/useless_conversion.rs:19:22 + | +LL | let _: i32 = 0i32.into(); + | ^^^^^^^^^^^ help: consider removing `.into()`: `0i32` + +error: useless conversion + --> $DIR/useless_conversion.rs:51:21 + | +LL | let _: String = "foo".to_string().into(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()` + +error: useless conversion + --> $DIR/useless_conversion.rs:52:21 + | +LL | let _: String = From::from("foo".to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()` + +error: useless conversion + --> $DIR/useless_conversion.rs:53:13 + | +LL | let _ = String::from("foo".to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()` + +error: useless conversion + --> $DIR/useless_conversion.rs:54:13 + | +LL | let _ = String::from(format!("A: {:04}", 123)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `format!("A: {:04}", 123)` + +error: useless conversion + --> $DIR/useless_conversion.rs:55:13 + | +LL | let _ = "".lines().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `"".lines()` + +error: useless conversion + --> $DIR/useless_conversion.rs:56:13 + | +LL | let _ = vec![1, 2, 3].into_iter().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2, 3].into_iter()` + +error: useless conversion + --> $DIR/useless_conversion.rs:57:21 + | +LL | let _: String = format!("Hello {}", "world").into(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `format!("Hello {}", "world")` + +error: aborting due to 10 previous errors + -- cgit 1.4.1-3-g733a5 From 7f317b708fe0889c04b7590ba53f3a41afa44a1d Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 17 May 2020 01:18:43 +0200 Subject: Run fmt --- src/driver.rs | 222 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 110 insertions(+), 112 deletions(-) (limited to 'src') diff --git a/src/driver.rs b/src/driver.rs index 1ce0300f239..d3a7e24937f 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -295,121 +295,119 @@ fn toolchain_path(home: Option, toolchain: Option) -> Option = env::args().collect(); - - if orig_args.iter().any(|a| a == "--version" || a == "-V") { - let version_info = rustc_tools_util::get_version_info!(); - println!("{}", version_info); - exit(0); - } + exit(rustc_driver::catch_with_exit_code(move || { + let mut orig_args: Vec = env::args().collect(); - // Get the sysroot, looking from most specific to this invocation to the least: - // - command line - // - runtime environment - // - SYSROOT - // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN - // - sysroot from rustc in the path - // - compile-time environment - // - SYSROOT - // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN - let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true); - let have_sys_root_arg = sys_root_arg.is_some(); - let sys_root = sys_root_arg - .map(PathBuf::from) - .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from)) - .or_else(|| { - let home = std::env::var("RUSTUP_HOME") - .or_else(|_| std::env::var("MULTIRUST_HOME")) - .ok(); - let toolchain = std::env::var("RUSTUP_TOOLCHAIN") - .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN")) - .ok(); - toolchain_path(home, toolchain) - }) - .or_else(|| { - Command::new("rustc") - .arg("--print") - .arg("sysroot") - .output() - .ok() - .and_then(|out| String::from_utf8(out.stdout).ok()) - .map(|s| PathBuf::from(s.trim())) - }) - .or_else(|| option_env!("SYSROOT").map(PathBuf::from)) - .or_else(|| { - let home = option_env!("RUSTUP_HOME") - .or(option_env!("MULTIRUST_HOME")) - .map(ToString::to_string); - let toolchain = option_env!("RUSTUP_TOOLCHAIN") - .or(option_env!("MULTIRUST_TOOLCHAIN")) - .map(ToString::to_string); - toolchain_path(home, toolchain) - }) - .map(|pb| pb.to_string_lossy().to_string()) - .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust"); - - // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. - // We're invoking the compiler programmatically, so we ignore this/ - let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref()); - - if wrapper_mode { - // we still want to be able to invoke it normally though - orig_args.remove(1); - } + if orig_args.iter().any(|a| a == "--version" || a == "-V") { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); + exit(0); + } - if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) { - display_help(); - exit(0); - } + // Get the sysroot, looking from most specific to this invocation to the least: + // - command line + // - runtime environment + // - SYSROOT + // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN + // - sysroot from rustc in the path + // - compile-time environment + // - SYSROOT + // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN + let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true); + let have_sys_root_arg = sys_root_arg.is_some(); + let sys_root = sys_root_arg + .map(PathBuf::from) + .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from)) + .or_else(|| { + let home = std::env::var("RUSTUP_HOME") + .or_else(|_| std::env::var("MULTIRUST_HOME")) + .ok(); + let toolchain = std::env::var("RUSTUP_TOOLCHAIN") + .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN")) + .ok(); + toolchain_path(home, toolchain) + }) + .or_else(|| { + Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output() + .ok() + .and_then(|out| String::from_utf8(out.stdout).ok()) + .map(|s| PathBuf::from(s.trim())) + }) + .or_else(|| option_env!("SYSROOT").map(PathBuf::from)) + .or_else(|| { + let home = option_env!("RUSTUP_HOME") + .or(option_env!("MULTIRUST_HOME")) + .map(ToString::to_string); + let toolchain = option_env!("RUSTUP_TOOLCHAIN") + .or(option_env!("MULTIRUST_TOOLCHAIN")) + .map(ToString::to_string); + toolchain_path(home, toolchain) + }) + .map(|pb| pb.to_string_lossy().to_string()) + .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust"); - let should_describe_lints = || { - let args: Vec<_> = env::args().collect(); - args.windows(2).any(|args| { - args[1] == "help" - && match args[0].as_str() { - "-W" | "-A" | "-D" | "-F" => true, - _ => false, - } - }) - }; - - if !wrapper_mode && should_describe_lints() { - describe_lints(); - exit(0); - } + // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. + // We're invoking the compiler programmatically, so we ignore this/ + let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref()); + + if wrapper_mode { + // we still want to be able to invoke it normally though + orig_args.remove(1); + } + + if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) { + display_help(); + exit(0); + } + + let should_describe_lints = || { + let args: Vec<_> = env::args().collect(); + args.windows(2).any(|args| { + args[1] == "help" + && match args[0].as_str() { + "-W" | "-A" | "-D" | "-F" => true, + _ => false, + } + }) + }; - // this conditional check for the --sysroot flag is there so users can call - // `clippy_driver` directly - // without having to pass --sysroot or anything - let mut args: Vec = orig_args.clone(); - if !have_sys_root_arg { - args.extend(vec!["--sysroot".into(), sys_root]); - }; - - // this check ensures that dependencies are built but not linted and the final - // crate is linted but not built - let clippy_enabled = env::var("CLIPPY_TESTS").map_or(false, |val| val == "true") - || arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_none(); - - if clippy_enabled { - args.extend(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]); - if let Ok(extra_args) = env::var("CLIPPY_ARGS") { - args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| { - if s.is_empty() { - None - } else { - Some(s.to_string()) - } - })); - } + if !wrapper_mode && should_describe_lints() { + describe_lints(); + exit(0); + } + + // this conditional check for the --sysroot flag is there so users can call + // `clippy_driver` directly + // without having to pass --sysroot or anything + let mut args: Vec = orig_args.clone(); + if !have_sys_root_arg { + args.extend(vec!["--sysroot".into(), sys_root]); + }; + + // this check ensures that dependencies are built but not linted and the final + // crate is linted but not built + let clippy_enabled = env::var("CLIPPY_TESTS").map_or(false, |val| val == "true") + || arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_none(); + + if clippy_enabled { + args.extend(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]); + if let Ok(extra_args) = env::var("CLIPPY_ARGS") { + args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| { + if s.is_empty() { + None + } else { + Some(s.to_string()) + } + })); } - let mut clippy = ClippyCallbacks; - let mut default = DefaultCallbacks; - let callbacks: &mut (dyn rustc_driver::Callbacks + Send) = - if clippy_enabled { &mut clippy } else { &mut default }; - rustc_driver::run_compiler(&args, callbacks, None, None) - }) - ) + } + let mut clippy = ClippyCallbacks; + let mut default = DefaultCallbacks; + let callbacks: &mut (dyn rustc_driver::Callbacks + Send) = + if clippy_enabled { &mut clippy } else { &mut default }; + rustc_driver::run_compiler(&args, callbacks, None, None) + })) } -- cgit 1.4.1-3-g733a5 From 07f1edf2d43efa0ef53e5b6c56c895bc9738ab94 Mon Sep 17 00:00:00 2001 From: Aleksei Latyshev Date: Sat, 25 Apr 2020 23:33:11 +0300 Subject: improve and generalize `option_and_then_some` lint - rename it to bind_instead_of_map --- CHANGELOG.md | 4 +- clippy_lints/src/lib.rs | 7 +- clippy_lints/src/loops.rs | 2 +- clippy_lints/src/methods/bind_instead_of_map.rs | 309 ++++++++++++++++++++++++ clippy_lints/src/methods/mod.rs | 100 ++------ clippy_lints/src/types.rs | 2 +- src/lintlist/mod.rs | 14 +- tests/ui/bind_instead_of_map.fixed | 25 ++ tests/ui/bind_instead_of_map.rs | 25 ++ tests/ui/bind_instead_of_map.stderr | 26 ++ tests/ui/bind_instead_of_map_multipart.rs | 61 +++++ tests/ui/bind_instead_of_map_multipart.stderr | 73 ++++++ tests/ui/blocks_in_if_conditions.fixed | 4 +- tests/ui/blocks_in_if_conditions.rs | 4 +- tests/ui/option_and_then_some.fixed | 25 -- tests/ui/option_and_then_some.rs | 25 -- tests/ui/option_and_then_some.stderr | 20 -- tests/ui/option_map_or_none.fixed | 2 +- tests/ui/option_map_or_none.rs | 2 +- 19 files changed, 564 insertions(+), 166 deletions(-) create mode 100644 clippy_lints/src/methods/bind_instead_of_map.rs create mode 100644 tests/ui/bind_instead_of_map.fixed create mode 100644 tests/ui/bind_instead_of_map.rs create mode 100644 tests/ui/bind_instead_of_map.stderr create mode 100644 tests/ui/bind_instead_of_map_multipart.rs create mode 100644 tests/ui/bind_instead_of_map_multipart.stderr delete mode 100644 tests/ui/option_and_then_some.fixed delete mode 100644 tests/ui/option_and_then_some.rs delete mode 100644 tests/ui/option_and_then_some.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index d05819a973a..7abefe65424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -315,7 +315,7 @@ Released 2019-11-07 * [`missing_safety_doc`] [#4535](https://github.com/rust-lang/rust-clippy/pull/4535) * [`mem_replace_with_uninit`] [#4511](https://github.com/rust-lang/rust-clippy/pull/4511) * [`suspicious_map`] [#4394](https://github.com/rust-lang/rust-clippy/pull/4394) - * [`option_and_then_some`] [#4386](https://github.com/rust-lang/rust-clippy/pull/4386) + * `option_and_then_some` [#4386](https://github.com/rust-lang/rust-clippy/pull/4386) * [`manual_saturating_arithmetic`] [#4498](https://github.com/rust-lang/rust-clippy/pull/4498) * Deprecate `unused_collect` lint. This is fully covered by rustc's `#[must_use]` on `collect` [#4348](https://github.com/rust-lang/rust-clippy/pull/4348) * Move `type_repetition_in_bounds` to pedantic group [#4403](https://github.com/rust-lang/rust-clippy/pull/4403) @@ -1273,6 +1273,7 @@ Released 2018-09-13 [`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops [`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock [`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask +[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map [`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name [`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions [`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison @@ -1494,7 +1495,6 @@ Released 2018-09-13 [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref -[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap [`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index bda0d5c0458..ec198b684b6 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -650,6 +650,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &mem_replace::MEM_REPLACE_OPTION_WITH_NONE, &mem_replace::MEM_REPLACE_WITH_DEFAULT, &mem_replace::MEM_REPLACE_WITH_UNINIT, + &methods::BIND_INSTEAD_OF_MAP, &methods::CHARS_LAST_CMP, &methods::CHARS_NEXT_CMP, &methods::CLONE_DOUBLE_REF, @@ -676,7 +677,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::MAP_UNWRAP_OR, &methods::NEW_RET_NO_SELF, &methods::OK_EXPECT, - &methods::OPTION_AND_THEN_SOME, &methods::OPTION_AS_REF_DEREF, &methods::OPTION_MAP_OR_NONE, &methods::OR_FUN_CALL, @@ -1291,6 +1291,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), + LintId::of(&methods::BIND_INSTEAD_OF_MAP), LintId::of(&methods::CHARS_LAST_CMP), LintId::of(&methods::CHARS_NEXT_CMP), LintId::of(&methods::CLONE_DOUBLE_REF), @@ -1307,7 +1308,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(&methods::NEW_RET_NO_SELF), LintId::of(&methods::OK_EXPECT), - LintId::of(&methods::OPTION_AND_THEN_SOME), LintId::of(&methods::OPTION_AS_REF_DEREF), LintId::of(&methods::OPTION_MAP_OR_NONE), LintId::of(&methods::OR_FUN_CALL), @@ -1559,10 +1559,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::MATCH_AS_REF), LintId::of(&matches::MATCH_SINGLE_BINDING), LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), + LintId::of(&methods::BIND_INSTEAD_OF_MAP), LintId::of(&methods::CLONE_ON_COPY), LintId::of(&methods::FILTER_NEXT), LintId::of(&methods::FLAT_MAP_IDENTITY), - LintId::of(&methods::OPTION_AND_THEN_SOME), LintId::of(&methods::OPTION_AS_REF_DEREF), LintId::of(&methods::SEARCH_IS_SOME), LintId::of(&methods::SKIP_WHILE_NEXT), @@ -1784,6 +1784,7 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"); + ls.register_renamed("clippy::option_and_then_some", "clippy::bind_instead_of_map"); ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"); ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"); ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"); diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index 4a9c411d7c8..84e8a010738 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -1462,7 +1462,7 @@ fn check_for_loop_over_map_kv<'a, 'tcx>( let map = sugg::Sugg::hir(cx, arg, "map"); multispan_sugg( diag, - "use the corresponding method".into(), + "use the corresponding method", vec![ (pat_span, snippet(cx, new_pat_span, kind).into_owned()), (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs new file mode 100644 index 00000000000..32e86637569 --- /dev/null +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -0,0 +1,309 @@ +use super::{contains_return, BIND_INSTEAD_OF_MAP}; +use crate::utils::{ + in_macro, match_qpath, match_type, method_calls, multispan_sugg_with_applicability, paths, remove_blocks, snippet, + snippet_with_macro_callsite, span_lint_and_sugg, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::Span; + +pub(crate) struct OptionAndThenSome; +impl BindInsteadOfMap for OptionAndThenSome { + const TYPE_NAME: &'static str = "Option"; + const TYPE_QPATH: &'static [&'static str] = &paths::OPTION; + + const BAD_METHOD_NAME: &'static str = "and_then"; + const BAD_VARIANT_NAME: &'static str = "Some"; + const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::OPTION_SOME; + + const GOOD_METHOD_NAME: &'static str = "map"; +} + +pub(crate) struct ResultAndThenOk; +impl BindInsteadOfMap for ResultAndThenOk { + const TYPE_NAME: &'static str = "Result"; + const TYPE_QPATH: &'static [&'static str] = &paths::RESULT; + + const BAD_METHOD_NAME: &'static str = "and_then"; + const BAD_VARIANT_NAME: &'static str = "Ok"; + const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_OK; + + const GOOD_METHOD_NAME: &'static str = "map"; +} + +pub(crate) struct ResultOrElseErrInfo; +impl BindInsteadOfMap for ResultOrElseErrInfo { + const TYPE_NAME: &'static str = "Result"; + const TYPE_QPATH: &'static [&'static str] = &paths::RESULT; + + const BAD_METHOD_NAME: &'static str = "or_else"; + const BAD_VARIANT_NAME: &'static str = "Err"; + const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_ERR; + + const GOOD_METHOD_NAME: &'static str = "map_err"; +} + +pub(crate) trait BindInsteadOfMap { + const TYPE_NAME: &'static str; + const TYPE_QPATH: &'static [&'static str]; + + const BAD_METHOD_NAME: &'static str; + const BAD_VARIANT_NAME: &'static str; + const BAD_VARIANT_QPATH: &'static [&'static str]; + + const GOOD_METHOD_NAME: &'static str; + + fn no_op_msg() -> String { + format!( + "using `{}.{}({})`, which is a no-op", + Self::TYPE_NAME, + Self::BAD_METHOD_NAME, + Self::BAD_VARIANT_NAME + ) + } + + fn lint_msg() -> String { + format!( + "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`", + Self::TYPE_NAME, + Self::BAD_METHOD_NAME, + Self::BAD_VARIANT_NAME, + Self::GOOD_METHOD_NAME + ) + } + + fn lint_closure_autofixable( + cx: &LateContext<'_, '_>, + expr: &hir::Expr<'_>, + args: &[hir::Expr<'_>], + closure_expr: &hir::Expr<'_>, + closure_args_span: Span, + ) -> bool { + if_chain! { + if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind; + if let hir::ExprKind::Path(ref qpath) = some_expr.kind; + if match_qpath(qpath, Self::BAD_VARIANT_QPATH); + if some_args.len() == 1; + then { + let inner_expr = &some_args[0]; + + if contains_return(inner_expr) { + return false; + } + + let some_inner_snip = if inner_expr.span.from_expansion() { + snippet_with_macro_callsite(cx, inner_expr.span, "_") + } else { + snippet(cx, inner_expr.span, "_") + }; + + let closure_args_snip = snippet(cx, closure_args_span, ".."); + let option_snip = snippet(cx, args[0].span, ".."); + let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip); + span_lint_and_sugg( + cx, + BIND_INSTEAD_OF_MAP, + expr.span, + Self::lint_msg().as_ref(), + "try this", + note, + Applicability::MachineApplicable, + ); + true + } else { + false + } + } + } + + fn lint_closure(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) { + let mut suggs = Vec::new(); + let can_sugg = find_all_ret_expressions(cx, closure_expr, |ret_expr| { + if_chain! { + if !in_macro(ret_expr.span); + if let hir::ExprKind::Call(ref func_path, ref args) = ret_expr.kind; + if let hir::ExprKind::Path(ref qpath) = func_path.kind; + if match_qpath(qpath, Self::BAD_VARIANT_QPATH); + if args.len() == 1; + if !contains_return(&args[0]); + then { + suggs.push((ret_expr.span, args[0].span.source_callsite())); + true + } else { + false + } + } + }); + + if can_sugg { + span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, Self::lint_msg().as_ref(), |diag| { + multispan_sugg_with_applicability( + diag, + "try this", + Applicability::MachineApplicable, + std::iter::once((*method_calls(expr, 1).2.get(0).unwrap(), Self::GOOD_METHOD_NAME.into())).chain( + suggs + .into_iter() + .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())), + ), + ) + }); + } + } + + /// Lint use of `_.and_then(|x| Some(y))` for `Option`s + fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if !match_type(cx, cx.tables.expr_ty(&args[0]), Self::TYPE_QPATH) { + return; + } + + match args[1].kind { + hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + if !Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) { + Self::lint_closure(cx, expr, closure_expr); + } + }, + // `_.and_then(Some)` case, which is no-op. + hir::ExprKind::Path(ref qpath) if match_qpath(qpath, Self::BAD_VARIANT_QPATH) => { + span_lint_and_sugg( + cx, + BIND_INSTEAD_OF_MAP, + expr.span, + Self::no_op_msg().as_ref(), + "use the expression directly", + snippet(cx, args[0].span, "..").into(), + Applicability::MachineApplicable, + ); + }, + _ => {}, + } + } +} + +/// returns `true` if expr contains match expr desugared from try +fn contains_try(expr: &hir::Expr<'_>) -> bool { + struct TryFinder { + found: bool, + } + + impl<'hir> intravisit::Visitor<'hir> for TryFinder { + type Map = Map<'hir>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { + if self.found { + return; + } + match expr.kind { + hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true, + _ => intravisit::walk_expr(self, expr), + } + } + } + + let mut visitor = TryFinder { found: false }; + visitor.visit_expr(expr); + visitor.found +} + +fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_, '_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool +where + F: FnMut(&'hir hir::Expr<'hir>) -> bool, +{ + struct RetFinder { + in_stmt: bool, + failed: bool, + cb: F, + } + + struct WithStmtGuarg<'a, F> { + val: &'a mut RetFinder, + prev_in_stmt: bool, + } + + impl RetFinder { + fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> { + let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt); + WithStmtGuarg { + val: self, + prev_in_stmt, + } + } + } + + impl std::ops::Deref for WithStmtGuarg<'_, F> { + type Target = RetFinder; + + fn deref(&self) -> &Self::Target { + self.val + } + } + + impl std::ops::DerefMut for WithStmtGuarg<'_, F> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.val + } + } + + impl Drop for WithStmtGuarg<'_, F> { + fn drop(&mut self) { + self.val.in_stmt = self.prev_in_stmt; + } + } + + impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder { + type Map = Map<'hir>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } + + fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) { + intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt) + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) { + if self.failed { + return; + } + if self.in_stmt { + match expr.kind { + hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr), + _ => intravisit::walk_expr(self, expr), + } + } else { + match expr.kind { + hir::ExprKind::Match(cond, arms, _) => { + self.inside_stmt(true).visit_expr(cond); + for arm in arms { + self.visit_expr(arm.body); + } + }, + hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr), + hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr), + _ => self.failed |= !(self.cb)(expr), + } + } + } + } + + !contains_try(expr) && { + let mut ret_finder = RetFinder { + in_stmt: false, + failed: false, + cb: callback, + }; + ret_finder.visit_expr(expr); + !ret_finder.failed + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index e6094edc5d7..626427c15ec 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1,3 +1,4 @@ +mod bind_instead_of_map; mod inefficient_to_string; mod manual_saturating_arithmetic; mod option_map_unwrap_or; @@ -7,6 +8,7 @@ use std::borrow::Cow; use std::fmt; use std::iter; +use bind_instead_of_map::BindInsteadOfMap; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; @@ -306,27 +308,34 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`. + /// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or + /// `_.or_else(|x| Err(y))`. /// /// **Why is this bad?** Readability, this can be written more concisely as - /// `_.map(|x| y)`. + /// `_.map(|x| y)` or `_.map_err(|x| y)`. /// /// **Known problems:** None /// /// **Example:** /// /// ```rust - /// let x = Some("foo"); - /// let _ = x.and_then(|s| Some(s.len())); + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().and_then(|s| Some(s.len())); + /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) }); + /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) }); /// ``` /// /// The correct use would be: /// /// ```rust - /// let x = Some("foo"); - /// let _ = x.map(|s| s.len()); + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().map(|s| s.len()); + /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 }); + /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 }); /// ``` - pub OPTION_AND_THEN_SOME, + pub BIND_INSTEAD_OF_MAP, complexity, "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`" } @@ -1243,7 +1252,7 @@ declare_lint_pass!(Methods => [ MAP_UNWRAP_OR, RESULT_MAP_OR_INTO_OPTION, OPTION_MAP_OR_NONE, - OPTION_AND_THEN_SOME, + BIND_INSTEAD_OF_MAP, OR_FUN_CALL, EXPECT_FUN_CALL, CHARS_NEXT_CMP, @@ -1302,7 +1311,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), ["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]), ["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]), - ["and_then", ..] => lint_option_and_then_some(cx, expr, arg_lists[0]), + ["and_then", ..] => { + bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]); + bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]); + }, + ["or_else", ..] => { + bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]); + }, ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), @@ -2601,73 +2616,6 @@ fn lint_map_or_none<'a, 'tcx>( ); } -/// Lint use of `_.and_then(|x| Some(y))` for `Option`s -fn lint_option_and_then_some(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - const LINT_MSG: &str = "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"; - const NO_OP_MSG: &str = "using `Option.and_then(Some)`, which is a no-op"; - - let ty = cx.tables.expr_ty(&args[0]); - if !is_type_diagnostic_item(cx, ty, sym!(option_type)) { - return; - } - - match args[1].kind { - hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { - let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); - if_chain! { - if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind; - if let hir::ExprKind::Path(ref qpath) = some_expr.kind; - if match_qpath(qpath, &paths::OPTION_SOME); - if some_args.len() == 1; - then { - let inner_expr = &some_args[0]; - - if contains_return(inner_expr) { - return; - } - - let some_inner_snip = if inner_expr.span.from_expansion() { - snippet_with_macro_callsite(cx, inner_expr.span, "_") - } else { - snippet(cx, inner_expr.span, "_") - }; - - let closure_args_snip = snippet(cx, closure_args_span, ".."); - let option_snip = snippet(cx, args[0].span, ".."); - let note = format!("{}.map({} {})", option_snip, closure_args_snip, some_inner_snip); - span_lint_and_sugg( - cx, - OPTION_AND_THEN_SOME, - expr.span, - LINT_MSG, - "try this", - note, - Applicability::MachineApplicable, - ); - } - } - }, - // `_.and_then(Some)` case, which is no-op. - hir::ExprKind::Path(ref qpath) => { - if match_qpath(qpath, &paths::OPTION_SOME) { - let option_snip = snippet(cx, args[0].span, ".."); - let note = format!("{}", option_snip); - span_lint_and_sugg( - cx, - OPTION_AND_THEN_SOME, - expr.span, - NO_OP_MSG, - "use the expression directly", - note, - Applicability::MachineApplicable, - ); - } - }, - _ => {}, - } -} - /// lint use of `filter().next()` for `Iterators` fn lint_filter_next<'a, 'tcx>( cx: &LateContext<'a, 'tcx>, diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index f50adbc48ab..6ed9ff22e46 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -2230,7 +2230,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitHasher { ); if !vis.suggestions.is_empty() { - multispan_sugg(diag, "...and use generic constructor".into(), vis.suggestions); + multispan_sugg(diag, "...and use generic constructor", vis.suggestions); } } diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index e5e3bf453a0..5b4e2906b5f 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -66,6 +66,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "bit_mask", }, + Lint { + name: "bind_instead_of_map", + group: "complexity", + desc: "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`", + deprecation: None, + module: "methods", + }, Lint { name: "blacklisted_name", group: "style", @@ -1578,13 +1585,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "eq_op", }, - Lint { - name: "option_and_then_some", - group: "complexity", - desc: "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`", - deprecation: None, - module: "methods", - }, Lint { name: "option_as_ref_deref", group: "complexity", diff --git a/tests/ui/bind_instead_of_map.fixed b/tests/ui/bind_instead_of_map.fixed new file mode 100644 index 00000000000..5815550d7a6 --- /dev/null +++ b/tests/ui/bind_instead_of_map.fixed @@ -0,0 +1,25 @@ +// run-rustfix +#![deny(clippy::bind_instead_of_map)] + +// need a main anyway, use it get rid of unused warnings too +pub fn main() { + let x = Some(5); + // the easiest cases + let _ = x; + let _ = x.map(|o| o + 1); + // and an easy counter-example + let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); + + // Different type + let x: Result = Ok(1); + let _ = x; +} + +pub fn foo() -> Option { + let x = Some(String::from("hello")); + Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) +} + +pub fn example2(x: bool) -> Option<&'static str> { + Some("a").and_then(|s| Some(if x { s } else { return None })) +} diff --git a/tests/ui/bind_instead_of_map.rs b/tests/ui/bind_instead_of_map.rs new file mode 100644 index 00000000000..623b100a4ce --- /dev/null +++ b/tests/ui/bind_instead_of_map.rs @@ -0,0 +1,25 @@ +// run-rustfix +#![deny(clippy::bind_instead_of_map)] + +// need a main anyway, use it get rid of unused warnings too +pub fn main() { + let x = Some(5); + // the easiest cases + let _ = x.and_then(Some); + let _ = x.and_then(|o| Some(o + 1)); + // and an easy counter-example + let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); + + // Different type + let x: Result = Ok(1); + let _ = x.and_then(Ok); +} + +pub fn foo() -> Option { + let x = Some(String::from("hello")); + Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) +} + +pub fn example2(x: bool) -> Option<&'static str> { + Some("a").and_then(|s| Some(if x { s } else { return None })) +} diff --git a/tests/ui/bind_instead_of_map.stderr b/tests/ui/bind_instead_of_map.stderr new file mode 100644 index 00000000000..24c6b7f9ef3 --- /dev/null +++ b/tests/ui/bind_instead_of_map.stderr @@ -0,0 +1,26 @@ +error: using `Option.and_then(Some)`, which is a no-op + --> $DIR/bind_instead_of_map.rs:8:13 + | +LL | let _ = x.and_then(Some); + | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` + | +note: the lint level is defined here + --> $DIR/bind_instead_of_map.rs:2:9 + | +LL | #![deny(clippy::bind_instead_of_map)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map.rs:9:13 + | +LL | let _ = x.and_then(|o| Some(o + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)` + +error: using `Result.and_then(Ok)`, which is a no-op + --> $DIR/bind_instead_of_map.rs:15:13 + | +LL | let _ = x.and_then(Ok); + | ^^^^^^^^^^^^^^ help: use the expression directly: `x` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/bind_instead_of_map_multipart.rs b/tests/ui/bind_instead_of_map_multipart.rs new file mode 100644 index 00000000000..91d9d11e3c1 --- /dev/null +++ b/tests/ui/bind_instead_of_map_multipart.rs @@ -0,0 +1,61 @@ +#![deny(clippy::bind_instead_of_map)] +#![allow(clippy::blocks_in_if_conditions)] + +pub fn main() { + let _ = Some("42").and_then(|s| if s.len() < 42 { Some(0) } else { Some(s.len()) }); + let _ = Some("42").and_then(|s| if s.len() < 42 { None } else { Some(s.len()) }); + + let _ = Ok::<_, ()>("42").and_then(|s| if s.len() < 42 { Ok(0) } else { Ok(s.len()) }); + let _ = Ok::<_, ()>("42").and_then(|s| if s.len() < 42 { Err(()) } else { Ok(s.len()) }); + + let _ = Err::<(), _>("42").or_else(|s| if s.len() < 42 { Err(s.len() + 20) } else { Err(s.len()) }); + let _ = Err::<(), _>("42").or_else(|s| if s.len() < 42 { Ok(()) } else { Err(s.len()) }); + + hard_example(); + macro_example(); +} + +fn hard_example() { + Some("42").and_then(|s| { + if { + if s == "43" { + return Some(43); + } + s == "42" + } { + return Some(45); + } + match s.len() { + 10 => Some(2), + 20 => { + if foo() { + return { + if foo() { + return Some(20); + } + println!("foo"); + Some(3) + }; + } + Some(20) + }, + 40 => Some(30), + _ => Some(1), + } + }); +} + +fn foo() -> bool { + true +} + +macro_rules! m { + () => { + Some(10) + }; +} + +fn macro_example() { + let _ = Some("").and_then(|s| if s.len() == 20 { m!() } else { Some(20) }); + let _ = Some("").and_then(|s| if s.len() == 20 { Some(m!()) } else { Some(Some(20)) }); +} diff --git a/tests/ui/bind_instead_of_map_multipart.stderr b/tests/ui/bind_instead_of_map_multipart.stderr new file mode 100644 index 00000000000..50ce2f4051e --- /dev/null +++ b/tests/ui/bind_instead_of_map_multipart.stderr @@ -0,0 +1,73 @@ +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:5:13 + | +LL | let _ = Some("42").and_then(|s| if s.len() < 42 { Some(0) } else { Some(s.len()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/bind_instead_of_map_multipart.rs:1:9 + | +LL | #![deny(clippy::bind_instead_of_map)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try this + | +LL | let _ = Some("42").map(|s| if s.len() < 42 { 0 } else { s.len() }); + | ^^^ ^ ^^^^^^^ + +error: using `Result.and_then(|x| Ok(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:8:13 + | +LL | let _ = Ok::<_, ()>("42").and_then(|s| if s.len() < 42 { Ok(0) } else { Ok(s.len()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try this + | +LL | let _ = Ok::<_, ()>("42").map(|s| if s.len() < 42 { 0 } else { s.len() }); + | ^^^ ^ ^^^^^^^ + +error: using `Result.or_else(|x| Err(y))`, which is more succinctly expressed as `map_err(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:11:13 + | +LL | let _ = Err::<(), _>("42").or_else(|s| if s.len() < 42 { Err(s.len() + 20) } else { Err(s.len()) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try this + | +LL | let _ = Err::<(), _>("42").map_err(|s| if s.len() < 42 { s.len() + 20 } else { s.len() }); + | ^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^ + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:19:5 + | +LL | / Some("42").and_then(|s| { +LL | | if { +LL | | if s == "43" { +LL | | return Some(43); +... | +LL | | } +LL | | }); + | |______^ + | +help: try this + | +LL | Some("42").map(|s| { +LL | if { +LL | if s == "43" { +LL | return 43; +LL | } +LL | s == "42" + ... + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/bind_instead_of_map_multipart.rs:60:13 + | +LL | let _ = Some("").and_then(|s| if s.len() == 20 { Some(m!()) } else { Some(Some(20)) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try this + | +LL | let _ = Some("").map(|s| if s.len() == 20 { m!() } else { Some(20) }); + | ^^^ ^^^^ ^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/blocks_in_if_conditions.fixed b/tests/ui/blocks_in_if_conditions.fixed index 9040552cefc..14562c4d32c 100644 --- a/tests/ui/blocks_in_if_conditions.fixed +++ b/tests/ui/blocks_in_if_conditions.fixed @@ -63,10 +63,10 @@ fn block_in_assert() { let opt = Some(42); assert!(opt .as_ref() - .and_then(|val| { + .map(|val| { let mut v = val * 2; v -= 1; - Some(v * 3) + v * 3 }) .is_some()); } diff --git a/tests/ui/blocks_in_if_conditions.rs b/tests/ui/blocks_in_if_conditions.rs index 2fe409b22d3..bda87650f6d 100644 --- a/tests/ui/blocks_in_if_conditions.rs +++ b/tests/ui/blocks_in_if_conditions.rs @@ -63,10 +63,10 @@ fn block_in_assert() { let opt = Some(42); assert!(opt .as_ref() - .and_then(|val| { + .map(|val| { let mut v = val * 2; v -= 1; - Some(v * 3) + v * 3 }) .is_some()); } diff --git a/tests/ui/option_and_then_some.fixed b/tests/ui/option_and_then_some.fixed deleted file mode 100644 index 035bc1e5465..00000000000 --- a/tests/ui/option_and_then_some.fixed +++ /dev/null @@ -1,25 +0,0 @@ -// run-rustfix -#![deny(clippy::option_and_then_some)] - -// need a main anyway, use it get rid of unused warnings too -pub fn main() { - let x = Some(5); - // the easiest cases - let _ = x; - let _ = x.map(|o| o + 1); - // and an easy counter-example - let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); - - // Different type - let x: Result = Ok(1); - let _ = x.and_then(Ok); -} - -pub fn foo() -> Option { - let x = Some(String::from("hello")); - Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) -} - -pub fn example2(x: bool) -> Option<&'static str> { - Some("a").and_then(|s| Some(if x { s } else { return None })) -} diff --git a/tests/ui/option_and_then_some.rs b/tests/ui/option_and_then_some.rs deleted file mode 100644 index d49da7813c6..00000000000 --- a/tests/ui/option_and_then_some.rs +++ /dev/null @@ -1,25 +0,0 @@ -// run-rustfix -#![deny(clippy::option_and_then_some)] - -// need a main anyway, use it get rid of unused warnings too -pub fn main() { - let x = Some(5); - // the easiest cases - let _ = x.and_then(Some); - let _ = x.and_then(|o| Some(o + 1)); - // and an easy counter-example - let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); - - // Different type - let x: Result = Ok(1); - let _ = x.and_then(Ok); -} - -pub fn foo() -> Option { - let x = Some(String::from("hello")); - Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) -} - -pub fn example2(x: bool) -> Option<&'static str> { - Some("a").and_then(|s| Some(if x { s } else { return None })) -} diff --git a/tests/ui/option_and_then_some.stderr b/tests/ui/option_and_then_some.stderr deleted file mode 100644 index 47825491765..00000000000 --- a/tests/ui/option_and_then_some.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error: using `Option.and_then(Some)`, which is a no-op - --> $DIR/option_and_then_some.rs:8:13 - | -LL | let _ = x.and_then(Some); - | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` - | -note: the lint level is defined here - --> $DIR/option_and_then_some.rs:2:9 - | -LL | #![deny(clippy::option_and_then_some)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` - --> $DIR/option_and_then_some.rs:9:13 - | -LL | let _ = x.and_then(|o| Some(o + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)` - -error: aborting due to 2 previous errors - diff --git a/tests/ui/option_map_or_none.fixed b/tests/ui/option_map_or_none.fixed index decbae4e5af..d80c3c7c1b7 100644 --- a/tests/ui/option_map_or_none.fixed +++ b/tests/ui/option_map_or_none.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![allow(clippy::option_and_then_some)] +#![allow(clippy::bind_instead_of_map)] fn main() { let opt = Some(1); diff --git a/tests/ui/option_map_or_none.rs b/tests/ui/option_map_or_none.rs index 0f1d2218d5d..629842419e5 100644 --- a/tests/ui/option_map_or_none.rs +++ b/tests/ui/option_map_or_none.rs @@ -1,6 +1,6 @@ // run-rustfix -#![allow(clippy::option_and_then_some)] +#![allow(clippy::bind_instead_of_map)] fn main() { let opt = Some(1); -- cgit 1.4.1-3-g733a5 From ecd0a67b01e13d7a80d2f64bbfa5da1e568367e5 Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Wed, 20 May 2020 13:23:51 +0300 Subject: Make match_wild_err_arm pedantic, and update help messages --- clippy_lints/src/lib.rs | 3 +-- clippy_lints/src/matches.rs | 6 +++--- src/lintlist/mod.rs | 2 +- tests/ui/match_wild_err_arm.stderr | 8 ++++---- 4 files changed, 9 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 4d4fff883b3..057d39d4c82 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1141,6 +1141,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), LintId::of(&matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), + LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), LintId::of(&methods::FILTER_MAP_NEXT), @@ -1285,7 +1286,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::MATCH_OVERLAPPING_ARM), LintId::of(&matches::MATCH_REF_PATS), LintId::of(&matches::MATCH_SINGLE_BINDING), - LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH), LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), @@ -1476,7 +1476,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), LintId::of(&matches::MATCH_OVERLAPPING_ARM), LintId::of(&matches::MATCH_REF_PATS), - LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH), LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 4106e5013b9..94380acfcfd 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -168,7 +168,7 @@ declare_clippy_lint! { /// **What it does:** Checks for arm which matches all errors with `Err(_)` /// and take drastic actions like `panic!`. /// - /// **Why is this bad?** It is generally a bad practice, just like + /// **Why is this bad?** It is generally a bad practice, similar to /// catching all exceptions in java with `catch(Exception)` /// /// **Known problems:** None. @@ -182,7 +182,7 @@ declare_clippy_lint! { /// } /// ``` pub MATCH_WILD_ERR_ARM, - style, + pedantic, "a `match` with `Err(_)` arm and take drastic actions" } @@ -711,7 +711,7 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>]) arm.pat.span, &format!("`Err({})` matches all errors", &ident_bind_name), None, - "match each error separately or use the error output", + "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", ); } } diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 0bf46491d31..8211a57b564 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1195,7 +1195,7 @@ pub static ref ALL_LINTS: Vec = vec![ }, Lint { name: "match_wild_err_arm", - group: "style", + group: "pedantic", desc: "a `match` with `Err(_)` arm and take drastic actions", deprecation: None, module: "matches", diff --git a/tests/ui/match_wild_err_arm.stderr b/tests/ui/match_wild_err_arm.stderr index 20d4c933418..6a2a02987de 100644 --- a/tests/ui/match_wild_err_arm.stderr +++ b/tests/ui/match_wild_err_arm.stderr @@ -5,7 +5,7 @@ LL | Err(_) => panic!("err"), | ^^^^^^ | = note: `-D clippy::match-wild-err-arm` implied by `-D warnings` - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: `Err(_)` matches all errors --> $DIR/match_wild_err_arm.rs:17:9 @@ -13,7 +13,7 @@ error: `Err(_)` matches all errors LL | Err(_) => panic!(), | ^^^^^^ | - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: `Err(_)` matches all errors --> $DIR/match_wild_err_arm.rs:23:9 @@ -21,7 +21,7 @@ error: `Err(_)` matches all errors LL | Err(_) => { | ^^^^^^ | - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: `Err(_e)` matches all errors --> $DIR/match_wild_err_arm.rs:31:9 @@ -29,7 +29,7 @@ error: `Err(_e)` matches all errors LL | Err(_e) => panic!(), | ^^^^^^^ | - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: aborting due to 4 previous errors -- cgit 1.4.1-3-g733a5 From 705bfdcc467c0ddd7eb61d3adb24809b27bae891 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 22 May 2020 11:46:17 +0200 Subject: Extend `useless_conversion` lint with TryInto --- clippy_lints/src/useless_conversion.rs | 38 +++++++++++++++++++++++++++------- clippy_lints/src/utils/paths.rs | 1 + src/lintlist/mod.rs | 2 +- tests/ui/useless_conversion_try.rs | 17 +++++++++++---- tests/ui/useless_conversion_try.stderr | 38 +++++++++++++++++++++++++++------- 5 files changed, 77 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 0b080d9be2c..1645c5777b2 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -10,8 +10,8 @@ use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { - /// **What it does:** Checks for `Into`, `From`, `TryFrom`,`IntoIter` calls that useless converts - /// to the same type as caller. + /// **What it does:** Checks for `Into`, `TryInto`, `From`, `TryFrom`,`IntoIter` calls + /// that useless converts to the same type as caller. /// /// **Why is this bad?** Redundant code. /// @@ -29,7 +29,7 @@ declare_clippy_lint! { /// ``` pub USELESS_CONVERSION, complexity, - "calls to `Into`, `From`, `TryFrom`, `IntoIter` that performs useless conversions to the same type" + "calls to `Into`, `TryInto`, `From`, `TryFrom`, `IntoIter` that performs useless conversions to the same type" } #[derive(Default)] @@ -39,6 +39,7 @@ pub struct UselessConversion { impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]); +#[allow(clippy::too_many_lines)] impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { if e.span.from_expansion() { @@ -66,7 +67,6 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { let b = cx.tables.expr_ty(&args[0]); if same_tys(cx, a, b) { let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); - span_lint_and_sugg( cx, USELESS_CONVERSION, @@ -94,6 +94,27 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { ); } } + if match_trait_method(cx, e, &paths::TRY_INTO_TRAIT) && &*name.ident.as_str() == "try_into" { + if_chain! { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if is_type_diagnostic_item(cx, a, sym!(result_type)); + if let ty::Adt(_, substs) = a.kind; + if let Some(a_type) = substs.types().next(); + if same_tys(cx, a_type, b); + + then { + span_lint_and_help( + cx, + USELESS_CONVERSION, + e.span, + "Useless conversion to the same type", + None, + "consider removing `.try_into()`", + ); + } + } + } }, ExprKind::Call(ref path, ref args) => { @@ -109,7 +130,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { if match_def_path(cx, def_id, &paths::TRY_FROM); if is_type_diagnostic_item(cx, a, sym!(result_type)); if let ty::Adt(_, substs) = a.kind; - if let Some(a_type) = substs.types().nth(0); + if let Some(a_type) = substs.types().next(); if same_tys(cx, a_type, b); then { @@ -125,8 +146,11 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { } } - if match_def_path(cx, def_id, &paths::FROM_FROM) { - if same_tys(cx, a, b) { + if_chain! { + if match_def_path(cx, def_id, &paths::FROM_FROM); + if same_tys(cx, a, b); + + then { let sugg = snippet(cx, args[0].span.source_callsite(), "").into_owned(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index e00d726282a..779da7e6bf2 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -131,6 +131,7 @@ pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"]; pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"]; pub const TRY_FROM_ERROR: [&str; 4] = ["std", "ops", "Try", "from_error"]; pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"]; +pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"]; pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"]; pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"]; pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 8211a57b564..f63301c7db0 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -2421,7 +2421,7 @@ pub static ref ALL_LINTS: Vec = vec![ Lint { name: "useless_conversion", group: "complexity", - desc: "calls to `Into`/`From`/`IntoIter` that performs useless conversions to the same type", + desc: "calls to `Into`, `TryInto`, `From`, `TryFrom`, `IntoIter` that performs useless conversions to the same type", deprecation: None, module: "useless_conversion", }, diff --git a/tests/ui/useless_conversion_try.rs b/tests/ui/useless_conversion_try.rs index abf0c891b52..ab4f960edb7 100644 --- a/tests/ui/useless_conversion_try.rs +++ b/tests/ui/useless_conversion_try.rs @@ -1,12 +1,16 @@ #![deny(clippy::useless_conversion)] -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; fn test_generic(val: T) -> T { - T::try_from(val).unwrap() + let _ = T::try_from(val).unwrap(); + val.try_into().unwrap() } fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.try_into().unwrap(); + let _: U = val.try_into().unwrap(); let _ = U::try_from(val).unwrap(); } @@ -14,12 +18,17 @@ fn main() { test_generic(10i32); test_generic2::(10i32); + let _: String = "foo".try_into().unwrap(); let _: String = TryFrom::try_from("foo").unwrap(); let _ = String::try_from("foo").unwrap(); #[allow(clippy::useless_conversion)] - let _ = String::try_from("foo").unwrap(); - + { + let _ = String::try_from("foo").unwrap(); + let _: String = "foo".try_into().unwrap(); + } + let _: String = "foo".to_string().try_into().unwrap(); let _: String = TryFrom::try_from("foo".to_string()).unwrap(); let _ = String::try_from("foo".to_string()).unwrap(); let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); + let _: String = format!("Hello {}", "world").try_into().unwrap(); } diff --git a/tests/ui/useless_conversion_try.stderr b/tests/ui/useless_conversion_try.stderr index b3cb01fbe32..5afb5dc45d3 100644 --- a/tests/ui/useless_conversion_try.stderr +++ b/tests/ui/useless_conversion_try.stderr @@ -1,8 +1,8 @@ error: Useless conversion to the same type - --> $DIR/useless_conversion_try.rs:6:5 + --> $DIR/useless_conversion_try.rs:6:13 | -LL | T::try_from(val).unwrap() - | ^^^^^^^^^^^^^^^^ +LL | let _ = T::try_from(val).unwrap(); + | ^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> $DIR/useless_conversion_try.rs:1:9 @@ -12,7 +12,23 @@ LL | #![deny(clippy::useless_conversion)] = help: consider removing `T::try_from()` error: Useless conversion to the same type - --> $DIR/useless_conversion_try.rs:22:21 + --> $DIR/useless_conversion_try.rs:7:5 + | +LL | val.try_into().unwrap() + | ^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: Useless conversion to the same type + --> $DIR/useless_conversion_try.rs:29:21 + | +LL | let _: String = "foo".to_string().try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: Useless conversion to the same type + --> $DIR/useless_conversion_try.rs:30:21 | LL | let _: String = TryFrom::try_from("foo".to_string()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +36,7 @@ LL | let _: String = TryFrom::try_from("foo".to_string()).unwrap(); = help: consider removing `TryFrom::try_from()` error: Useless conversion to the same type - --> $DIR/useless_conversion_try.rs:23:13 + --> $DIR/useless_conversion_try.rs:31:13 | LL | let _ = String::try_from("foo".to_string()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -28,12 +44,20 @@ LL | let _ = String::try_from("foo".to_string()).unwrap(); = help: consider removing `String::try_from()` error: Useless conversion to the same type - --> $DIR/useless_conversion_try.rs:24:13 + --> $DIR/useless_conversion_try.rs:32:13 | LL | let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider removing `String::try_from()` -error: aborting due to 4 previous errors +error: Useless conversion to the same type + --> $DIR/useless_conversion_try.rs:33:21 + | +LL | let _: String = format!("Hello {}", "world").try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: aborting due to 7 previous errors -- cgit 1.4.1-3-g733a5 From 7fd3bd0f57e11a65641501d6a898328ecb83ca77 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Fri, 24 Apr 2020 00:14:03 +0200 Subject: Register redundant_field_names and non_expressive_names as early passes --- clippy_lints/src/lib.rs | 12 ++++++------ src/driver.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 057d39d4c82..902f3d56c1e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -346,13 +346,8 @@ mod reexport { /// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. /// /// Used in `./src/driver.rs`. -pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &Conf) { +pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { store.register_pre_expansion_pass(|| box write::Write::default()); - store.register_pre_expansion_pass(|| box redundant_field_names::RedundantFieldNames); - let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; - store.register_pre_expansion_pass(move || box non_expressive_names::NonExpressiveNames { - single_char_binding_names_threshold, - }); store.register_pre_expansion_pass(|| box attrs::EarlyAttributes); store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro); } @@ -1066,6 +1061,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive); store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); + store.register_early_pass(|| box redundant_field_names::RedundantFieldNames); + let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; + store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { + single_char_binding_names_threshold, + }); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), diff --git a/src/driver.rs b/src/driver.rs index d3a7e24937f..70c47b42682 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -79,7 +79,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { let conf = clippy_lints::read_conf(&[], &sess); clippy_lints::register_plugins(&mut lint_store, &sess, &conf); - clippy_lints::register_pre_expansion_lints(&mut lint_store, &conf); + clippy_lints::register_pre_expansion_lints(&mut lint_store); clippy_lints::register_renamed(&mut lint_store); })); -- cgit 1.4.1-3-g733a5 From 5faab874f9f8655c8f284944b5acdede5c088af4 Mon Sep 17 00:00:00 2001 From: Tim Nielens Date: Sat, 23 May 2020 00:07:09 +0200 Subject: new lint: vec_resize_to_zero --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 5 +++ clippy_lints/src/utils/paths.rs | 1 + clippy_lints/src/vec_resize_to_zero.rs | 59 ++++++++++++++++++++++++++++++++++ src/lintlist/mod.rs | 7 ++++ tests/ui/vec_resize_to_zero.rs | 15 +++++++++ tests/ui/vec_resize_to_zero.stderr | 13 ++++++++ 7 files changed, 101 insertions(+) create mode 100644 clippy_lints/src/vec_resize_to_zero.rs create mode 100644 tests/ui/vec_resize_to_zero.rs create mode 100644 tests/ui/vec_resize_to_zero.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac9057199f..f7dae3dcfff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1630,6 +1630,7 @@ Released 2018-09-13 [`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute [`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec [`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box +[`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero [`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask [`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads [`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 902f3d56c1e..4f0ecab393d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -325,6 +325,7 @@ mod unwrap; mod use_self; mod useless_conversion; mod vec; +mod vec_resize_to_zero; mod verbose_file_reads; mod wildcard_dependencies; mod wildcard_imports; @@ -847,6 +848,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &utils::internal_lints::OUTER_EXPN_EXPN_DATA, &utils::internal_lints::PRODUCE_ICE, &vec::USELESS_VEC, + &vec_resize_to_zero::VEC_RESIZE_TO_ZERO, &verbose_file_reads::VERBOSE_FILE_READS, &wildcard_dependencies::WILDCARD_DEPENDENCIES, &wildcard_imports::ENUM_GLOB_USE, @@ -1062,6 +1064,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive); store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); store.register_early_pass(|| box redundant_field_names::RedundantFieldNames); + store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero); let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { single_char_binding_names_threshold, @@ -1430,6 +1433,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&vec::USELESS_VEC), + LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO), LintId::of(&write::PRINTLN_EMPTY_STRING), LintId::of(&write::PRINT_LITERAL), LintId::of(&write::PRINT_WITH_NEWLINE), @@ -1677,6 +1681,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), + LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO), ]); store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index 779da7e6bf2..3b7e9739211 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -138,5 +138,6 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"]; pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; +pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; diff --git a/clippy_lints/src/vec_resize_to_zero.rs b/clippy_lints/src/vec_resize_to_zero.rs new file mode 100644 index 00000000000..86cbfa8203d --- /dev/null +++ b/clippy_lints/src/vec_resize_to_zero.rs @@ -0,0 +1,59 @@ +use crate::utils::span_lint_and_then; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +use crate::utils::{match_def_path, paths}; +use rustc_ast::ast::LitKind; +use rustc_hir as hir; + +declare_clippy_lint! { + /// **What it does:** Finds occurences of `Vec::resize(0, an_int)` + /// + /// **Why is this bad?** This is probably an argument inversion mistake. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// vec!(1, 2, 3, 4, 5).resize(0, 5) + /// ``` + pub VEC_RESIZE_TO_ZERO, + correctness, + "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" +} + +declare_lint_pass!(VecResizeToZero => [VEC_RESIZE_TO_ZERO]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for VecResizeToZero { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let hir::ExprKind::MethodCall(path_segment, _, ref args) = expr.kind; + if let Some(method_def_id) = cx.tables.type_dependent_def_id(expr.hir_id); + if match_def_path(cx, method_def_id, &paths::VEC_RESIZE) && args.len() == 3; + if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = args[1].kind; + if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = args[2].kind; + then { + let method_call_span = expr.span.with_lo(path_segment.ident.span.lo()); + span_lint_and_then( + cx, + VEC_RESIZE_TO_ZERO, + expr.span, + "emptying a vector with `resize`", + |db| { + db.help("the arguments may be inverted..."); + db.span_suggestion( + method_call_span, + "...or you can empty the vector with", + "clear()".to_string(), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index f63301c7db0..1e94ca00c14 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -2460,6 +2460,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "types", }, + Lint { + name: "vec_resize_to_zero", + group: "correctness", + desc: "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake", + deprecation: None, + module: "vec_resize_to_zero", + }, Lint { name: "verbose_bit_mask", group: "style", diff --git a/tests/ui/vec_resize_to_zero.rs b/tests/ui/vec_resize_to_zero.rs new file mode 100644 index 00000000000..0263e2f5f20 --- /dev/null +++ b/tests/ui/vec_resize_to_zero.rs @@ -0,0 +1,15 @@ +#![warn(clippy::vec_resize_to_zero)] + +fn main() { + // applicable here + vec![1, 2, 3, 4, 5].resize(0, 5); + + // not applicable + vec![1, 2, 3, 4, 5].resize(2, 5); + + // applicable here, but only implemented for integer litterals for now + vec!["foo", "bar", "baz"].resize(0, "bar"); + + // not applicable + vec!["foo", "bar", "baz"].resize(2, "bar") +} diff --git a/tests/ui/vec_resize_to_zero.stderr b/tests/ui/vec_resize_to_zero.stderr new file mode 100644 index 00000000000..feb846298c6 --- /dev/null +++ b/tests/ui/vec_resize_to_zero.stderr @@ -0,0 +1,13 @@ +error: emptying a vector with `resize` + --> $DIR/vec_resize_to_zero.rs:5:5 + | +LL | vec![1, 2, 3, 4, 5].resize(0, 5); + | ^^^^^^^^^^^^^^^^^^^^------------ + | | + | help: ...or you can empty the vector with: `clear()` + | + = note: `-D clippy::vec-resize-to-zero` implied by `-D warnings` + = help: the arguments may be inverted... + +error: aborting due to previous error + -- cgit 1.4.1-3-g733a5 From 18b5ceed7991e3d8616b74b42de26330ca4c40db Mon Sep 17 00:00:00 2001 From: djugei Date: Sun, 31 May 2020 16:00:29 +0200 Subject: ran update_lints --- clippy_lints/src/lib.rs | 3 +-- src/lintlist/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 902f3d56c1e..6475fa67d25 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1164,6 +1164,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::CAST_POSSIBLE_TRUNCATION), LintId::of(&types::CAST_POSSIBLE_WRAP), LintId::of(&types::CAST_PRECISION_LOSS), + LintId::of(&types::CAST_PTR_ALIGNMENT), LintId::of(&types::CAST_SIGN_LOSS), LintId::of(&types::IMPLICIT_HASHER), LintId::of(&types::INVALID_UPCAST_COMPARISONS), @@ -1410,7 +1411,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::ABSURD_EXTREME_COMPARISONS), LintId::of(&types::BORROWED_BOX), LintId::of(&types::BOX_VEC), - LintId::of(&types::CAST_PTR_ALIGNMENT), LintId::of(&types::CAST_REF_TO_MUT), LintId::of(&types::CHAR_LIT_AS_U8), LintId::of(&types::FN_TO_NUMERIC_CAST), @@ -1669,7 +1669,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&transmute::WRONG_TRANSMUTE), LintId::of(&transmuting_null::TRANSMUTING_NULL), LintId::of(&types::ABSURD_EXTREME_COMPARISONS), - LintId::of(&types::CAST_PTR_ALIGNMENT), LintId::of(&types::CAST_REF_TO_MUT), LintId::of(&types::UNIT_CMP), LintId::of(&unicode::ZERO_WIDTH_SPACE), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index f63301c7db0..b9c84654593 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -166,7 +166,7 @@ pub static ref ALL_LINTS: Vec = vec![ }, Lint { name: "cast_ptr_alignment", - group: "correctness", + group: "pedantic", desc: "cast from a pointer to a more-strictly-aligned pointer", deprecation: None, module: "types", -- cgit 1.4.1-3-g733a5 From 7e843515d9525b6389c3fc1bcfa6ae046c1351dc Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Thu, 14 May 2020 15:06:05 -0700 Subject: Created lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 4 ++++ clippy_lints/src/sort_by_key_reverse.rs | 28 ++++++++++++++++++++++++++++ src/lintlist/mod.rs | 7 +++++++ tests/ui/sort_by_key_reverse.rs | 5 +++++ 5 files changed, 45 insertions(+) create mode 100644 clippy_lints/src/sort_by_key_reverse.rs create mode 100644 tests/ui/sort_by_key_reverse.rs (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index f7dae3dcfff..c00f84bdb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1555,6 +1555,7 @@ Released 2018-09-13 [`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else [`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next [`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization +[`sort_by_key_reverse`]: https://rust-lang.github.io/rust-clippy/master/index.html#sort_by_key_reverse [`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string [`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add [`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 38cfa212d9f..f51855badff 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -304,6 +304,7 @@ mod serde_api; mod shadow; mod single_component_path_imports; mod slow_vector_initialization; +mod sort_by_key_reverse; mod strings; mod suspicious_trait_impl; mod swap; @@ -779,6 +780,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &shadow::SHADOW_UNRELATED, &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, + &sort_by_key_reverse::SORT_BY_KEY_REVERSE, &strings::STRING_ADD, &strings::STRING_ADD_ASSIGN, &strings::STRING_LIT_AS_BYTES, @@ -1391,6 +1393,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), + LintId::of(&sort_by_key_reverse::SORT_BY_KEY_REVERSE), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -1592,6 +1595,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), + LintId::of(&sort_by_key_reverse::SORT_BY_KEY_REVERSE), LintId::of(&swap::MANUAL_SWAP), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs new file mode 100644 index 00000000000..65830afd0f8 --- /dev/null +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -0,0 +1,28 @@ +use rustc_lint::{LateLintPass, LateContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_hir::*; + +declare_clippy_lint! { + /// **What it does:** + /// + /// **Why is this bad?** + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// ``` + pub SORT_BY_KEY_REVERSE, + complexity, + "default lint description" +} + +declare_lint_pass!(SortByKeyReverse => [SORT_BY_KEY_REVERSE]); + +impl LateLintPass<'_, '_> for SortByKeyReverse {} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 69578732898..1b82f34c863 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1984,6 +1984,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "slow_vector_initialization", }, + Lint { + name: "sort_by_key_reverse", + group: "complexity", + desc: "default lint description", + deprecation: None, + module: "sort_by_key_reverse", + }, Lint { name: "string_add", group: "restriction", diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs new file mode 100644 index 00000000000..2338dc6e594 --- /dev/null +++ b/tests/ui/sort_by_key_reverse.rs @@ -0,0 +1,5 @@ +#![warn(clippy::sort_by_key_reverse)] + +fn main() { + // test code goes here +} -- cgit 1.4.1-3-g733a5 From 24847ea53e332853597aca2c7dfe48a9f3be1de8 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sat, 16 May 2020 13:50:33 -0700 Subject: Attempted start at sort_by_key_reverse lint --- clippy_lints/src/sort_by_key_reverse.rs | 71 +++++++++++++++++++++++++++++++-- src/lintlist/mod.rs | 2 +- tests/ui/sort_by_key_reverse.fixed | 0 tests/ui/sort_by_key_reverse.rs | 3 +- tests/ui/sort_by_key_reverse.stderr | 0 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tests/ui/sort_by_key_reverse.fixed create mode 100644 tests/ui/sort_by_key_reverse.stderr (limited to 'src') diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs index 65830afd0f8..7d7097a8125 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -1,28 +1,91 @@ +use crate::utils::{match_type, span_lint_and_sugg}; +use crate::utils::paths; +use crate::utils::sugg::Sugg; +use if_chain::if_chain; +use rustc_errors::Applicability; use rustc_lint::{LateLintPass, LateContext}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_hir::*; declare_clippy_lint! { /// **What it does:** + /// Detects when people use `Vec::sort_by` and pass in a function + /// which compares the second argument to the first. /// /// **Why is this bad?** + /// It is more clear to use `Vec::sort_by_key` and `std::cmp::Reverse` /// /// **Known problems:** None. /// /// **Example:** /// /// ```rust - /// // example code where clippy issues a warning + /// vec.sort_by(|a, b| b.foo().cmp(&a.foo())); /// ``` /// Use instead: /// ```rust - /// // example code which does not raise clippy warning + /// vec.sort_by_key(|e| Reverse(e.foo())); /// ``` pub SORT_BY_KEY_REVERSE, complexity, - "default lint description" + "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer" } declare_lint_pass!(SortByKeyReverse => [SORT_BY_KEY_REVERSE]); -impl LateLintPass<'_, '_> for SortByKeyReverse {} +struct LintTrigger { + vec_name: String, + closure_arg: String, + closure_reverse_body: String, + unstable: bool, +} + +fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { + if_chain! { + if let ExprKind::MethodCall(name_ident, _, args) = &expr.kind; + if let name = name_ident.ident.name.to_ident_string(); + if name == "sort_by" || name == "sort_unstable_by"; + if let [vec, Expr { kind: ExprKind::Closure(_, closure_decl, closure_body_id, _, _), .. }] = args; + if closure_decl.inputs.len() == 2; + if match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); + then { + let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); + let unstable = name == "sort_unstable_by"; + Some(LintTrigger { vec_name, unstable, closure_arg: "e".to_string(), closure_reverse_body: "e".to_string() }) + } else { + None + } + } +} + +impl LateLintPass<'_, '_> for SortByKeyReverse { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + println!("{:?}", expr); + span_lint_and_sugg( + cx, + SORT_BY_KEY_REVERSE, + expr.span, + "use Vec::sort_by_key here instead", + "try", + String::from("being a better person"), + Applicability::MachineApplicable, + ); + if let Some(trigger) = detect_lint(cx, expr) { + span_lint_and_sugg( + cx, + SORT_BY_KEY_REVERSE, + expr.span, + "use Vec::sort_by_key here instead", + "try", + format!( + "{}.sort{}_by_key(|{}| Reverse({}))", + trigger.vec_name, + if trigger.unstable { "_unstable" } else { "" }, + trigger.closure_arg, + trigger.closure_reverse_body, + ), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 1b82f34c863..b5d9ef0110e 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1987,7 +1987,7 @@ pub static ref ALL_LINTS: Vec = vec![ Lint { name: "sort_by_key_reverse", group: "complexity", - desc: "default lint description", + desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer", deprecation: None, module: "sort_by_key_reverse", }, diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key_reverse.fixed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs index 2338dc6e594..c0350f243c7 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key_reverse.rs @@ -1,5 +1,6 @@ #![warn(clippy::sort_by_key_reverse)] fn main() { - // test code goes here + let mut vec = vec![3, 6, 1, 2, 5]; + vec.sort_by(|a, b| b.cmp(a)); } diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr new file mode 100644 index 00000000000..e69de29bb2d -- cgit 1.4.1-3-g733a5 From 32fde0b5116b3a1115d11c49a9bf2af2ebdd5773 Mon Sep 17 00:00:00 2001 From: Ericko Samudera Date: Mon, 25 May 2020 23:22:01 +0700 Subject: New lint: iter_next_slice --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 3 ++ clippy_lints/src/loops.rs | 26 +++++------ clippy_lints/src/methods/mod.rs | 84 ++++++++++++++++++++++++++++++++++- clippy_lints/src/needless_continue.rs | 2 +- src/lintlist/mod.rs | 7 +++ tests/ui/into_iter_on_ref.fixed | 2 + tests/ui/into_iter_on_ref.rs | 2 + tests/ui/into_iter_on_ref.stderr | 8 +++- tests/ui/iter_next_slice.fixed | 24 ++++++++++ tests/ui/iter_next_slice.rs | 24 ++++++++++ tests/ui/iter_next_slice.stderr | 28 ++++++++++++ tests/ui/needless_collect.fixed | 2 +- tests/ui/needless_collect.stderr | 16 +++---- 14 files changed, 204 insertions(+), 25 deletions(-) create mode 100644 tests/ui/iter_next_slice.fixed create mode 100644 tests/ui/iter_next_slice.rs create mode 100644 tests/ui/iter_next_slice.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac9057199f..714e25a32ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1401,6 +1401,7 @@ Released 2018-09-13 [`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements [`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect [`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop +[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice [`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 057d39d4c82..7c16dbd8f26 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -669,6 +669,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::INTO_ITER_ON_REF, &methods::ITERATOR_STEP_BY_ZERO, &methods::ITER_CLONED_COLLECT, + &methods::ITER_NEXT_SLICE, &methods::ITER_NTH, &methods::ITER_NTH_ZERO, &methods::ITER_SKIP_NEXT, @@ -1303,6 +1304,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::ITERATOR_STEP_BY_ZERO), LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NEXT_SLICE), LintId::of(&methods::ITER_NTH), LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_SKIP_NEXT), @@ -1483,6 +1485,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::CHARS_NEXT_CMP), LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NEXT_SLICE), LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_SKIP_NEXT), LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index 38a5829b3f7..dbe41823a9c 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -27,7 +27,7 @@ use rustc_middle::middle::region; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::BytePos; +use rustc_span::symbol::Symbol; use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase}; use std::iter::{once, Iterator}; use std::mem; @@ -2381,32 +2381,32 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, ' match_type(cx, ty, &paths::BTREEMAP) || is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) { if method.ident.name == sym!(len) { - let span = shorten_needless_collect_span(expr); + let span = shorten_span(expr, sym!(collect)); span_lint_and_sugg( cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, "replace with", - ".count()".to_string(), + "count()".to_string(), Applicability::MachineApplicable, ); } if method.ident.name == sym!(is_empty) { - let span = shorten_needless_collect_span(expr); + let span = shorten_span(expr, sym!(iter)); span_lint_and_sugg( cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, "replace with", - ".next().is_none()".to_string(), + "get(0).is_none()".to_string(), Applicability::MachineApplicable, ); } if method.ident.name == sym!(contains) { let contains_arg = snippet(cx, args[1].span, "??"); - let span = shorten_needless_collect_span(expr); + let span = shorten_span(expr, sym!(collect)); span_lint_and_then( cx, NEEDLESS_COLLECT, @@ -2422,7 +2422,7 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, ' span, "replace with", format!( - ".any(|{}| x == {})", + "any(|{}| x == {})", arg, pred ), Applicability::MachineApplicable, @@ -2435,13 +2435,13 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, ' } } -fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { - if_chain! { - if let ExprKind::MethodCall(_, _, ref args) = expr.kind; - if let ExprKind::MethodCall(_, ref span, _) = args[0].kind; - then { - return expr.span.with_lo(span.lo() - BytePos(1)); +fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span { + let mut current_expr = expr; + while let ExprKind::MethodCall(ref path, ref span, ref args) = current_expr.kind { + if path.ident.name == target_fn_name { + return expr.span.with_lo(span.lo()); } + current_expr = &args[0]; } unreachable!() } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 32b3b7f7947..7cb04d4d81c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -26,7 +26,7 @@ use rustc_span::symbol::{sym, SymbolStr}; use crate::consts::{constant, Constant}; use crate::utils::usage::mutated_variables; use crate::utils::{ - get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy, + get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy, is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability, @@ -1242,6 +1242,32 @@ declare_clippy_lint! { "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" } +declare_clippy_lint! { + /// **What it does:** Checks for usage of `iter().next()` on a Slice or an Array + /// + /// **Why is this bad?** These can be shortened into `.get()` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a[2..].iter().next(); + /// b.iter().next(); + /// ``` + /// should be written as: + /// ```rust + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a.get(2); + /// b.get(0); + /// ``` + pub ITER_NEXT_SLICE, + style, + "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`" +} + declare_lint_pass!(Methods => [ UNWRAP_USED, EXPECT_USED, @@ -1273,6 +1299,7 @@ declare_lint_pass!(Methods => [ FIND_MAP, MAP_FLATTEN, ITERATOR_STEP_BY_ZERO, + ITER_NEXT_SLICE, ITER_NTH, ITER_NTH_ZERO, ITER_SKIP_NEXT, @@ -1320,6 +1347,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { }, ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), + ["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]), ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]), @@ -2184,6 +2212,60 @@ fn lint_step_by<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, args } } +fn lint_iter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { + let caller_expr = &iter_args[0]; + + // Skip lint if the `iter().next()` expression is a for loop argument, + // since it is already covered by `&loops::ITER_NEXT_LOOP` + let mut parent_expr_opt = get_parent_expr(cx, expr); + while let Some(parent_expr) = parent_expr_opt { + if higher::for_loop(parent_expr).is_some() { + return; + } + parent_expr_opt = get_parent_expr(cx, parent_expr); + } + + if derefs_to_slice(cx, caller_expr, cx.tables.expr_ty(caller_expr)).is_some() { + // caller is a Slice + if_chain! { + if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind; + if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) + = higher::range(cx, index_expr); + if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind; + if let ast::LitKind::Int(start_idx, _) = start_lit.node; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "Using `.iter().next()` on a Slice without end index.", + "try calling", + format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx), + applicability, + ); + } + } + } else if is_type_diagnostic_item(cx, cx.tables.expr_ty(caller_expr), sym!(vec_type)) + || matches!(&walk_ptrs_ty(cx.tables.expr_ty(caller_expr)).kind, ty::Array(_, _)) + { + // caller is a Vec or an Array + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "Using `.iter().next()` on an array", + "try calling", + format!( + "{}.get(0)", + snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability) + ), + applicability, + ); + } +} + fn lint_iter_nth<'a, 'tcx>( cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index 28183810df4..a971d041ca6 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -424,7 +424,7 @@ fn erode_from_back(s: &str) -> String { } fn span_of_first_expr_in_block(block: &ast::Block) -> Option { - block.stmts.iter().next().map(|stmt| stmt.span) + block.stmts.get(0).map(|stmt| stmt.span) } #[cfg(test)] diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 8211a57b564..79da1f3702e 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -934,6 +934,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "loops", }, + Lint { + name: "iter_next_slice", + group: "style", + desc: "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`", + deprecation: None, + module: "methods", + }, Lint { name: "iter_nth", group: "perf", diff --git a/tests/ui/into_iter_on_ref.fixed b/tests/ui/into_iter_on_ref.fixed index c30d23de3f8..7f92d0dbdc9 100644 --- a/tests/ui/into_iter_on_ref.fixed +++ b/tests/ui/into_iter_on_ref.fixed @@ -40,4 +40,6 @@ fn main() { let _ = (&HashSet::::new()).iter(); //~ WARN equivalent to .iter() let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter() let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter() + + let _ = (&[1, 2, 3]).iter().next(); //~ WARN equivalent to .iter() } diff --git a/tests/ui/into_iter_on_ref.rs b/tests/ui/into_iter_on_ref.rs index 94bc1689619..416056d3fdb 100644 --- a/tests/ui/into_iter_on_ref.rs +++ b/tests/ui/into_iter_on_ref.rs @@ -40,4 +40,6 @@ fn main() { let _ = (&HashSet::::new()).into_iter(); //~ WARN equivalent to .iter() let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() + + let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter() } diff --git a/tests/ui/into_iter_on_ref.stderr b/tests/ui/into_iter_on_ref.stderr index 80e2d104f82..1cd6400b019 100644 --- a/tests/ui/into_iter_on_ref.stderr +++ b/tests/ui/into_iter_on_ref.stderr @@ -156,5 +156,11 @@ error: this `.into_iter()` call is equivalent to `.iter()` and will not move the LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() | ^^^^^^^^^ help: call directly: `iter` -error: aborting due to 26 previous errors +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `array` + --> $DIR/into_iter_on_ref.rs:44:26 + | +LL | let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: aborting due to 27 previous errors diff --git a/tests/ui/iter_next_slice.fixed b/tests/ui/iter_next_slice.fixed new file mode 100644 index 00000000000..79c1db87ac3 --- /dev/null +++ b/tests/ui/iter_next_slice.fixed @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::iter_next_slice)] + +fn main() { + // test code goes here + let s = [1, 2, 3]; + let v = vec![1, 2, 3]; + + s.get(0); + // Should be replaced by s.get(0) + + s.get(2); + // Should be replaced by s.get(2) + + v.get(5); + // Should be replaced by v.get(5) + + v.get(0); + // Should be replaced by v.get(0) + + let o = Some(5); + o.iter().next(); + // Shouldn't be linted since this is not a Slice or an Array +} diff --git a/tests/ui/iter_next_slice.rs b/tests/ui/iter_next_slice.rs new file mode 100644 index 00000000000..ef9a55f3d99 --- /dev/null +++ b/tests/ui/iter_next_slice.rs @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::iter_next_slice)] + +fn main() { + // test code goes here + let s = [1, 2, 3]; + let v = vec![1, 2, 3]; + + s.iter().next(); + // Should be replaced by s.get(0) + + s[2..].iter().next(); + // Should be replaced by s.get(2) + + v[5..].iter().next(); + // Should be replaced by v.get(5) + + v.iter().next(); + // Should be replaced by v.get(0) + + let o = Some(5); + o.iter().next(); + // Shouldn't be linted since this is not a Slice or an Array +} diff --git a/tests/ui/iter_next_slice.stderr b/tests/ui/iter_next_slice.stderr new file mode 100644 index 00000000000..bbf61df0cda --- /dev/null +++ b/tests/ui/iter_next_slice.stderr @@ -0,0 +1,28 @@ +error: Using `.iter().next()` on an array + --> $DIR/iter_next_slice.rs:9:5 + | +LL | s.iter().next(); + | ^^^^^^^^^^^^^^^ help: try calling: `s.get(0)` + | + = note: `-D clippy::iter-next-slice` implied by `-D warnings` + +error: Using `.iter().next()` on a Slice without end index. + --> $DIR/iter_next_slice.rs:12:5 + | +LL | s[2..].iter().next(); + | ^^^^^^^^^^^^^^^^^^^^ help: try calling: `s.get(2)` + +error: Using `.iter().next()` on a Slice without end index. + --> $DIR/iter_next_slice.rs:15:5 + | +LL | v[5..].iter().next(); + | ^^^^^^^^^^^^^^^^^^^^ help: try calling: `v.get(5)` + +error: Using `.iter().next()` on an array + --> $DIR/iter_next_slice.rs:18:5 + | +LL | v.iter().next(); + | ^^^^^^^^^^^^^^^ help: try calling: `v.get(0)` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index b4227eaf2f8..be37dc16b9a 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -9,7 +9,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; fn main() { let sample = [1; 5]; let len = sample.iter().count(); - if sample.iter().next().is_none() { + if sample.get(0).is_none() { // Empty } sample.iter().cloned().any(|x| x == 1); diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 8884c8e1612..9113aad90dd 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -1,28 +1,28 @@ error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:11:28 + --> $DIR/needless_collect.rs:11:29 | LL | let len = sample.iter().collect::>().len(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` | = note: `-D clippy::needless-collect` implied by `-D warnings` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:12:21 + --> $DIR/needless_collect.rs:12:15 | LL | if sample.iter().collect::>().is_empty() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.next().is_none()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get(0).is_none()` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:15:27 + --> $DIR/needless_collect.rs:15:28 | LL | sample.iter().cloned().collect::>().contains(&1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.any(|x| x == 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:16:34 + --> $DIR/needless_collect.rs:16:35 | LL | sample.iter().map(|x| (x, x)).collect::>().len(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: aborting due to 4 previous errors -- cgit 1.4.1-3-g733a5 From b89880a30ce4dd7887614f305a565b6779dc4825 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sun, 31 May 2020 15:19:31 -0700 Subject: Ran update_lints --- CHANGELOG.md | 2 +- clippy_lints/src/lib.rs | 6 +++--- src/lintlist/mod.rs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index c00f84bdb85..086a1141be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1555,7 +1555,6 @@ Released 2018-09-13 [`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else [`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next [`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization -[`sort_by_key_reverse`]: https://rust-lang.github.io/rust-clippy/master/index.html#sort_by_key_reverse [`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string [`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add [`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign @@ -1602,6 +1601,7 @@ Released 2018-09-13 [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed [`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation +[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fd832d11577..03addf1f4a4 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -780,7 +780,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &shadow::SHADOW_UNRELATED, &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, - &unnecessary_sort_by::UNNECESSARY_SORT_BY, &strings::STRING_ADD, &strings::STRING_ADD_ASSIGN, &strings::STRING_LIT_AS_BYTES, @@ -835,6 +834,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unicode::ZERO_WIDTH_SPACE, &unnamed_address::FN_ADDRESS_COMPARISONS, &unnamed_address::VTABLE_ADDRESS_COMPARISONS, + &unnecessary_sort_by::UNNECESSARY_SORT_BY, &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, &unused_io_amount::UNUSED_IO_AMOUNT, &unused_self::UNUSED_SELF, @@ -1394,7 +1394,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), - LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -1431,6 +1430,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unicode::ZERO_WIDTH_SPACE), LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), @@ -1596,7 +1596,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), - LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&swap::MANUAL_SWAP), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), @@ -1613,6 +1612,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNIT_ARG), LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index b5d9ef0110e..ab9542a7b9c 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1984,13 +1984,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "slow_vector_initialization", }, - Lint { - name: "sort_by_key_reverse", - group: "complexity", - desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer", - deprecation: None, - module: "sort_by_key_reverse", - }, Lint { name: "string_add", group: "restriction", @@ -2299,6 +2292,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "no_effect", }, + Lint { + name: "unnecessary_sort_by", + group: "complexity", + desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer", + deprecation: None, + module: "unnecessary_sort_by", + }, Lint { name: "unnecessary_unwrap", group: "complexity", -- cgit 1.4.1-3-g733a5 From 9893254dff38c2644612c8465ae9abfa553f4ea3 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 31 May 2020 12:08:41 +0200 Subject: Add more corrected code for doc --- clippy_lints/src/methods/mod.rs | 65 ++++++++++++++++++++++++++-------- clippy_lints/src/misc.rs | 2 +- clippy_lints/src/vec.rs | 10 ++++-- clippy_lints/src/verbose_file_reads.rs | 1 + clippy_lints/src/wildcard_imports.rs | 12 +++++-- clippy_lints/src/write.rs | 22 ++++++++++-- clippy_lints/src/zero_div_zero.rs | 6 +++- src/lintlist/mod.rs | 2 +- 8 files changed, 95 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 52ca962e7ef..fbc29efdeb2 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -218,7 +218,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let x = Ok::<_, ()>(()); - /// x.ok().expect("why did I do this again?") + /// + /// // Bad + /// x.ok().expect("why did I do this again?"); + /// + /// // Good + /// x.expect("why did I do this again?"); /// ``` pub OK_EXPECT, style, @@ -273,8 +278,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let opt = Some(1); - /// opt.map_or(None, |a| Some(a + 1)) - /// # ; + /// + /// // Bad + /// opt.map_or(None, |a| Some(a + 1)); + /// + /// // Good + /// opt.and_then(|a| Some(a + 1)); /// ``` pub OPTION_MAP_OR_NONE, style, @@ -390,14 +399,19 @@ declare_clippy_lint! { /// **What it does:** Checks for usage of `_.map(_).flatten(_)`, /// /// **Why is this bad?** Readability, this can be written more concisely as a - /// single method call. + /// single method call using `_.flat_map(_)` /// /// **Known problems:** /// /// **Example:** /// ```rust /// let vec = vec![vec![1]]; + /// + /// // Bad /// vec.iter().map(|x| x.iter()).flatten(); + /// + /// // Good + /// vec.iter().flat_map(|x| x.iter()); /// ``` pub MAP_FLATTEN, pedantic, @@ -417,7 +431,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let vec = vec![1]; + /// + /// // Bad /// vec.iter().filter(|x| **x == 0).map(|x| *x * 2); + /// + /// // Good + /// vec.iter().filter_map(|x| Some(*x * 2)); /// ``` pub FILTER_MAP, pedantic, @@ -634,7 +653,12 @@ declare_clippy_lint! { /// ```rust /// # use std::rc::Rc; /// let x = Rc::new(1); + /// + /// // Bad /// x.clone(); + /// + /// // Good + /// Rc::clone(&x); /// ``` pub CLONE_ON_REF_PTR, restriction, @@ -741,7 +765,12 @@ declare_clippy_lint! { /// **Known problems:** Does not catch multi-byte unicode characters. /// /// **Example:** - /// `_.split("x")` could be `_.split('x')` + /// ```rust,ignore + /// // Bad + /// _.split("x"); + /// + /// // Good + /// _.split('x'); pub SINGLE_CHAR_PATTERN, perf, "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" @@ -964,8 +993,8 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for usage of `.chars().last()` or - /// `.chars().next_back()` on a `str` to check if it ends with a given char. + /// **What it does:** Checks for usage of `_.chars().last()` or + /// `_.chars().next_back()` on a `str` to check if it ends with a given char. /// /// **Why is this bad?** Readability, this can be written more concisely as /// `_.ends_with(_)`. @@ -975,8 +1004,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let name = "_"; - /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-') - /// # ; + /// + /// // Bad + /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// + /// // Good + /// name.ends_with('_') || name.ends_with('-'); /// ``` pub CHARS_LAST_CMP, style, @@ -1044,17 +1077,15 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); - /// ``` - /// As there is no transformation of the argument this could be written as: - /// ```rust + /// + /// // As there is no transformation of the argument this could be written as: /// let _ = (0..3).filter(|&x| x > 2); /// ``` /// /// ```rust /// let _ = (0..4).filter_map(|x| Some(x + 1)); - /// ``` - /// As there is no conditional check on the argument this could be written as: - /// ```rust + /// + /// // As there is no conditional check on the argument this could be written as: /// let _ = (0..4).map(|x| x + 1); /// ``` pub UNNECESSARY_FILTER_MAP, @@ -1075,7 +1106,11 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// // Bad /// let _ = (&vec![3, 4, 5]).into_iter(); + /// + /// // Good + /// let _ = (&vec![3, 4, 5]).iter(); /// ``` pub INTO_ITER_ON_REF, style, diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index a3b7134a376..51282ab93ef 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -93,7 +93,7 @@ declare_clippy_lint! { /// ```rust /// let x = 1.2331f64; /// let y = 1.2332f64; - /// + /// /// // Bad /// if y == 1.23f64 { } /// if y != x {} // where both are floats diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 1174f421577..a8d4c7620b1 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -17,8 +17,14 @@ declare_clippy_lint! { /// **Known problems:** None. /// /// **Example:** - /// ```rust,ignore - /// foo(&vec![1, 2]) + /// ```rust + /// # fn foo(my_vec: &[u8]) {} + /// + /// // Bad + /// foo(&vec![1, 2]); + /// + /// // Good + /// foo(&[1, 2]); /// ``` pub USELESS_VEC, perf, diff --git a/clippy_lints/src/verbose_file_reads.rs b/clippy_lints/src/verbose_file_reads.rs index 4d8d4438d88..7247518e19b 100644 --- a/clippy_lints/src/verbose_file_reads.rs +++ b/clippy_lints/src/verbose_file_reads.rs @@ -9,6 +9,7 @@ declare_clippy_lint! { /// /// **Why is this bad?** `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// /// **Known problems:** None. /// /// **Example:** diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 32d9a45c37d..b637253bd02 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -19,8 +19,14 @@ declare_clippy_lint! { /// still around. /// /// **Example:** - /// ```rust + /// ```rust,ignore + /// // Bad /// use std::cmp::Ordering::*; + /// foo(Less); + /// + /// // Good + /// use std::cmp::Ordering; + /// foo(Ordering::Less) /// ``` pub ENUM_GLOB_USE, pedantic, @@ -60,15 +66,15 @@ declare_clippy_lint! { /// /// **Example:** /// - /// Bad: /// ```rust,ignore + /// // Bad /// use crate1::*; /// /// foo(); /// ``` /// - /// Good: /// ```rust,ignore + /// // Good /// use crate1::foo; /// /// foo(); diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 5f794598052..22ce484b24e 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -23,7 +23,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// println!(""); + /// + /// // Good + /// println!(); /// ``` pub PRINTLN_EMPTY_STRING, style, @@ -32,8 +36,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// **What it does:** This lint warns when you use `print!()` with a format - /// string that - /// ends in a newline. + /// string that ends in a newline. /// /// **Why is this bad?** You should use `println!()` instead, which appends the /// newline. @@ -125,7 +128,12 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); + /// + /// // Bad /// writeln!(buf, ""); + /// + /// // Good + /// writeln!(buf); /// ``` pub WRITELN_EMPTY_STRING, style, @@ -147,7 +155,12 @@ declare_clippy_lint! { /// # use std::fmt::Write; /// # let mut buf = String::new(); /// # let name = "World"; + /// + /// // Bad /// write!(buf, "Hello {}!\n", name); + /// + /// // Good + /// writeln!(buf, "Hello {}!", name); /// ``` pub WRITE_WITH_NEWLINE, style, @@ -168,7 +181,12 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); + /// + /// // Bad /// writeln!(buf, "{}", "foo"); + /// + /// // Good + /// writeln!(buf, "foo"); /// ``` pub WRITE_LITERAL, style, diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index fb4700d8743..0820385e01b 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -14,7 +14,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust - /// 0.0f32 / 0.0; + /// // Bad + /// let nan = 0.0f32 / 0.0; + /// + /// // Good + /// let nan = f32::NAN; /// ``` pub ZERO_DIVIDED_BY_ZERO, complexity, diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index ab9542a7b9c..6b6e2c7324c 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1735,7 +1735,7 @@ pub static ref ALL_LINTS: Vec = vec![ Lint { name: "pub_enum_variant_names", group: "pedantic", - desc: "enums where all variants share a prefix/postfix", + desc: "public enums where all variants share a prefix/postfix", deprecation: None, module: "enum_variants", }, -- cgit 1.4.1-3-g733a5 From dc13016ac2df1ce2663660389409b15eb2cf7e40 Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Wed, 3 Jun 2020 01:13:57 +0200 Subject: Make let_and_return a late lint pass --- clippy_lints/src/let_and_return.rs | 82 ++++++++++++++++++++++++++++++++++++++ clippy_lints/src/lib.rs | 8 ++-- clippy_lints/src/returns.rs | 82 ++------------------------------------ src/lintlist/mod.rs | 2 +- 4 files changed, 91 insertions(+), 83 deletions(-) create mode 100644 clippy_lints/src/let_and_return.rs (limited to 'src') diff --git a/clippy_lints/src/let_and_return.rs b/clippy_lints/src/let_and_return.rs new file mode 100644 index 00000000000..8b877f696af --- /dev/null +++ b/clippy_lints/src/let_and_return.rs @@ -0,0 +1,82 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Block, ExprKind, PatKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{in_macro, match_qpath, snippet_opt, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for `let`-bindings, which are subsequently + /// returned. + /// + /// **Why is this bad?** It is just extraneous code. Remove it to make your code + /// more rusty. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo() -> String { + /// let x = String::new(); + /// x + /// } + /// ``` + /// instead, use + /// ``` + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + pub LET_AND_RETURN, + style, + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" +} + +declare_lint_pass!(LetReturn => [LET_AND_RETURN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetReturn { + fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) { + // we need both a let-binding stmt and an expr + if_chain! { + if let Some(retexpr) = block.expr; + if let Some(stmt) = block.stmts.iter().last(); + if let StmtKind::Local(local) = &stmt.kind; + if local.ty.is_none(); + if local.attrs.is_empty(); + if let Some(initexpr) = &local.init; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + if let ExprKind::Path(qpath) = &retexpr.kind; + if match_qpath(qpath, &[&*ident.name.as_str()]); + if !in_external_macro(cx.sess(), initexpr.span); + if !in_external_macro(cx.sess(), retexpr.span); + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); + then { + span_lint_and_then( + cx, + LET_AND_RETURN, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(snippet) = snippet_opt(cx, initexpr.span) { + err.multipart_suggestion( + "return the expression directly", + vec![ + (local.span, String::new()), + (retexpr.span, snippet), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, + ); + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6f8923b2660..6bc9e23bac5 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -239,6 +239,7 @@ mod large_const_arrays; mod large_enum_variant; mod large_stack_arrays; mod len_zero; +mod let_and_return; mod let_if_seq; mod let_underscore; mod lifetimes; @@ -596,6 +597,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &large_stack_arrays::LARGE_STACK_ARRAYS, &len_zero::LEN_WITHOUT_IS_EMPTY, &len_zero::LEN_ZERO, + &let_and_return::LET_AND_RETURN, &let_if_seq::USELESS_LET_IF_SEQ, &let_underscore::LET_UNDERSCORE_LOCK, &let_underscore::LET_UNDERSCORE_MUST_USE, @@ -772,7 +774,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ®ex::INVALID_REGEX, ®ex::REGEX_MACRO, ®ex::TRIVIAL_REGEX, - &returns::LET_AND_RETURN, &returns::NEEDLESS_RETURN, &returns::UNUSED_UNIT, &serde_api::SERDE_API_MISUSE, @@ -1022,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box formatting::Formatting); store.register_early_pass(|| box misc_early::MiscEarlyLints); store.register_early_pass(|| box returns::Return); + store.register_late_pass(|| box let_and_return::LetReturn); store.register_early_pass(|| box collapsible_if::CollapsibleIf); store.register_early_pass(|| box items_after_statements::ItemsAfterStatements); store.register_early_pass(|| box precedence::Precedence); @@ -1265,6 +1267,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_and_return::LET_AND_RETURN), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(&lifetimes::NEEDLESS_LIFETIMES), @@ -1390,7 +1393,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(®ex::INVALID_REGEX), LintId::of(®ex::REGEX_MACRO), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&returns::LET_AND_RETURN), LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&serde_api::SERDE_API_MISUSE), @@ -1474,6 +1476,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inherent_to_string::INHERENT_TO_STRING), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_and_return::LET_AND_RETURN), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::FOR_KV_MAP), @@ -1526,7 +1529,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(®ex::REGEX_MACRO), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&returns::LET_AND_RETURN), LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 35464f629c3..3c939744173 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -8,7 +8,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::BytePos; -use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_sugg, span_lint_and_then}; +use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then}; declare_clippy_lint! { /// **What it does:** Checks for return statements at the end of a block. @@ -36,33 +36,6 @@ declare_clippy_lint! { "using a return statement like `return expr;` where an expression would suffice" } -declare_clippy_lint! { - /// **What it does:** Checks for `let`-bindings, which are subsequently - /// returned. - /// - /// **Why is this bad?** It is just extraneous code. Remove it to make your code - /// more rusty. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// fn foo() -> String { - /// let x = String::new(); - /// x - /// } - /// ``` - /// instead, use - /// ``` - /// fn foo() -> String { - /// String::new() - /// } - /// ``` - pub LET_AND_RETURN, - style, - "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" -} - declare_clippy_lint! { /// **What it does:** Checks for unit (`()`) expressions that can be removed. /// @@ -90,7 +63,7 @@ enum RetReplacement { Block, } -declare_lint_pass!(Return => [NEEDLESS_RETURN, LET_AND_RETURN, UNUSED_UNIT]); +declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]); impl Return { // Check the final stmt or expr in a block for unnecessary return. @@ -105,7 +78,7 @@ impl Return { } } - // Check a the final expression in a block if it's a return. + // Check the final expression in a block if it's a return. fn check_final_expr( &mut self, cx: &EarlyContext<'_>, @@ -186,54 +159,6 @@ impl Return { }, } } - - // Check for "let x = EXPR; x" - fn check_let_return(cx: &EarlyContext<'_>, block: &ast::Block) { - let mut it = block.stmts.iter(); - - // we need both a let-binding stmt and an expr - if_chain! { - if let Some(retexpr) = it.next_back(); - if let ast::StmtKind::Expr(ref retexpr) = retexpr.kind; - if let Some(stmt) = it.next_back(); - if let ast::StmtKind::Local(ref local) = stmt.kind; - // don't lint in the presence of type inference - if local.ty.is_none(); - if local.attrs.is_empty(); - if let Some(ref initexpr) = local.init; - if let ast::PatKind::Ident(_, ident, _) = local.pat.kind; - if let ast::ExprKind::Path(_, ref path) = retexpr.kind; - if match_path_ast(path, &[&*ident.name.as_str()]); - if !in_external_macro(cx.sess(), initexpr.span); - if !in_external_macro(cx.sess(), retexpr.span); - if !in_external_macro(cx.sess(), local.span); - if !in_macro(local.span); - then { - span_lint_and_then( - cx, - LET_AND_RETURN, - retexpr.span, - "returning the result of a `let` binding from a block", - |err| { - err.span_label(local.span, "unnecessary `let` binding"); - - if let Some(snippet) = snippet_opt(cx, initexpr.span) { - err.multipart_suggestion( - "return the expression directly", - vec![ - (local.span, String::new()), - (retexpr.span, snippet), - ], - Applicability::MachineApplicable, - ); - } else { - err.span_help(initexpr.span, "this expression can be directly returned"); - } - }, - ); - } - } - } } impl EarlyLintPass for Return { @@ -254,7 +179,6 @@ impl EarlyLintPass for Return { } fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { - Self::check_let_return(cx, block); if_chain! { if let Some(ref stmt) = block.stmts.last(); if let ast::StmtKind::Expr(ref expr) = stmt.kind; diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index d5d07ccb2eb..bb191f9be92 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1023,7 +1023,7 @@ pub static ref ALL_LINTS: Vec = vec![ group: "style", desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block", deprecation: None, - module: "returns", + module: "let_and_return", }, Lint { name: "let_underscore_lock", -- cgit 1.4.1-3-g733a5 From 7b6dc7b33dc437a59330ef3f5426102ca60fbf51 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 19 Mar 2020 14:14:52 +0100 Subject: add `unnested_or_patterns` lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 7 + clippy_lints/src/unnested_or_patterns.rs | 407 +++++++++++++++++++++++ clippy_lints/src/utils/ast_utils.rs | 525 ++++++++++++++++++++++++++++++ clippy_lints/src/utils/hir_utils.rs | 10 +- clippy_lints/src/utils/mod.rs | 3 +- src/lintlist/mod.rs | 7 + tests/ui/neg_cmp_op_on_partial_ord.rs | 1 + tests/ui/neg_cmp_op_on_partial_ord.stderr | 8 +- tests/ui/unnested_or_patterns.fixed | 41 +++ tests/ui/unnested_or_patterns.rs | 41 +++ tests/ui/unnested_or_patterns.stderr | 267 +++++++++++++++ tests/ui/wildcard_enum_match_arm.fixed | 3 +- tests/ui/wildcard_enum_match_arm.rs | 3 +- tests/ui/wildcard_enum_match_arm.stderr | 10 +- 15 files changed, 1314 insertions(+), 20 deletions(-) create mode 100644 clippy_lints/src/unnested_or_patterns.rs create mode 100755 clippy_lints/src/utils/ast_utils.rs create mode 100644 tests/ui/unnested_or_patterns.fixed create mode 100644 tests/ui/unnested_or_patterns.rs create mode 100644 tests/ui/unnested_or_patterns.stderr (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a184480fb..adc945a6944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1683,6 +1683,7 @@ Released 2018-09-13 [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern +[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns [`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable [`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal [`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6f8923b2660..9809f953d67 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,5 +1,6 @@ // error-pattern:cargo-clippy +#![feature(bindings_after_at)] #![feature(box_syntax)] #![feature(box_patterns)] #![feature(or_patterns)] @@ -12,6 +13,7 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![feature(crate_visibility_modifier)] #![feature(concat_idents)] +#![feature(drain_filter)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) @@ -319,6 +321,7 @@ mod types; mod unicode; mod unnamed_address; mod unnecessary_sort_by; +mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_io_amount; mod unused_self; @@ -836,6 +839,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unnamed_address::FN_ADDRESS_COMPARISONS, &unnamed_address::VTABLE_ADDRESS_COMPARISONS, &unnecessary_sort_by::UNNECESSARY_SORT_BY, + &unnested_or_patterns::UNNESTED_OR_PATTERNS, &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, &unused_io_amount::UNUSED_IO_AMOUNT, &unused_self::UNUSED_SELF, @@ -1073,6 +1077,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { single_char_binding_names_threshold, }); + store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1433,6 +1438,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), + LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), @@ -1616,6 +1622,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), + LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs new file mode 100644 index 00000000000..2723af03c0b --- /dev/null +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -0,0 +1,407 @@ +#![allow(clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path}; +use crate::utils::{over, span_lint_and_then}; +use rustc_ast::ast::{self, Pat, PatKind, PatKind::*, DUMMY_NODE_ID}; +use rustc_ast::mut_visit::*; +use rustc_ast::ptr::P; +use rustc_ast_pretty::pprust; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::DUMMY_SP; + +use std::cell::Cell; +use std::mem; + +declare_clippy_lint! { + /// **What it does:** + /// + /// Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and + /// suggests replacing the pattern with a nested one, `Some(0 | 2)`. + /// + /// Another way to think of this is that it rewrites patterns in + /// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*. + /// + /// **Why is this bad?** + /// + /// In the example above, `Some` is repeated, which unncessarily complicates the pattern. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn main() { + /// if let Some(0) | Some(2) = Some(0) {} + /// } + /// ``` + /// Use instead: + /// ```rust + /// #![feature(or_patterns)] + /// + /// fn main() { + /// if let Some(0 | 2) = Some(0) {} + /// } + /// ``` + pub UNNESTED_OR_PATTERNS, + complexity, + "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`" +} + +declare_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]); + +impl EarlyLintPass for UnnestedOrPatterns { + fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) { + lint_unnested_or_patterns(cx, &a.pat); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::Let(pat, _) = &e.kind { + lint_unnested_or_patterns(cx, pat); + } + } + + fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) { + lint_unnested_or_patterns(cx, &p.pat); + } + + fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) { + lint_unnested_or_patterns(cx, &l.pat); + } +} + +fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { + if !cx.sess.opts.unstable_features.is_nightly_build() { + // User cannot do `#![feature(or_patterns)]`, so bail. + return; + } + + if let Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) = pat.kind { + // This is a leaf pattern, so cloning is unprofitable. + return; + } + + let mut pat = P(pat.clone()); + + // Nix all the paren patterns everywhere so that they aren't in our way. + remove_all_parens(&mut pat); + + // Transform all unnested or-patterns into nested ones, and if there were none, quit. + if !unnest_or_patterns(&mut pat) { + return; + } + + span_lint_and_then(cx, UNNESTED_OR_PATTERNS, pat.span, "unnested or-patterns", |db| { + insert_necessary_parens(&mut pat); + db.span_suggestion_verbose( + pat.span, + "nest the patterns", + pprust::pat_to_string(&pat), + Applicability::MachineApplicable, + ); + }); +} + +/// Remove all `(p)` patterns in `pat`. +fn remove_all_parens(pat: &mut P) { + struct Visitor; + impl MutVisitor for Visitor { + fn visit_pat(&mut self, pat: &mut P) { + noop_visit_pat(pat, self); + let inner = match &mut pat.kind { + Paren(i) => mem::replace(&mut i.kind, Wild), + _ => return, + }; + pat.kind = inner; + } + } + Visitor.visit_pat(pat); +} + +/// Insert parens where necessary according to Rust's precedence rules for patterns. +fn insert_necessary_parens(pat: &mut P) { + struct Visitor; + impl MutVisitor for Visitor { + fn visit_pat(&mut self, pat: &mut P) { + use ast::{BindingMode::*, Mutability::*}; + noop_visit_pat(pat, self); + let target = match &mut pat.kind { + // `i @ a | b`, `box a | b`, and `& mut? a | b`. + Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, + Ref(p, Not) if matches!(p.kind, Ident(ByValue(Mut), ..)) => p, // `&(mut x)` + _ => return, + }; + target.kind = Paren(P(take_pat(target))); + } + } + Visitor.visit_pat(pat); +} + +/// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. +/// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. +fn unnest_or_patterns(pat: &mut P) -> bool { + struct Visitor { + changed: bool, + } + impl MutVisitor for Visitor { + fn visit_pat(&mut self, p: &mut P) { + // This is a bottom up transformation, so recurse first. + noop_visit_pat(p, self); + + // Don't have an or-pattern? Just quit early on. + let alternatives = match &mut p.kind { + Or(ps) => ps, + _ => return, + }; + + // Collapse or-patterns directly nested in or-patterns. + let mut idx = 0; + let mut this_level_changed = false; + while idx < alternatives.len() { + let inner = if let Or(ps) = &mut alternatives[idx].kind { + mem::take(ps) + } else { + idx += 1; + continue; + }; + this_level_changed = true; + alternatives.splice(idx..=idx, inner); + } + + // Focus on `p_n` and then try to transform all `p_i` where `i > n`. + let mut focus_idx = 0; + while focus_idx < alternatives.len() { + this_level_changed |= transform_with_focus_on_idx(alternatives, focus_idx); + focus_idx += 1; + } + self.changed |= this_level_changed; + + // Deal with `Some(Some(0)) | Some(Some(1))`. + if this_level_changed { + noop_visit_pat(p, self); + } + } + } + + let mut visitor = Visitor { changed: false }; + visitor.visit_pat(pat); + visitor.changed +} + +/// Match `$scrutinee` against `$pat` and extract `$then` from it. +/// Panics if there is no match. +macro_rules! always_pat { + ($scrutinee:expr, $pat:pat => $then:expr) => { + match $scrutinee { + $pat => $then, + _ => unreachable!(), + } + }; +} + +/// Focus on `focus_idx` in `alternatives`, +/// attempting to extend it with elements of the same constructor `C` +/// in `alternatives[focus_idx + 1..]`. +fn transform_with_focus_on_idx(alternatives: &mut Vec>, focus_idx: usize) -> bool { + // Extract the kind; we'll need to make some changes in it. + let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild); + // We'll focus on `alternatives[focus_idx]`, + // so we're draining from `alternatives[focus_idx + 1..]`. + let start = focus_idx + 1; + + // We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`. + let changed = match &mut focus_kind { + // These pattern forms are "leafs" and do not have sub-patterns. + // Therefore they are not some form of constructor `C`, + // with which a pattern `C(P0)` may be formed, + // which we would want to join with other `C(Pj)`s. + Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) + // Dealt with elsewhere. + | Or(_) | Paren(_) => false, + // Transform `box x | ... | box y` into `box (x | y)`. + // + // The cases below until `Slice(...)` deal *singleton* products. + // These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`. + Box(target) => extend_with_matching( + target, start, alternatives, + |k| matches!(k, Box(_)), + |k| always_pat!(k, Box(p) => p), + ), + // Transform `&m x | ... | &m y` into `&m (x, y)`. + Ref(target, m1) => extend_with_matching( + target, start, alternatives, + |k| matches!(k, Ref(_, m2) if m1 == m2), // Mutabilities must match. + |k| always_pat!(k, Ref(p, _) => p), + ), + // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`. + Ident(b1, i1, Some(target)) => extend_with_matching( + target, start, alternatives, + // Binding names must match. + |k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)), + |k| always_pat!(k, Ident(_, _, Some(p)) => p), + ), + // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`. + Slice(ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!(k, Slice(ps2) if eq_pre_post(ps1, ps2, idx)), + |k| always_pat!(k, Slice(ps) => ps), + ), + // Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post]`. + Tuple(ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!(k, Tuple(ps2) if eq_pre_post(ps1, ps2, idx)), + |k| always_pat!(k, Tuple(ps) => ps), + ), + // Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post]`. + TupleStruct(path1, ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!( + k, + TupleStruct(path2, ps2) if eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx) + ), + |k| always_pat!(k, TupleStruct(_, ps) => ps), + ), + // Transform a record pattern `S { fp_0, ..., fp_n }`. + Struct(path1, fps1, rest1) => extend_with_struct_pat(path1, fps1, *rest1, start, alternatives), + }; + + alternatives[focus_idx].kind = focus_kind; + changed +} + +/// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`. +/// In particular, for a record pattern, the order in which the field patterns is irrelevant. +/// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern +/// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal. +fn extend_with_struct_pat( + path1: &ast::Path, + fps1: &mut Vec, + rest1: bool, + start: usize, + alternatives: &mut Vec>, +) -> bool { + (0..fps1.len()).any(|idx| { + let pos_in_2 = Cell::new(None); // The element `k`. + let tail_or = drain_matching( + start, + alternatives, + |k| { + matches!(k, Struct(path2, fps2, rest2) + if rest1 == *rest2 // If one struct pattern has `..` so must the other. + && eq_path(path1, path2) + && fps1.len() == fps2.len() + && fps1.iter().enumerate().all(|(idx_1, fp1)| { + if idx_1 == idx { + // In the case of `k`, we merely require identical field names + // so that we will transform into `ident_k: p1_k | p2_k`. + let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident)); + pos_in_2.set(pos); + pos.is_some() + } else { + fps2.iter().any(|fp2| eq_field_pat(fp1, fp2)) + } + })) + }, + // Extract `p2_k`. + |k| always_pat!(k, Struct(_, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat), + ); + extend_with_tail_or(&mut fps1[idx].pat, tail_or) + }) +} + +/// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`. +/// Here, the idea is that we fixate on some `p_k` in `C`, +/// allowing it to vary between two `targets` and `ps2` (returned by `extract`), +/// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post), +/// where `~` denotes semantic equality. +fn extend_with_matching_product( + targets: &mut Vec>, + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind, &[P], usize) -> bool, + extract: impl Fn(PatKind) -> Vec>, +) -> bool { + (0..targets.len()).any(|idx| { + let tail_or = drain_matching( + start, + alternatives, + |k| predicate(k, targets, idx), + |k| extract(k).swap_remove(idx), + ); + extend_with_tail_or(&mut targets[idx], tail_or) + }) +} + +/// Extract the pattern from the given one and replace it with `Wild`. +/// This is meant for temporarily swapping out the pattern for manipulation. +fn take_pat(from: &mut Pat) -> Pat { + let dummy = Pat { + id: DUMMY_NODE_ID, + kind: Wild, + span: DUMMY_SP, + }; + mem::replace(from, dummy) +} + +/// Extend `target` as an or-pattern with the alternatives +/// in `tail_or` if there are any and return if there were. +fn extend_with_tail_or(target: &mut Pat, tail_or: Vec>) -> bool { + fn extend(target: &mut Pat, mut tail_or: Vec>) { + match target { + // On an existing or-pattern in the target, append to it. + Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), + // Otherwise convert the target to an or-pattern. + target => { + let mut init_or = vec![P(take_pat(target))]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); + }, + } + } + + let changed = !tail_or.is_empty(); + if changed { + // Extend the target. + extend(target, tail_or); + } + changed +} + +// Extract all inner patterns in `alternatives` matching our `predicate`. +// Only elements beginning with `start` are considered for extraction. +fn drain_matching( + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind) -> bool, + extract: impl Fn(PatKind) -> P, +) -> Vec> { + let mut tail_or = vec![]; + let mut idx = 0; + for pat in alternatives.drain_filter(|p| { + // Check if we should extract, but only if `idx >= start`. + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.into_inner().kind)); + } + tail_or +} + +fn extend_with_matching( + target: &mut Pat, + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind) -> bool, + extract: impl Fn(PatKind) -> P, +) -> bool { + extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract)) +} + +/// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`? +fn eq_pre_post(ps1: &[P], ps2: &[P], idx: usize) -> bool { + ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`. + && ps1.len() == ps2.len() + && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r)) + && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r)) +} diff --git a/clippy_lints/src/utils/ast_utils.rs b/clippy_lints/src/utils/ast_utils.rs new file mode 100755 index 00000000000..69a7b6c051e --- /dev/null +++ b/clippy_lints/src/utils/ast_utils.rs @@ -0,0 +1,525 @@ +//! Utilities for manipulating and extracting information from `rustc_ast::ast`. +//! +//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s. + +#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::utils::{both, over}; +use rustc_ast::ast::{self, *}; +use rustc_ast::ptr::P; +use std::mem; + +/// Checks if each element in the first slice is contained within the latter as per `eq_fn`. +pub fn unordered_over(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { + left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r))) +} + +pub fn eq_id(l: Ident, r: Ident) -> bool { + l.name == r.name +} + +pub fn eq_pat(l: &Pat, r: &Pat) -> bool { + use PatKind::*; + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_pat(l, r), + (_, Paren(r)) => eq_pat(l, r), + (Wild, Wild) | (Rest, Rest) => true, + (Lit(l), Lit(r)) => eq_expr(l, r), + (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)), + (Range(lf, lt, le), Range(rf, rt, re)) => { + eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node) + }, + (Box(l), Box(r)) + | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) + | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), + (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp), + (TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)), + (Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => { + lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf)) + }, + (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + _ => false, + } +} + +pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool { + match (l, r) { + (RangeEnd::Excluded, RangeEnd::Excluded) => true, + (RangeEnd::Included(l), RangeEnd::Included(r)) => { + matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq) + }, + _ => false, + } +} + +pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_pat(&l.pat, &r.pat) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool { + l.position == r.position && eq_ty(&l.ty, &r.ty) +} + +pub fn eq_path(l: &Path, r: &Path) -> bool { + over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r)) +} + +pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool { + eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r)) +} + +pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool { + match (l, r) { + (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => { + over(&l.args, &r.args, |l, r| eq_angle_arg(l, r)) + }, + (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => { + over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output) + }, + _ => false, + } +} + +pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool { + match (l, r) { + (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r), + (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r), + _ => false, + } +} + +pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool { + match (l, r) { + (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident), + (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r), + (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value), + _ => false, + } +} + +pub fn eq_expr_opt(l: &Option>, r: &Option>) -> bool { + both(l, r, |l, r| eq_expr(l, r)) +} + +pub fn eq_expr(l: &Expr, r: &Expr) -> bool { + use ExprKind::*; + if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) { + return false; + } + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_expr(l, r), + (_, Paren(r)) => eq_expr(l, r), + (Err, Err) => true, + (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r), + (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)), + (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value), + (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), + (MethodCall(lc, la), MethodCall(rc, ra)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), + (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr), + (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r), + (Lit(l), Lit(r)) => l.kind == r.kind, + (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt), + (Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re), + (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re), + (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt), + (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => { + eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt) + }, + (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt), + (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb), + (TryBlock(l), TryBlock(r)) => eq_block(l, r), + (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r), + (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re), + (Continue(ll), Continue(rl)) => eq_label(ll, rl), + (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2), + (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv), + (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp), + (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)), + (Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => { + lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb) + }, + (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb), + (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt), + (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + (Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => { + eq_path(lp, rp) && eq_expr_opt(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r)) + }, + _ => false, + } +} + +pub fn eq_field(l: &Field, r: &Field) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_expr(&l.expr, &r.expr) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_arm(l: &Arm, r: &Arm) -> bool { + l.is_placeholder == r.is_placeholder + && eq_pat(&l.pat, &r.pat) + && eq_expr(&l.body, &r.body) + && eq_expr_opt(&l.guard, &r.guard) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_label(l: &Option